写点什么

Android Volley 源码解析(一),ffmpeg 音视频开发实战 2019 下载

发布于: 2021 年 11 月 08 日


Volley 是 Google 在 2013 年的 I/O 大会上推出的 「Android 异步网络请求框架和图片加载框架」,它的设计目标就是去进行 数据量不大,但 通信频繁 的网络操作,而对于大数据量的网络操作,比如下载文件等,Volley 的表现就会非常糟糕。

Volley 的使用方法

在进行源码分析之前,先让我们来看下平时是怎样使用 Volley 的


RequestQueue requestQueue = Volley.newRequestQueue(context);


StringRequest stringRequest = new StringRequest(url


, new Response.Listener<String>() {


@Override


public void onResponse(String s) {


// TODO:


}


}, new Response.ErrorListener() {


@Override


public void onErrorResponse(VolleyError error) {


// TODO:


}


});


requestQueue.add(stringRequest);


1、通过 Volley.newRequestQueue(Context) 获取一个 RequestQueue


2、传入 URL 构建 Request,并实现相应的回调


3、将 Request 加入到 RequestQueue 中

Volley 中比较重要的类

在这先把 Volley 中比较重要的类说一下,到时候看源码能更加明白:


| 类名 | 作用 |


| --- | --- |


| Volley | 对外暴露的 API,主要作用是构建 RequestQueue |


| Request | 所有网络请求的抽象类,StringRequest、JsonRequest、ImageRequest 都是它的子类 |


| RequestQueue | 存放请求的队列,里面包括 CacheDispatcher、NetworkDispatcher 和 ResponseDelivery |


| Response | 封装一个解析后的结果以便分发 |


| CacheDispatcher | 用于执行缓存队列请求的线程 |


| NetworkDispatcher | 用户执行网络队列请求的线程 |


| Cache | 缓存请求结果,Volley 默认使用的是基于 sdcard 的 DiskBaseCache |


| HttpStack | 处理 Http 请求,并返回请求结果 |


| Network | 调用 HttpStack 处理请求,并将结果转换成可被 ResponseDelivery 处理的 NetworkResponse |


| ResponseDelivery | 返回结果的分发接口 |

二、请求的执行流程



我们从 Volley 的使用方法入手,一步一步探究底层的源码实现,我们的入手点就是


Volley.newRequestQueue(context)


public static RequestQueue newRequestQueue(Context context) {


return newRequestQueue(context, (BaseHttpStack) null);


}


这个方法只有一行代码,只是调用了 newRequestQueue() 的方法重载,并给第二个参数传入 null,那我们看下带有两个参数的 newRequestQueue 方法中的代码


public static RequestQueue newRequestQueue(Context context, BaseHttpSta


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


ck stack) {


BasicNetwork network;


if (stack == null) {


if (Build.VERSION.SDK_INT >= 9) {


network = new BasicNetwork(new HurlStack());


} else {


String userAgent = "volley/0";


try {


String packageName = context.getPackageName();


PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);


userAgent = packageName + "/" + info.versionCode;


} catch (NameNotFoundException e) {


}


network = new BasicNetwork(


new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));


}


} else {


network = new BasicNetwork(stack);


}


return newRequestQueue(context, network);


}


private static RequestQueue newRequestQueue(Context context, Network network) {


File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);


RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);


queue.start();


return queue;


}


可以看到,这个方法中先判断 stack 是否为 null,如果是的话,这里会根据 Android 手机的系统版本号来进行相应的处理,当 SDK >= 9,则创建一个 HurlStack 实例,否则创建一个 HttpClientStack 实例,实际上 HurlStack 内部使用的是 HttpURLConnction 进行网络请求,而 HttpClientStack 则是使用 HttpClient 进行网络请求,这里之所以要这么处理,主要是因为在 Android 2.3(SDK = 9)之前,HttpURLConnection 存在一个很严重的问题,所以这时候用 HttpClient 来进行网络请求会比较合适。


不过由于现在的 Android 手机基本都是 4.0 以上的,而且 HttpClient 已经由于某些原因被弃用了,所以现在只要了解 HttpURLConnection 相关的知识就够了。思路拉回来,我们继续看代码,拿到 Stack 的实例之后将其构建成一个 Network 对象,它是用于根据传入的 Stack 对象来处理网络请求的,紧接着构建出一个 RequestQueue 对象,并调用 start() 方法。


我们接着看 start() 方法究竟做了什么:


public void start() {


stop();


mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);


mCacheDispatcher.start();


for (int i = 0; i < mDispatchers.length; i++) {


NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,


mCache, mDelivery);


mDispatchers[i] = networkDispatcher;


networkDispatcher.start();


}


}


public void stop() {


if (mCacheDispatcher != null) {


mCacheDispatcher.quit();


}


for (final NetworkDispatcher mDispatcher : mDispatchers) {


if (mDispatcher != null) {


mDispatcher.quit();


}


}


}


先调用 stop() 方法将当前正在进行 Dispatcher 都停掉,然后创建了一个 CacheDispatcher 实例,并调用了它的 start() 方法,接着在一个循环里去创建 NetworkDispatcher 的实例,分别调用它们的 start() 方法,这里的 CacheDispatcher 和 NetworkDispatcher 都是继承自 Thread 的,默认情况下 for 循环会执行四次,也就是说当调用了 Volley.newRequestQueue(context) 之后,就会有五个线程在后台运行,等待网络请求的到来,其中 CacheDispatcher 是缓存线程,NetworkDispatcher 是网络请求线程。


