Volley源码阅读——(一)为何适用于多而小

Volley算是比较简单的http库,所以决定从Volley入手去开始读源码之路。
打算写一系列Volley源码阅读的文章,顺序按照我的源码阅读顺序。这是第一章,为何适用于多而小。
本章主要介绍我阅读源码的起始过程,理解Volley的初始化工作和Volley中请求线程的工作原理。

1. 初步浏览 - 接口

接口 实现类 主要方法/作用
Cache NoCache
NetWork BasikNetWork performRequest()
MockNetWork
ResponseDelivery ExecutorDelivery postResponse()/postError()
RetryPolicy DefaultRetryPolicy
toolbox.HttpStack HttpClientStack 使用HttpClient来performRequest
HurlStack 使用HttpURLConnection来performRequest
MockHttpStack
toolbox.Authenticator DefaultAuthenticator

Volley源码算是比较少的,我也没有看源码的经验。于是先将接口列出,试图知道设计人员一开始都想做些啥,这个方法卓有成效。

接口列出如上,如何快速找到接口呢?我用的Idea看的源码,里面非常清晰,如下图:



图中1处绿色写着“I”就是Interface接口,甚至还有2处圆圈两边灰色的,打开Request类会发现这是个抽象类。
那下面把抽象类也列一下:

抽象类 实现类 主要方法/作用
Request ClearCacheRequest
ImageRequest
StringRequest
JsonRequest JsonArrayRequest
JsonObjectRequest

比较特殊的是JsonRequest也是Request的子类,同时它也是一个抽象类,再分别由JsonArrayRequest和JsonObjectRequest实现。

这些东西列出来,可能就对框架已经有了初步的了解,比如:

  • 这个框架可能做了缓存 —— 基于Cache接口
  • 用NetWork还是HttpStack来实现请求? —— 基于二者都有的performRequest()方法
  • 应答有一个分发机制 —— 基于ResponseDelivery
  • 有重发机制 —— 基于RetryPolicy
  • 可能还有认证机制 —— 基于Authenticator

这些东西先有个印象,有个印象能加快后面的进度。

2. 简单使用

网上一搜一大把,这里建议大家看博客的时候,找一些排版比较好的。那些连代码格式都没有的就不要去看了。

参考Android Volley完全解析(一),初识Volley的基本用法,这个是我搜到的百度的第一个。

第三条 StringRequest介绍到:

(1). 初始化,创建RequestQueue对象

RequestQueue mQueue = Volley.newRequestQueue(context);  

(2). 创建StringRequest请求,并add进RequestQueue

StringRequest stringRequest = new StringRequest("http://www.baidu.com",  
                        new Response.Listener<String>() {  
                            @Override  
                            public void onResponse(String response) {  
                                Log.d("TAG", response);  
                            }  
                        }, new Response.ErrorListener() {  
                            @Override  
                            public void onErrorResponse(VolleyError error) {  
                                Log.e("TAG", error.getMessage(), error);  
                            }  
                        });  
mQueue.add(stringRequest);  

3. 参考简单使用理解初始化工作

看2中代码,初始化仅仅创建了一个RequestQueue,这里面完成了什么工作呢?

43行:初始化了默认的缓存目录
53-59行:根据不同的sdk版本创建不同的HttpStack(主要的实现网络请求的对象)
63行:以HttpStack对象作为参数新建NetWork对象

猜想:这应该是NetWork对象调用HttpStack对象实现网络请求了

65行:以DiskBasedCache对象和netWork对象作为参数创建了RequestQueue对象
66行:RequestQueue执行start方法

理一理思绪:

  • 简单说就是创建了缓存对象DiskBasedCache和网络请求对象BasicNetwork,然后传给RequestQueue的构造器创建了RequestQueue对象。
  • BasicNetWork对象好像代理了HttpStack对象来执行http请求的具体实现
  • 非常贴心地可以传入HttpStack对象,表示开发人员可以自己选择http请求的具体实现
  • RequestQueue对象执行了start方法,不知道做了什么操作

现在来找一些比较关心的事情。

Q: 网络请求到底怎么执行的?NetWork和HttpStack到底是如何工作的?

Volley只是一个封装库,底层的http请求还是调用JDK接口。基于这样的背景,去看一下NetWork和HttpStack的performRequest()方法,这个方法一看起来就像是执行http请求的。

BasicNetWork的performRequest()


发现关键的httpResponse还是由HttpStack实现的,所以只要看HttpStack。

HurlStack的performRequest()
ps. 由于14号字体无法截全方法,不得不缩小字体

可以不去管其中的具体实现,找到了其中比较重点的部分:responseStatus和response是依靠HttpURLConnection获得的。我一开始学习Android,就是手写HttpURLConnection来实现get和post方法的。
反正response是HttpURLConnection实现的,暂时先不求甚解,假装已经拿到了response。

RequestQueue 构造器做了什么工作?

StringRequest例子里面是两个参数的RequestQueue构造器

两个参数的构造器调用了3个参数的构造器方法,并将第三个参数设置为默认4。看注释我们就知道,这个4是4个用来执行请求的线程,大胆猜想,RequestQueue中有一个线程池,默认线程数量是4。
看3个参数的构造器


