Volley手写属于自己的万能网络访问框架

时间:2023-03-08 22:36:34

用户在调用层(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,具体用法可以自行百度,唯一需要注意的就是其中的拒绝策略。

Volley手写属于自己的万能网络访问框架

代码编写

首先,我们需要一个线程池的管理类,来管理请求队列和处理中心处理任务。由于系统中仅有一个线程池管理的类,所以该类应该设计成单例模式

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), 
在请求接口中,我们需要做的事有三个

  1. 设置url
  2. 设置请求参数
  3. 执行请求

IHttpRequest.java

/**
* 封装请求
*/
public interface IHttpRequest {
void setUrl(String url);
void setRequestData( byte[] requestData );
void execute();
//需要设置请求和响应两个接口之间的关系
void setHttpCallBack( IHttpListener httpListener );
}

在响应接口中,我们需要做的事也很简单

  1. 处理结果
  2. 回调应用层

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();
}
}

最后两步

  1. 请求成功后,返回结果给调用层时,我们还要写一个数据返回的统一接口IDataListener来给用户使用(跟Volley一样)
  2. 不能让用户自己去创建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