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

  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

Volley手写属于自己的万能网络访问框架的更多相关文章

  1. 带你手写基于 Spring 的可插拔式 RPC 框架(一)介绍

    概述 首先这篇文章是要带大家来实现一个框架,听到框架大家可能会觉得非常高大上,其实这和我们平时写业务员代码没什么区别,但是框架是要给别人使用的,所以我们要换位思考,怎么才能让别人用着舒服,怎么样才能让 ...

  2. 带你手写基于 Spring 的可插拔式 RPC 框架(二)整体结构

    前言 上一篇文章中我们已经知道了什么是 RPC 框架和为什么要做一个 RPC 框架了,这一章我们来从宏观上分析,怎么来实现一个 RPC 框架,这个框架都有那些模块以及这些模块的作用. 总体设计 在我们 ...

  3. 带你手写基于 Spring 的可插拔式 RPC 框架(五)注册中心

    注册中心代码使用 zookeeper 实现,我们通过图片来看看我们注册中心的架构. 首先说明, zookeeper 的实现思路和代码是参考架构探险这本书上的,另外在 github 和我前面配置文件中的 ...

  4. 带你手写基于 Spring 的可插拔式 RPC 框架(四)代理类的注入与服务启动

    上一章节我们已经实现了从客户端往服务端发送数据并且通过反射方法调用服务端的实现类最后返回给客户端的底层协议. 这一章节我们来实现客户端代理类的注入. 承接上一章,我们实现了多个底层协议,procoto ...

  5. 带你手写基于 Spring 的可插拔式 RPC 框架(三)通信协议模块

    在写代码之前我们先要想清楚几个问题. 我们的框架到底要实现什么功能? 我们要实现一个远程调用的 RPC 协议. 最终实现效果是什么样的? 我们能像调用本地服务一样调用远程的服务. 怎样实现上面的效果? ...

  6. Retrofit 网络访问框架简单使用

    1.引入远程依赖:包括okhttp;retrofit2;retrofit的GSON解析器 compile'com.squareup.okhttp3:okhttp:3.2.0' compile'com. ...

  7. Android下基于线程池的网络访问基础框架

    引言 现在的Android开发很多都使用Volley.OkHttp.Retrofit等框架,这些框架固然有优秀的地方(以后会写代码学习分享),但是我们今天介绍一种基于Java线程池的网络访问框架. 实 ...

  8. 基于Retrofit+RxJava的Android分层网络请求框架

    目前已经有不少Android客户端在使用Retrofit+RxJava实现网络请求了,相比于xUtils,Volley等网络访问框架,其具有网络访问效率高(基于OkHttp).内存占用少.代码量小以及 ...

  9. Android网络请求框架之Retrofit实践

    网络访问框架经过了从使用最原始的AsyncTask构建简单的网络访问框架(甚至不能称为框架),后来使用开源的android-async-http库,再到使用google发布的volley库,一直不懈的 ...

随机推荐

  1. Spring3 MVC

    一.前言: 大家好,Spring3 MVC是非常优秀的MVC框架,由其是在3.0版本发布后,现在有越来越多的团队选择了Spring3 MVC了.Spring3 MVC结构简单,应了那句话简单就是美,而 ...

  2. 三大框架:Struts+Hibernate+Spring

    三大框架:Struts+Hibernate+Spring Java三大框架主要用来做WEN应用. Struts主要负责表示层的显示 Spring利用它的IOC和AOP来处理控制业务(负责对数据库的操作 ...

  3. windows下编译安装BOOST

    boost的编译和使用,经过搜集资料和总结,记录成文.感谢文后所列参考资料的作者. 1 下载 地址:http://sourceforge.net/projects/boost/files/boost/ ...

  4. Angular5 宏观把控

    1.首先,Angular5相对于Angular4有了一些新的特性: (1)i18n国际化管道: (2)一个组件可以以多个名称导出: (3)使用httpClient: 相比于http,httpClien ...

  5. JS实现鼠标放在文字上面显示全部内容

    web中当我们把text等的宽固定后如果文本框中内容过多就只能看到前面部分的内容,这时我们可以用样式控制当鼠标移到文本框时显示全部内容. var pointX; var pointY; $(funct ...

  6. 和同门一起做的PHP网站

    用的是PHP语言开发,使用ThinkPHP框架,数据库是mysql  统计是用的站长统计,前端很多都是别人网 上扒下来的.css.js水平不高啊.服务器是香港恒创科技的,性价比还可以哦,而且还免备案. ...

  7. SQL Server中表锁定的原理及解锁演示

    有几个朋友留言建议结合例子来演示一下, 上篇已经说过锁的几种类型, 可以利用系统动态视图sys.dm_tran_locks查看到,重要的栏位如下: resource_type 被锁的资源类型(Data ...

  8. 如何更好的编写async函数

    2018年已经到了5月份,node的4.x版本也已经停止了维护 我司的某个服务也已经切到了8.x,目前正在做koa2.x的迁移 将之前的generator全部替换为async 但是,在替换的过程中,发 ...

  9. VM虚拟机链接克隆及linux eth0网卡的快速设置方法

    对于后台开发者来说,在学习过程中必然接触众多中间件,在自己的虚拟机进行操作甚至搭建cluster是很常见的事情. 我在初学者时摸索出一套快速的克隆虚拟机方法.分享给大家. 产品VMware® Work ...

  10. Git分支合并冲突解决

    前2天群里发了张git历史图,如下: 根据提交历史,可以看出图中所有分支合并都采用merge的方式,具体merge是怎么操作的,可以阅读下边文章. 根据项目上的需求,如果要求git提交历史是比较简单的 ...