第三个参数确实命名为threadPoolSize。同时加上了第4个参数默认值,一个ResponseDelivery的唯一实现ExecutorDelivery的对象。这个类我们在一开始整理接口的时候就见过,暂时只需要知道用来分发response。

再看最终的4个参数的构造器


原来只是赋值给成员变量而已。而且心心念念的线程池也仅仅是一个线程对象数组。
很好,其实可以发现,一开始整理出来的接口,有3个已经被RequestQueue成员变量实现了:Cache、NetWork、和ResponseDelivery

queue.start();完成了什么工作?

start之前都是对象的创建,到这一步,终于要运行起来了吧!


142行:备注已经说明了,确保关闭dispatcher线程。具体逻辑肯定和start相反,那么就先不care吧
144-145行:这里居然偷偷创建了一个缓存分发器,进入实现会发现这继承了线程。
148-152行:这里估计就是关键了,for循环一个一个地创建NetworkDispatcher对象将线程池数组填满,然后再将线程运行起来。

看到这里想必和我一样不耐烦了,这个dispatcher是个什么东西,线程现在就运行起来,岂不是占资源,而且,怎么保证线程一直在等用户发请求?

所以还是先看一看,networkDispatcher.start();之后会有什么发生?
这既然是继承了线程,那直接找到run()方法,好在一开始就看到了关键:

  1. while(true)这是一个死循环
  2. 第二个红框里面应该就是从队列里面拿请求了。所以不断从队列里面拿请求,然后执行。

这个mQueue是什么对象?队列里面要是没有请求存在又怎么办?
再看start方法中NetworkDispatcher对象的构造器参数


这个mNetworkQueue是一个优先级阻塞队列,保存的是Request对象


NetworkDispatcher中的mQueue确实就是这个mNetworkQueue

那么不难猜想,mQueue(优先级阻塞队列PriorityBlockingQueue)在为空的时候take会阻塞线程,直到queue不为空。那么进入take()方法去验证一下这个猜想:
直接点击take方法会跳入BlockingQueue接口里面的方法,如下

点击左边那个向下箭头就能看到进入实现,我们选择PriorityBlockingQueue:


我们看到关键的一行,dequeue()方法肯定就是队列弹出一个对象(可以自己去看看具体实现),为空的话,就会await()。
其中notEmpty对象是一个条件对象,这设计了多线程的锁。


在PriorityBlockingQueue的构造器内初始化


这里面的原理需要再开一篇来细讲。这里我们只需要知道阻塞队列为空,当前线程就会被阻塞await()。如何唤醒线程,后文讲一个请求的发送流程继续讲。

但是默认有4个线程,4个线程都会被阻塞吗?

我们不妨进入await()方法里面去再深入看一看:
这里我们需要换一种寻找实现方法的方式,找到notEmpty的初始化



进入newCondition()方法


再找到sync对象的声明地方


!!!这是可重入锁!而且下面就是Sync抽象类的声明,找到其中newCondition()方法


发现返回的是一个ConditionObject对象,找到这个类的声明并找到其中的await()方法:


2031行:将当前线程加入条件等待队列
2034行:检查当前线程node是否还在等待条件,需要等就进入循环执行LovkSupport.Park()来阻塞线程。
看一看这部分源码,可以理解,线程来一个阻塞一个,就是一个阻塞队列。

初始化工作也是为了一般工作能够正常进行,这里也保留一个印象,便于后面具体工作展开的理解。

4. 参考简单使用理解请求如何发送

StringRequest stringRequest = new StringRequest("http://www.baidu.com",  
                        new Response.Listener<String>() {  
                            @Override  
                            public void onResponse(String response) {  
                                Log.d("TAG", response);  
                            }  
                        }, new Response.ErrorListener() {  
                            @Override  
                            public void onErrorResponse(VolleyError error) {  
                                Log.e("TAG", error.getMessage(), error);  
                            }  
                        });  
mQueue.add(stringRequest);  

再看一看基本使用的代码,我已经写累了,太长了。太长不想看,我也不想写了。看,里面requestQueue.add(request)这样就把请求发送出去了。前面我们知道线程被阻塞住了。那当有请求add进来的时候,是如何唤醒线程的呢?

找到RequestQueue类中的add方法:


简单分析一下:
229:让request对象持有当前RequestQueue对象的引用
236:addMarker,注意request执行到每一个步骤都会设置一个marker,用来标志请求进行的阶段
239:是否需要缓存的条件语句,当然先从不需要的看起
240:mNetworkQueue.add,这个就是关键。
那240这里add进去了,怎么唤醒线程去执行呢?看源码


看到最后一个signal方法,这就是和await相对应的唤醒方法。假如还是不太理解这个阻塞和唤醒到底怎么回事,建议和我一样,写一个小demo看看

打印出来的效果是


不会无限打印print,也不会打印print finally

结合初始化那部分,就完整地呈现了多线程如何去实时发送新增的请求的流程。

结论
这也解释了为何Volley有利于多而小的请求,毕竟有4个线程可以使用。反而大的请求其实并没有做什么优化,用Volley执行大请求并没有什么优势,反而可能因为太耗时而占用了线程,导致其他小的请求无法及时执行。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容