用户在调用层(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. selenium获取百度账户cookies

    [效果图] 效果图最后即为获取到的cookies,百度账户的cookies首次获取,需要手动登录,之后就可以注入cookies,实现免密登录. [代码] public class baiduCooki ...

  2. Day4_闭包含数

    闭包函数: 闭包函数是在作用域的前提下 闭包含数:定义在函数内部的函数,包含对外部作用域名字的引用,而不是对全局作用域名字的引用,那么该内部函数就称为闭包含数. eg: x=1 def f1(): x ...

  3. YUV420格式解析

    一般的的YUV420图像格式实际上是Y'UV,420指的是其在Y U V上面的采样率.在YUV420的格式中,首先存储每一个像素的Y'值,然后跟着存储的是每2*2方阵采样一次的U值,最后存储的是每2* ...

  4. 利用Python进行数据分析

    最近在阅读<利用Python进行数据分析>,本篇博文作为读书笔记 ,记录一下阅读书签和实践心得. 准备工作 python环境配置好了,可以参见我之前的博文<基于Python的数据分析 ...

  5. Navicat永久激活步骤,激活工具,解决注册码无效的问题

    Navicat for MySQL是一套管理和开发MySQL或MariaDB的理想解决方案,支持单一程序,可同时连接到MySQL和MariaDB.这个功能齐备的前端软件为数据库管理.开发和维护提供了直 ...

  6. SSM框架下声明式事务管理(注解配置方式)

    一.spring-mybatis.xml文件中加入事务管理配置 <?xml version="1.0" encoding="UTF-8"?> < ...

  7. lambda函数常见用法

    # lambda 参数:返回值/表达式 # print((lambda :100)()) # f = lambda a,b : a + b # print(f(10, 20)) # f = lambd ...

  8. 8.Vue基础

    环境搭建 node.js安装 https://nodejs.org/en/ cnpm npm install -g cnpm --registry=https://registry.npm.taoba ...

  9. 嵌入Python系列 | 调用Python模块中无参数函数

    开发环境 Python版本:3.6.4 (32-bit) 编辑器:Visual Studio Code C++环境:Visual Studio 2013 需求说明 在用VS2013编写的Win32程序 ...

  10. 0513JS数组内置方法、数学函数、时间函数

    |数组中常用的内置方法|-push()与pop()|--push()是往数组的尾部添加,同时返回新数组的长度 var attr = [1,2,3,4,5];var attr2 = [6,7,8,9,0 ...