在android网络通信中,我们平常开发时都会使用诸如Volley,Retrofit,AsyncHttpClient等等别人的框架,确实十分的便捷,省去了我们不少的时间。但是使用第三方框架也会带来一些不可避免的缺点。
一、项目中大量使用第三方框架的缺点
下面,我们就仿照Volley框架的思想手撸一个属于自己的网络访问框架,可以在自己的项目中随意使用,这样既减少了项目的总体代码量,又能提高开发效率。
在开发之前,我们需要考虑许多问题:
二、开发一个网络框架需要考虑的问题
三、手写一个简单的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 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(4,20,15, TimeUnit.SECONDS,new ArrayBlockingQueue(4),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),
在请求接口中,我们需要做的事有三个
在响应接口中,我们需要做的事也很简单
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 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(6000);
//不使用缓存
urlConnection.setUseCaches(false);
urlConnection.setInstanceFollowRedirects(true);
//响应的超时时间
urlConnection.setReadTimeout(3000);
//设置这个连接是否可以写入数据
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 responseClass;
private IDataListener dataListener;
public JsonHttpListener(Class responseClass, IDataListener 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.java
/**
* 回调调用层的接口,数据返回的统一接口
*/
public interface IDataListener {
void onSuccess( M m );
void onFailure();
}
Volley.java
/**
* 用户请求的统一接口
*/
public class Volley {
public static void sendJsonRequest( T requestInfo , String url , Class response , IDataListener dataListener){
IHttpRequest httpRequest = new JsonHttpRequest();
IHttpListener httpListener = new JsonHttpListener<>(response,dataListener);
HttpTask httpTask = new HttpTask(requestInfo,url,httpRequest,httpListener);
ThreadPoolManager.getSingleIntance().execute(httpTask);
}
}
最后在MainActivity中测试
public void onClick(View view) {
//Student为自己定义的javaBean
Volley.sendJsonRequest(null, url, Student.class, new IDataListener() {
@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();
}
});
}
这样一来就大功告成啦,我们通过以上的步骤自己写出了一个初具雏形的网络访问框架,是不是很有成就感?我们也可以自己造轮子给别人用了!!!虽然目前为止还比较简单,只能请求Json数据,但是麻雀虽小,五脏俱全,想要拓展更多的功能,请求更多类型的数据,我们只需要实现IHttpRequest和IHttpListener这两个接口就行了。
而且,与Volley相比,我们不需要自己手动创建请求队列,返回的对象类型同样可以由用户自定义,网络访问不需要在额外的子线程进行,不需要自己进行线程的切换等等。