Volley手写属于自己的万能网络访问框架
用户在调用层(Activity或Service中),发起一个网络请求,该请求肯定包含url,请求参数(requestParameter),以及我们需要给调用层提供一个请求成功或失败以后回调监听的接口dataListener(这一点与Volley类似)。
在框架层,每一次用户请求可以看做一个Http任务,这些任务我们可以用一个请求队列存储起来,框架工作时不断地从请求队列中取出任务放到处理中心中,处理中心是一个线程池ThreadPool。使用线程池可以带来3个好处:
1.降低资源消耗:通过重用已经创建的线程来降低线程创建和销毁的消耗
2.提高响应速度:任务到达时不需要等待线程创建就可以立即执行。
3.提高线程的可管理性:线程池可以统一管理、分配、调优和监控。
由于用户请求的数量是不确定的,所以请求队列的长度应该没有限制,所以这里的数据结构,我们应该使用链表。正好,java给我们提供了一个阻塞式队列LinkedBlockingQueue,它是一个单向链表实现的无界阻塞队列,先进先出,支持多线程并发操作。
再仔细考虑一下框架层的操作,用户不断发请求产生HttpTask,处理中心不断从请求队列中去任务,这和那个生产者消费者模式是不是很像?所以我们还需要考虑并发和同步问题,而LinkedBlockingQueue是线程安全的,实现了先进先出等特性,是作为生产者消费者的首选。LinkedBlockingQueue 可以指定队列的容量,也可以不指定,不指定的话,默认最大是Integer.MAX_VALUE,其中主要用到put和take方法,put方法在队列满的时候会阻塞直到有队列成员被消费,take方法在队列空的时候会阻塞,直到有队列成员被放进来。
至于处理中心的线程池,我们使用jdk里的ThreadPoolExecutor,具体用法可以自行百度,唯一需要注意的就是其中的拒绝策略。