得到 RequestQueue 之后,构建相应的 Request,然后调用 add() 方法将其加入到请求队列中


public <T> Request<T> add(Request<T> request) {


// 将 Request 标记为属于此队列,并将其放入 mCurrentRequests 中


request.setRequestQueue(this);


synchronized (mCurrentRequests) {


mCurrentRequests.add(request);


}


// 让 Request 按照他们被添加的顺序执行


request.setSequence(getSequenceNumber());


request.addMarker("add-to-queue");


//如果请求不需要被缓存,就跳过缓存,直接进行网络请求


if (!request.shouldCache()) {


mNetworkQueue.add(request);


return request;


}


mCacheQueue.add(request);


return request;


}


可以看到,传入 Request 之后,会先判断该 Request 是否需要进行缓存,如果不需要就直接将其加入到网络请求队列,需要缓存则加入缓存队列。默认情况下,每条请求都是应该缓存的,当然我们也可以调用 Request 的 setShouldCache() 方法来进行设置。


Request 被添加到缓存队列中后,在后台等待的缓存线程就要开始运行起来了,我们看下 CacheDispatcher 的 run() 方法究竟是怎么实现的。


@Override


public void run() {


Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);


// 初始化 Cache


mCache.initialize();


while (true) {


try {


processRequest();


} catch (InterruptedException e) {


if (mQuit) {


return;


}


}


}


}


private void processRequest() throws InterruptedException {


final Request<?> request = mCacheQueue.take();


request.addMarker("cache-queue-take");


// 如果请求已经取消了,我们直接结束该请求


if (request.isCanceled()) {


request.finish("cache-discard-canceled");


return;


}


// 从 Cache 中取出包含请求缓存数据的 Entry


Cache.Entry entry = mCache.get(request.getCacheKey());


if (entry == null) {


request.addMarker("cache-miss");


if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {


mNetworkQueue.put(request);


}


return;


}


// 如果缓存的请求过期了,就将其添加到网络请求队列中


if (entry.isExpired()) {


request.addMarker("cache-hit-expired");


request.setCacheEntry(entry);


if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {


mNetworkQueue.put(request);


}


return;


}


// 缓存的数据封装成 NetworkResponse


Response<?> response = request.parseNetworkResponse(


new NetworkResponse(entry.data, entry.responseHeaders));


if (!entry.refreshNeeded()) {


// 如果缓存没有过期就直接进行分发


mDelivery.postResponse(request, response);


} else {


// 重置该请求的 Entry


request.setCacheEntry(entry);


response.intermediate = true;


if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {


mDelivery.postResponse(request, response, new Runnable() {


@Override


public void run() {


try {


mNetworkQueue.put(request);


} catch (InterruptedException e) {


Thread.currentThread().interrupt();


}


}


});


} else {


mDelivery.postResponse(request, response);


}


}


}


代码相对比较长,我在关键的地方已经打上注释了,在这里总结一下,可以看到在初始化了 Cache 之后,有一个 while(true) 循环,说明缓存线程是始终执行的,接着会在缓存中取出响应结果,如果为 null 的话,就将其加入到网络请求队列中,如果不为空的话,再判断该缓存是否已过期,已经过期则同样把这条请求加入到网络请求队列中,否则直接使用缓存中的数据。最后将数据进行解析,并进行分发。


看完 CacheDispathcer 的 run() 方法,我们接着看 NetworkDispatcher 的 run() 方法


@Override


public void run() {


Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);


while (true) {


try {


processRequest();


} catch (InterruptedException e) {


if (mQuit) {


return;


}


}


}


}


private void processRequest() throws InterruptedException {


Request<?> request = mQueue.take();


long startTimeMs = SystemClock.elapsedRealtime();


try {


request.addMarker("network-queue-take");


// 如果 Request 已经取消了,那就不执行网络请求


if (request.isCanceled()) {


request.finish("network-discard-cancelled");


request.notifyListenerResponseNotUsable();


return;


}


addTrafficStatsTag(request);


// 执行网络请求


NetworkResponse networkResponse = mNetwork.performRequest(request);


// 如果服务器返回 304,而且我们已经分发过该 Request 的结果,那就不用进行第二次分发了


//(这里补充一下,304 代表服务器上的结果跟上次访问的结果是一样的,也就是说数据没有变化)


if (networkResponse.notModified && request.hasHadResponseDelivered()) {


request.finish("not-modified");


request.notifyListenerResponseNotUsable();


return;


}


// 在子线程解析返回的结果


Response<?> response = request.parseNetworkResponse(networkResponse);


// 如果需要的话,就将返回结果写入缓存


if (request.shouldCache() && response.cacheEntry != null) {


mCache.put(request.getCacheKey(), response.cacheEntry); }


// 分发响应结果


request.markDelivered();


mDelivery.postResponse(request, response);


request.notifyListenerResponseReceived(response);


} catch (VolleyError volleyError) {


request.notifyListenerResponseNotUsable();


} catch (Exception e) {


request.notifyListenerResponseNotUsable();


}


}

评论

发布
暂无评论
Android Volley 源码解析(一),ffmpeg音视频开发实战2019下载