技术控

    今日:47| 主题:49507
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] Android Volley源码分析(一)

[复制链接]
三千哽咽 发表于 2016-10-15 15:43:25
490 16

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
volley 是一个非常流行的 Android 开源框架,自己平时也经常使用它,但自己对于它的内部的实现过程并没有进行太多的深究。所以为了以后能更通透的使用它,了解它的实现是一个非常重要的过程。自己有了一点研究,做个笔记同时与大家一起分享。期间自己也画了一张图,希望能更好的帮助我们理解其中的步骤与原理。如下:
   
Android Volley源码分析(一)-1 (Android,public,图片,流行,网络)
    开始看可能会一脸懵逼,我们先结合源码一步一步来,现在让我们一起进入 Volley 的源码世界,来解读大神们的编程思想。
    newRequestQueue

   如果使用过 Volley 的都知道,不管是进行网络请求还是什么别的图片加载,首先都要创建好一个 RequestQueue
  [code]public static final RequestQueue volleyQueue = Volley.newRequestQueue(App.mContext);
[/code]   而 RequestQueue 的创建自然离不开 Volley 中的静态方法 newRequestQueue ,从上面的图片也能知道首先进入点是 newRequestQueue ,好了现在我们来看下该方法中到底做了什么:
  [code]public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
// Prior to Gingerbread, HttpUrlConnection was unreliable.
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
Network network = new BasicNetwork(stack);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
[/code]   从上面的源码中我们能发现有一个 stack ,它代表着网络请求通信 HttpClient 与 HttpUrlConnection ,但我们一般都是默认设置为 null 。因为它会默认帮我们进判断选择更适合的。当手机版本大于等于 9 时会使用 HurlStack 它里面使用 HttpUrlConnection 进行实现通信的,而小于 9 时则创建 HttpClientStack ,它里面自然是 HttpClient 的实现。通过 BasicNetwork 构造成 Network 来进行传递使用;在这之后构建了 RequestQueue ,其中帮我们构造了一个缓存 new DiskBasedCache(cacheDir) ,默认为 5MB ,缓存目录为 volley 。所以我们能在 data/data/应用包名/volley 下找到缓存。最后调用其 start 方法,并返回 RequestQueue 。这就是我们前面第一段代码的内部实现。下面我们进入 RequestQueue 中的 start 方法看下它到底做了什么呢?
  RequestQueue

   在 RequestQueue 中通过 this 调用自身来默认帮我们调用了下面的构造函数
  [code]public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
[/code]   cache 后续在 NetworkDispatcher 中会帮我们进行 response.cacheEntry 的缓存, netWork 是前面的根据版本所封装的通信, threadPoolSize 线程池大小默认为 4 , delivery ,是 ExecutorDelivery 作用在主线程,在最后对请求响应的分发。
  start

  [code]public void start() {
stop(); // Make sure any currently running dispatchers are stopped.
// Create the cache dispatcher and start it.
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// Create network dispatchers (and corresponding threads) up to the pool size.
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers = networkDispatcher;
networkDispatcher.start();
}
}
[/code]   在 start 方法中我们发现它实现了两个 dispatch ,一个是 CacheDispatcher 缓存派遣,另一个是 networkDispatcher 进行网络派遣,其实他们都是 Thread ,所以都调用了他们的 start 方法。其中 networkDispatcher 默认构建了 4 个,相当于包含 4 个线程的线程池。现在我们先不去看他们内部的 run 方法到底实现了什么,我们还是接着看 RequestQueue 中我们频繁使用的 add 方法。
  add

  [code]public Request add(Request request) {
// Tag the request as belonging to this queue and add it to the set of current requests.
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
// Process requests in the order they are added.
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// If the request is uncacheable, skip the cache queue and go straight to the network.
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
// Insert request into stage if there's already a request with the same cache key in flight.
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
if (mWaitingRequests.containsKey(cacheKey)) {
// There is already a request in flight. Queue up.
Queue> stagedRequests = mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new LinkedList>();
}
stagedRequests.add(request);
mWaitingRequests.put(cacheKey, stagedRequests);
if (VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
}
} else {
// Insert 'null' queue for this cacheKey, indicating there is now a request in
// flight.
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}
[/code]   我们结合代码与前面的图,首先会为当前的 request 设置 RequestQueue ,并且根据情况同步是否是当前正在进行的请求,加入到 mCurrentRequests 中,看 11 行代码,之后对当前 request 进行判断是否需要缓存(默认实现是 true ,如果不需要可调用 request.setShouldCache() 进行设置),如果不需要则直接加入到前面的 mNetworkQueue 中,它会在 CacheDispatcher 与 NetworkDispatcher 中做相应的处理,然后返回 request 。如果需要缓存,看16行代码,则对 mWaitingRequests 中是否包含 cacheKey 进行相应的处理。其中 cacheKey 为请求的 url 。最后再加入到缓存队列 mCacheQueue 中。
  finish

   细心的人会发现当对应 cacheKey 的 value 不为空时,创建了 LinkedList 即 Queue ,只是将 request 加入到了 Queue 中,只是更新了 mWaitingRequests 中相应的 value 但并没有加入到 mCacheQueue 中。其实不然,因为后续会调用 finish 方法,我们来看下源码:
  [code] void finish(Request request) {
// Remove from the set of requests currently being processed.
synchronized (mCurrentRequests) {
mCurrentRequests.remove(request);
}
synchronized (mFinishedListeners) {
for (RequestFinishedListener listener : mFinishedListeners) {
listener.onRequestFinished(request);
}
}
if (request.shouldCache()) {
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
Queue> waitingRequests = mWaitingRequests.remove(cacheKey);
if (waitingRequests != null) {
if (VolleyLog.DEBUG) {
VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",
waitingRequests.size(), cacheKey);
}
// Process all queued up requests. They won't be considered as in flight, but
// that's not a problem as the cache has been primed by 'request'.
mCacheQueue.addAll(waitingRequests);
}
}
}
}
[/code]   看 14 、 22 行代码,正如上面我所说,会将 Queue 中的 request 全部加入到 mCacheQueue 中。
   好了 RequestQueue 的主要源码差不多就这些,下面我们进入 CacheDispatcher 的源码分析,看它究竟如何工作的呢?
  CacheDispatcher

   前面提到了它与 NetworkDispatcher 本质都是 Thread ,那么我们自然是看 run 方法
  run

  [code]@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