代码编写
首先,我们需要一个线程池的管理类,来管理请求队列和处理中心处理任务。由于系统中仅有一个线程池管理的类,所以该类应该设计成单例模式
ThreadPoolManager.java
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; /**
1. 线程池管理类
*/
public class ThreadPoolManager {
//1.将请求任务放到请求队列中
//通过阻塞式队列来存储任务
private LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
//添加任务
public void execute( Runnable runnable ){
if( runnable != null ) {
try {
queue.put(runnable);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//2.把队列中的任务放到线程池
private ThreadPoolExecutor threadPoolExecutor ;
private ThreadPoolManager(){
threadPoolExecutor = new ThreadPoolExecutor(,,, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(),rejectedExecutionHandler);
//运行线程池
threadPoolExecutor.execute(runnable);
}
//当线程数超过maxPoolSize或者keep-alive时间超时时执行拒绝策略
private RejectedExecutionHandler rejectedExecutionHandler = new RejectedExecutionHandler() {
/**
* @param runnable 超时被线程池抛弃的线程
* @param threadPoolExecutor
*/
@Override
public void rejectedExecution(Runnable runnable, ThreadPoolExecutor threadPoolExecutor) {
//将该线程重新放入请求队列中
try {
queue.put(runnable);
} catch (InterruptedException e) {
e.printStackTrace();
} }
};
//3.让他们开始工作起来
//整个的工作线程
private Runnable runnable = new Runnable() {
@Override
public void run() {
while(true){
Runnable runnable = null ;
//不断从从请求队列中取出请求
try {
runnable = queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
//如果不为空,放入线程池中执行
if( runnable != null ){
threadPoolExecutor.execute(runnable);
}
} }
}; //单例模式
private static ThreadPoolManager singleInstance = new ThreadPoolManager();
public static ThreadPoolManager getSingleIntance(){
return singleInstance;
}
}
接下来我们需要将写两个接口将用户的网络访问操作分成两部分(参考架构图),一个是请求(IHttpRequest),一个是响应(IHttpListener),
在请求接口中,我们需要做的事有三个
- 设置url
- 设置请求参数
- 执行请求
IHttpRequest.java
/**
* 封装请求
*/
public interface IHttpRequest {
void setUrl(String url);
void setRequestData( byte[] requestData );
void execute();
//需要设置请求和响应两个接口之间的关系
void setHttpCallBack( IHttpListener httpListener );
}
在响应接口中,我们需要做的事也很简单
- 处理结果
- 回调应用层
IHttpListener.java
import java.io.InputStream; /**
* 封装响应
*/
public interface IHttpListener {
//接受上一个接口的结果
void onSuccess(InputStream inputStream);
void onFailure();
}
接下来我们编写代表请求任务的类HttpTask,让它实现Runnable接口,并且维护IHttpRequest和IHttpListener两个接口的引用,在HttpTask的构造方法中,进行一些初始化操作,设置请求的url,请求参数,设置监听器等等。在将请求参数对象转换成字符串的过程中,我们使用了阿里的fastjson这个jar包。最后在run方法中,执行httpRequest的请求。另外注意这里泛型的使用。
HttpTask.java
import com.alibaba.fastjson.JSON;
import java.io.UnsupportedEncodingException;
public class HttpTask<T> implements Runnable {
private IHttpRequest httpRequest;
private IHttpListener httpListener;
public<T> HttpTask( T requestInfo , String url , IHttpRequest httpRequest, IHttpListener httpListener){
this.httpRequest = httpRequest;
this.httpListener = httpListener;
//设置url
this.httpRequest.setUrl(url);
//设置响应回调
this.httpRequest.setHttpCallBack(httpListener);
//设置请求参数
if( requestInfo != null ){
//将用户发送的请求参数对象转换成字符串
String requestContent = JSON.toJSONString(requestInfo);
//字符串转byte数组
try {
this.httpRequest.setRequestData(requestContent.getBytes("utf-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
httpRequest.execute();
}
}
接下来我们编写IHttpRequest的实现类,JsonHttpRequest , 在重写的execute方法中,使用原生的HttpURLConnection执行网络请求,请求成功后,回调IHttpListener的onSuccess方法。
如果想要传送其他数据(图片,文件等),同样实现IHttpRequest该接口即可,所以这个框架的扩展性十分良好。
JsonHttpRequest.java
import java.io.BufferedOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL; /**
* Json版Http请求
*/ public class JsonHttpRequest implements IHttpRequest{ private String url ;
private byte[] requestData ;
private IHttpListener httpListener; @Override
public void setUrl(String url) {
this.url = url ;
} @Override
public void setRequestData(byte[] requestData) {
this.requestData = requestData;
} //原生的网络操作在这里实现
@Override
public void execute() {
httpUrlconnPost();
} private HttpURLConnection urlConnection = null ; public void httpUrlconnPost(){
URL url = null;
try{
url = new URL(this.url);
//打开http连接
urlConnection = (HttpURLConnection) url.openConnection();
//设置连接的超时时间
urlConnection.setConnectTimeout();
//不使用缓存
urlConnection.setUseCaches(false);
urlConnection.setInstanceFollowRedirects(true);
//响应的超时时间
urlConnection.setReadTimeout();
//设置这个连接是否可以写入数据
urlConnection.setDoInput(true);
//设置这个连接是否可以输出数据
urlConnection.setDoOutput(true);
//设置请求的方式
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
urlConnection.connect(); //使用字节流发送数据
OutputStream out = urlConnection.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(out);
if( requestData != null ){
//把字节数组的数据写入缓冲区
bos.write(requestData);
}
//刷新缓冲区,发送数据
bos.flush();
out.close();
bos.close(); //获得服务器响应
if( urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK){
InputStream in = urlConnection.getInputStream();
//回调监听器的listener方法
httpListener.onSuccess(in);
}
}catch ( Exception e){
e.printStackTrace();
}
} @Override
public void setHttpCallBack(IHttpListener httpListener) {
this.httpListener = httpListener;
}
}
接下来是IHttpListener的Json版本实现类,在该类中要注意的事就是回调结果给用户时要进行线程的切换(使用Handler),并且要将返回的json字符串转换成泛型对象(该对象由用户自定义)。
JsonHttpListener.java
import android.os.Handler;
import android.os.Looper; import com.alibaba.fastjson.JSON; import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader; /**
* Json版本的httpListener
*/ public class JsonHttpListener<M> implements IHttpListener{
Class<M> responseClass;
private IDataListener<M> dataListener; public JsonHttpListener(Class<M> responseClass, IDataListener<M> dataListener) {
this.responseClass = responseClass;
this.dataListener = dataListener;
} //用于切换线程
Handler handler = new Handler(Looper.getMainLooper());
@Override
public void onSuccess(InputStream inputStream) {
//获取响应结果,把byte数据转换成String数据
String content = getContent(inputStream);
//将json字符串转换成对象
final M response = JSON.parseObject(content,responseClass);
//把结果传送到调用层
handler.post(new Runnable() {
@Override
public void run() {
if( dataListener != null ){
dataListener.onSuccess(response);
} }
}); } @Override
public void onFailure() {
handler.post(new Runnable() {
@Override
public void run() {
if( dataListener != null ){
dataListener.onFailure();
} }
}); } /**
* 将流转换成字符串
* @param inputStream
* @return
*/
private String getContent(InputStream inputStream){
String content = null ;
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
String line = null ;
try{
while( (line = reader.readLine()) != null){
sb.append(line + "\n");
}
}catch ( IOException e ){
e.printStackTrace();
}finally {
try {
inputStream.close();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString();
}
}
最后两步
- 请求成功后,返回结果给调用层时,我们还要写一个数据返回的统一接口IDataListener来给用户使用(跟Volley一样)
- 不能让用户自己去创建HttpTask,用户请求也需要一个统一接口。
这样我们框架的封装性会更好。
IDataListener.java
/**
* 回调调用层的接口,数据返回的统一接口
*/ public interface IDataListener<M> {
void onSuccess( M m );
void onFailure();
}
Volley.java
/**
* 用户请求的统一接口
*/
public class Volley {
public static<T,M> void sendJsonRequest( T requestInfo , String url , Class<M> response , IDataListener<M> dataListener){
IHttpRequest httpRequest = new JsonHttpRequest();
IHttpListener httpListener = new JsonHttpListener<>(response,dataListener);
HttpTask<T> httpTask = new HttpTask<T>(requestInfo,url,httpRequest,httpListener);
ThreadPoolManager.getSingleIntance().execute(httpTask);
}
}
最后在MainActivity中测试
public void onClick(View view) {
//Student为自己定义的javaBean
Volley.sendJsonRequest(null, url, Student.class, new IDataListener<Student>() {
@Override
public void onSuccess(Student student) {
Toast.makeText(MainActivity.this,student.getName(),Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure() {
Toast.makeText(MainActivity.this,"请求失败",Toast.LENGTH_SHORT).show();
}
});
}
想要拓展更多的功能,请求更多类型的数据,我们只需要实现IHttpRequest和IHttpListener这两个接口就行了。
参考:https://www.jianshu.com/p/396ada3885cc
Volley手写属于自己的万能网络访问框架的更多相关文章
- 带你手写基于 Spring 的可插拔式 RPC 框架(一)介绍
概述 首先这篇文章是要带大家来实现一个框架,听到框架大家可能会觉得非常高大上,其实这和我们平时写业务员代码没什么区别,但是框架是要给别人使用的,所以我们要换位思考,怎么才能让别人用着舒服,怎么样才能让 ...
- 带你手写基于 Spring 的可插拔式 RPC 框架(二)整体结构
前言 上一篇文章中我们已经知道了什么是 RPC 框架和为什么要做一个 RPC 框架了,这一章我们来从宏观上分析,怎么来实现一个 RPC 框架,这个框架都有那些模块以及这些模块的作用. 总体设计 在我们 ...
- 带你手写基于 Spring 的可插拔式 RPC 框架(五)注册中心
注册中心代码使用 zookeeper 实现,我们通过图片来看看我们注册中心的架构. 首先说明, zookeeper 的实现思路和代码是参考架构探险这本书上的,另外在 github 和我前面配置文件中的 ...
- 带你手写基于 Spring 的可插拔式 RPC 框架(四)代理类的注入与服务启动
上一章节我们已经实现了从客户端往服务端发送数据并且通过反射方法调用服务端的实现类最后返回给客户端的底层协议. 这一章节我们来实现客户端代理类的注入. 承接上一章,我们实现了多个底层协议,procoto ...
- 带你手写基于 Spring 的可插拔式 RPC 框架(三)通信协议模块
在写代码之前我们先要想清楚几个问题. 我们的框架到底要实现什么功能? 我们要实现一个远程调用的 RPC 协议. 最终实现效果是什么样的? 我们能像调用本地服务一样调用远程的服务. 怎样实现上面的效果? ...
- Retrofit 网络访问框架简单使用
1.引入远程依赖:包括okhttp;retrofit2;retrofit的GSON解析器 compile'com.squareup.okhttp3:okhttp:3.2.0' compile'com. ...
- Android下基于线程池的网络访问基础框架
引言 现在的Android开发很多都使用Volley.OkHttp.Retrofit等框架,这些框架固然有优秀的地方(以后会写代码学习分享),但是我们今天介绍一种基于Java线程池的网络访问框架. 实 ...
- 基于Retrofit+RxJava的Android分层网络请求框架
目前已经有不少Android客户端在使用Retrofit+RxJava实现网络请求了,相比于xUtils,Volley等网络访问框架,其具有网络访问效率高(基于OkHttp).内存占用少.代码量小以及 ...
- Android网络请求框架之Retrofit实践
网络访问框架经过了从使用最原始的AsyncTask构建简单的网络访问框架(甚至不能称为框架),后来使用开源的android-async-http库,再到使用google发布的volley库,一直不懈的 ...
随机推荐
- 推荐eclipse插件Properties Editor(转)
Properties Editor 是一款properties文件编辑器. 需求:一般我们在做“国际化”功能时,我们需要properties中文表示方式用unicode表示.eclipse默认prop ...
- 【Web页面测试】测试点和测试用例
1. 需求符合度测试 1. 各级菜单名称显示是否按照需求说明书规定的设计,并且没有遗漏和多余 2. 各级菜单所完成的功能是否按照需求说明书规定的设计,并且没有遗漏和多余 3. 各级菜单的操作顺序和操作 ...
- Nginx Rewrite详解
Nginx Rewrite详解 引用链接:http://blog.cafeneko.info/2010/10/nginx_rewrite_note/ 原文如下: 在新主机的迁移过程中,最大的困难就是W ...
- strtok函数读写冲突问题
先上测试代码 #include "stdafx.h" #include <iostream> using namespace std; int _tmain(int a ...
- 表示一个文件的 File 类型
从本篇文章开始,我们将开启对 Java IO 系统的学习,本质上就是对文件的读写操作,听上去简单,其实并不容易.Java 的 IO 系统一直在完善和改进,设计了大量的类,也只有理解了这些类型被设计出来 ...
- Web移动端页面 --响应式和动态REM
响应式 什么是响应式页面呢? 顾名思义响应式页面就是能做出响应的页面,它的页面效果不是定死的,会随着用户的改变而改变. 如何着手响应式有以下几个思考的方向 找一份设计图 使用Media Query 隐 ...
- python 面向对象终极进阶之开发流程
好了,你现在会了面向对象的各种语法了, 但是你会发现很多同学都是学会了面向对象的语法,却依然写不出面向对象的程序,原因是什么呢?原因就是因为你还没掌握一门面向对象设计利器, 此刻有经验的人可能会想到 ...
- H5之画布canvas小记,以及通过画布实现原子无规则运动
我们知道html在h5出之前就仅仅只是一个标签,一个标记,语义化并不强,后来新增的标签如video,audio都是语义化更强(让人一看就懂是什么东西,反正我是这么理解的,一个div不代表着什么),本身 ...
- PAT1047: Student List for Course
1047. Student List for Course (25) 时间限制 400 ms 内存限制 64000 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Y ...
- ROC曲线的计算
1.ROC曲线简介 在评价分类模型时,会用到ROC(receiver operating characteristic)曲线.ROC曲线可用来评价二元分类器( binary classifier)的优 ...