下图为Volley执行的简单流程的步骤
这里以JsonRequest(即网络请求数据为json格式)为例: (先示例一段代码, 可以边看代码边分析)
1. 请求队列(缓存请求队列和网络请求队列)的初始化和启动 :
public class VolleyApplication extends Application{
@Override
public void onCreate() {
super.onCreate();
RequestTest.init(getApplicationContext());
}
}
===============================
public class RequestTest {
private static RequestQueue mRequestQueue;
public static void init(Context context) {
mRequestQueue = Volley.newRequestQueue(context);
}
/**
* 发生请求
* @param request
*/
public static void sendRequest(Request<?> request) {
mRequestQueue.add(request);
<span style="white-space:pre"> </span> // 将请求添加到队列中,代码中先判断请求是否需要缓存,需要则将请求添加到缓存请求队列中
<span style="white-space:pre"> </span> // 不需要则将请求添加到网络请求队列中
}
}<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
2 创建对应类继承JsonRequest : (传入对应的请求参数 , 并重写解析方法 对网络数据进行解析返回)
public class VolleyRequest<T> extends JsonRequest<T> {
private Gson mGson;
private Class<T> mClass;//T表示你想要解析的java bean的类
/**
*
* @param method 请求方法 GET POST (PUT DELETE),假设目前只有获取数据(只有GET可以直接写)
* @param url 网络请求地址
* @param requestBody 上传的数据
* @param listener 网络请求成功返回回调, 能够获取到解析后的结果
* @param errorListener 网络请求失败的回调
*/
public Volley<span style="font-family: Arial, Helvetica, sans-serif;">Request(Class<T> classz, String url, Listener<T> listener, ErrorListener errorListener) {</span>
super(Method.GET, url, null, listener, errorListener);
mGson = new Gson();
mClass = classz;
}
/**
* 解析网络请求的结果,回调listener,返回解析解析后的结果,前提是网络已经成功返回
* 在子线程解析 , 后边将解析结果分发给主线程
*/
@Override
protected Response parseNetworkResponse(NetworkResponse response) {
//将网络返回字节数组转成字符串
try {
String result= new String(response.data, PROTOCOL_CHARSET);
T resultBean = mGson.fromJson(result, mClass);
//从网络请求返回的响应头里面解析出缓存相关的数据,比如缓存过期时间
Cache.Entry cacheEntry = HttpHeaderParser.parseCacheHeaders(response);
//返回解析后的结果,构成一个Response
return Response.success(resultBean, cacheEntry);
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
3, 将请求添加到请求队列中
public class RequestDatas {
public void loadDatas(){
String url = "...";
VolleyRequest<TestBean> request = new VolleyRequest<TestBean>(TestBean.class<span style="font-family: Arial, Helvetica, sans-serif;">, url, </span>
successListener, errorListener);
RequestTest.sendRequest(request); // 即调用 <span style="font-family: Arial, Helvetica, sans-serif;">mRequestQueue.add(request);</span>
}
// 回调已经被分发在主线程中执行
private Listener<TestBean> successListener = new Listener<TestBean>(){
@Override
public void onResponse(TestBean bean) {
// 成功的回调
}
};
private ErrorListener errorListener = new ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 失败的回调
}
};
}
以下来通过源码分析每个步骤的具体实现:
从代码最初的初始化一个请求队列开始, 由于一个app中请求数据的频繁, 可以将初始化请求队列的代码写在application中,表示当应用一开始创建就初始化好;
RequestQueue requestQueue = Volley.newRequestQueue(getApplicationContext); // 这一行代码里面分别执行了1和2两个步骤
1 请求队列的初始化 :
具体分析 : (1) Volley.newRequestQueue(context)方法中调用了 :
newRequestQueue(context, null); 指的是 ==> newRequestQueue(Context context, HttpStack stack) 中又用了
newRequestQueue(context, stack, -1);指的是 ==> newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes)
其中参数 stack 指的是栈,默认为null, 参数maxDiskCacheBytes指的是最大磁盘缓存字节大小,默认为-1;
在 newRequestQueue(.....)方法中会有两个判断: if (stack == null) 和 if (maxDiskCacheBytes <= -1) 作为第一次初始化栈和磁盘缓存大小的条件
a . 初始化栈的请求方式:
SDK版本在9或以上里面封装的是HttpUrlConnextion比较常用的也比较轻量
SDK版本在9以前用的是阿帕奇下的第三方请求方式 :里面封装的是 httpclient,因为9以上的HttpUrlConnextion并不稳定(可能会出BUG)
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
接下来是创建了一个network对象 ==> Network network = new BasicNetwork(stack);
并把这个stack传给了一个网络工作的对象
b . 初始化了磁盘缓存的对象,里面定义了缓存的路径和大小 : 路径就是data/data/包名/cache/volley, 默认大小是5M
c . 最后是正式的创建一个请求队列的对象, 并把netWork对象和缓存对象传给他
queue = new RequestQueue(new DiskBasedCache(cacheDir), network);方法里调用了this.RequestQueue(....),
最后是调用了RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery)又传入了两个参数 :
线程池大小threadPoolSize (默认为开启四个线程NetworkDispatcher) ====> 定义了在线程池中执行网络请求,线程池中可以开启四个子线程
mDispatchers = new NetworkDispatcher[threadPoolSize]; //threadPoolSize = 4;
响应的分发器 delivery ===> 定义了将网络请求的结果处理分发到主线程去处理
delivery =new ExecutorDelivery(new Handler(Looper.getMainLooper())); { handle.post() };
2 请求队列的启动 ( mCacheQueue缓存的请求队列,mNetworkQueue网络请求队列)
由代码 queue.start(); 开始: // 这里面总共开启了五个子线程 , 一个为获取缓存数据线程 , 四个为获取网络数据线程
a. 创建缓存的分发器CacheDispather并且启动它
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
这里开启了一个缓存线程, 调用了其中的run()方法, 方法中 :
(1) . mCache.initialize(); // 磁盘缓存的初始化 读出所有缓存的文件的头(包括key判断是否有缓存数据,ttl记录缓存过期时间等等),
并没有读出数据(当网络请求有缓存,再去读取数据),将CacheHeader存入头的集合
private final Map<String, CacheHeader> mEntries = new LinkedHashMap<String, CacheHeader>(16, .75f, true);
(2) . while (true) { ... } // 这里是一个死循环 , 判断缓存队列 mCacheQueue 中有没有请求, 有的话将其取出, 查询一下有没有缓存
Cache.Entry entry = mCache.get(request.getCacheKey());
如果entry不为null,有缓存,并且缓存没有过期,就解析缓存网络请求的响应
Response<?> response = request.parseNetworkResponse( new NetworkResponse(entry.data, entry.responseHeaders));
并将解析后的结果分发到主线程中 :
mDelivery.postResponse(request, response);
如果缓存没有或者缓存已经过期 , 就将请求分发给网络请求队列
mNetworkQueue.put(request);
b. 网络请求线程池的网络线程的创建,创建4个NetworkDispather
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,mCache, mDelivery);
networkDispatcher.start(); // 并且启动这四个线程
在其中的run()方法中:
while (true) { ... } // 这里也是一个死循环 , 判断网络请求队列 mNetworkQueue 中有没有请求, 有的话则执行网络请求
NetworkResponse networkResponse = mNetwork.performRequest(request);
Response<?> response = request.parseNetworkResponse(networkResponse);
同时将网络请求的结果做缓存处理 :
mCache.put(request.getCacheKey(), response.cacheEntry);
最后同样将解析后的结果分发到主线程中
mDelivery.postResponse(request, response);
(注 : 方法parseNetworkResponse() 为一个抽象方法 , 解析数据逻辑需要自己重写去实现 )