// Make a blocking call to initialize the cache.
mCache.initialize();

while (true) {
try {
// Get a request from the cache triage queue, blocking until
// at least one is available.
final Request request = mCacheQueue.take();
request.addMarker("cache-queue-take");

// If the request has been canceled, don't bother dispatching it.
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}

// Attempt to retrieve this item from cache.
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
mNetworkQueue.put(request);
continue;
}

// If it is completely expired, just send it to the network.
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}

// We have a cache hit; parse its data for delivery back to the request.
request.addMarker("cache-hit");
Response response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");

if (!entry.refreshNeeded()) {
// Completely unexpired cache hit. Just deliver the response.
mDelivery.postResponse(request, response);
} else {
// Soft-expired cache hit. We can deliver the cached response,
// but we need to also send the request to the network for
// refreshing.
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);

// Mark the response as intermediate.
response.intermediate = true;

// Post the intermediate response back to the user and have
// the delivery then forward the request along to the network.
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
// Not much we can do about this.
}
}
});
}

} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
}
}
[/code]   看起来很多,我们结合图来挑主要的看。首先创建了一个无限循环一直在监视着 request 的变化。从缓存队列 mCacheQueue 中获取 request ,如果该请求是 cancle 了,调用 request.finish() 清除相应数据并进行下一个请求的操作,否则从前面提到的 mCache 中获取 Cache.Entry 。如果不存在或者已经过期,将请求加入到网络队列中 mNetWorkQueue ,进行后续的网络请求。如果存在( 41 行代码)则进行 request.parseNetworkResponse() 解析出 response ,不同的 request 对应不同的解析方法。例如 StringRequest 与 JsonObjectRequest 有各自的解析实现。再看 45 行,发现不管 entry 是否需要更新的,都会进一步对 response 进行 mDelivery.postResponse(request, response) 递送,不同的是需要更新的话重新设置 request 的 entry 与加入到 mNetworkQueue 中,也就相当与重新进行网络请求一遍。那么再回到递送的阶段,前面已经提到在创建 RequestQueue 是实现了 ExecutorDelivery , mDelivery.postResponse 就是其中的方法。我们来看一下
  ExecutorDelivery

   在这里创建了一个 Executor ,对后面进行递送,作用在主线程
  [code]public ExecutorDelivery(final Handler handler) {
// Make an Executor that just wraps the handler.
mResponsePoster = new Executor() {
@Override
public void execute(Runnable command) {
handler.post(command);
}
};
}
[/code]  postResponse

  [code]@Override
public void postResponse(Request request, Response response) {
postResponse(request, response, null);
}

@Override
public void postResponse(Request request, Response response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
[/code]   这个方法就简单了就是调用 execute 进行执行,在进入 ResponseDeliveryRunnable 的 run 看它如何执行
  ResponseDeliveryRunnable

  [code]public void run() {
// If this request has canceled, finish it and don't deliver.
if (mRequest.isCanceled()) {
mRequest.finish("canceled-at-delivery");
return;
}

// Deliver a normal response or error, depending.
if (mResponse.isSuccess()) {
mRequest.deliverResponse(mResponse.result);
} else {
mRequest.deliverError(mResponse.error);
}

// If this is an intermediate response, add a marker, otherwise we're done
// and the request can be finished.
if (mResponse.intermediate) {
mRequest.addMarker("intermediate-response");
} else {
mRequest.finish("done");
}

// If we have been provided a post-delivery runnable, run it.
if (mRunnable != null) {
mRunnable.run();
}
}
[/code]   主要是第 9 行代码,对于不同的响应做不同的递送, deliverResponse 与 deliverError 内部分别调用的就是我们非常熟悉的 Listener 中的 onResponse 与 onErrorResponse 方法,进而返回到我们对网络请求结果的处理函数。
  这就是整个的缓存派遣,简而言之,存在请求响应的缓存数据就不进行网络请求,直接调用缓存中的数据进行分发递送。反之执行网络请求。
   下面我来看下 NetworkDispatcher 是如何处理的
  NetworkDispatcher

  run

  [code]public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
// Prior to Gingerbread, HttpUrlConnection was unreliable.
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
Network network = new BasicNetwork(stack);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
0[/code]   在这里也创建了一个无限循环的 while ,同样也是先获取 request ,不过是从 mQueue 中,即前面多次出现的 mNetWorkQueue ,通过看代码发现一些实现跟 CacheDispatcher 中的类似。也正如图片中所展示的一样,( 23 行)如何请求取消了,直接 finish ;否则进行网络请求,调用( 31 行) mNetwork.performRequest(request) ,这里的 mNetWork 即为前面 RequestQueue 中对不同版本进行选择的 stack 的封装,分别调用 HurlStack 与 HttpClientStack 各自的 performRequest 方法,该方法中构造请求头与参数分别使用 HttpClient 或者 HttpUrlConnection 进行网络请求。我们再来看 42 行,是不是很熟悉,与 CacheDispatcher 中的一样进行 response 进行解析,然后如果需要缓存就加入到缓存中,最后( 54 行)再调用 mDelivery.postResponse(request, response) 进行递送。至于后面的剩余的步骤与 CacheDispatcher 中的一模一样,这里就不多累赘了。
   好了, Volley 的源码解析先就到这里了,我们再回过去看那张图是不是感觉很清晰了呢?
  总结

   我们来对使用 Volley 网络请求做个总结
  
       
  • 通过 newRequestQueue 初始化与构造 RequestQueue   
  • 调用 RequestQueue 中的 add 方法添加 request 到请求队列中   
  • 缓存派遣,先进行 CacheDispatcher ,判断缓存中是否存在,有则解析 response ,再直接 postResponse 递送,否则进行后续的网络请求   
  • 网络派遣, NetworkDispatcher 中进行相应的 request 请求,解析 response 如设置了缓存就将结果保存到 cache 中,再进行最后的 postResponse 递送。  
   转载请指明出处idisfkj博客: https://idisfkj.github.io
友荐云推荐




上一篇:Node.js 模块系统
下一篇:项目总结:游船管理系统
酷辣虫提示酷辣虫禁止发表任何与中华人民共和国法律有抵触的内容!所有内容由用户发布,并不代表酷辣虫的观点,酷辣虫无法对用户发布内容真实性提供任何的保证,请自行验证并承担风险与后果。如您有版权、违规等问题,请通过"联系我们"或"违规举报"告知我们处理。

会飞的鱼 发表于 2016-10-15 17:51:50
开往春天的坦克!
回复 支持 反对

使用道具 举报

袖浅笼温柔 发表于 2016-10-16 14:03:29
三千哽咽总是默默地一个人奋斗,无人理会,而我来此祝你一臂之力。
回复 支持 反对

使用道具 举报

得意的笑 发表于 2016-10-20 05:00:44
什么啊,语文是苍老师教的吗?
回复 支持 反对

使用道具 举报

我没来 发表于 2016-10-24 14:10:57
楼下各位,今年发大财!
回复 支持 反对

使用道具 举报

梦里花落知多少 发表于 2016-10-30 10:45:15
我也顶起出售广告位
回复 支持 反对

使用道具 举报

陈果 发表于 2016-10-30 15:04:57
你会变成大海,然后被鲨鱼吃掉!
回复 支持 反对

使用道具 举报

操蛋 发表于 2016-11-3 00:50:37
当你的眼泪忍不住要流出来的时候,睁大眼睛,千万别眨眼,你会看到世界由清晰到模糊的全过程
回复 支持 反对

使用道具 举报

jesusNaky 发表于 2016-11-10 03:42:16
怀揣两块,胸怀500万!  
回复 支持 反对

使用道具 举报

Lee-CL 发表于 2016-11-10 16:19:19
宇宙第一贴诞生了!
回复 支持 反对

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读

扫码访问 @iTTTTT瑞翔 的微博
回页顶回复上一篇下一篇回列表手机版
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )|网站地图 酷辣虫

© 2001-2016 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表