说起网络连接,我想很多人都会对下面这3种非常熟悉:HttpClient、HttpURLConnection、OKHttp
1)HttpClient这个就不说了,在Android6.0之后直接被谷歌废弃删除了
2)HttpURLConnection这个相信很多人刚开学Android的时候都用过它,我还记得那会还不会用什么第三方框架时,就自己用了HttpURLConnection进行网络请求什么的,自己封装了方法,后来用了第三方框架后,反倒对它有些陌生了
3)OKHttp目前得到了官方的认可,成为谷歌推荐的android网络请求框架,我们可以在AndroidStudio中通过如下图的方式直接看到OKHttp的身影:
而Volley又是什么呢?Volley于2013年Google I/O大会上被推荐,它提供了HttpClient 和 HttpUrlConnection两种连接方式,是个混合体来着,下面我们会通过源码进行分析,目前该框架已停止更新。
其实既然OKHttp已获得官方的认可和推荐,那我们又为什么要再来学习Volley这个已经停止更新的框架呢?那是因为笔者公司项目用的就是Volley,而正好笔者最近又在学习部分框架的源码,正好拿它来学习学习,特此记录一下罢了,大神们可以绕路了O(∩_∩)O~
首先我们不要被“源码”这两个字吓住了,而且还是网络请求框架的源码,感觉很高深的样子,其实不然,我们可以先开口Volley包源码截图如下:
Volley源码看到没?也就差不多这样子,没你想的那么复杂的,不怕,盘它!
下面我们一步步对源码进行分析学习:
1、创建RequestQueue
我们使用Volley的第一步就是创建RequestQueue,一般我们都是直接这样调用的:
RequestQueue mRequestQueue = Volley.newRequestQueue(context);
那么,我们来看看源码
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, null);
}
//主要是下面这个方法
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;
}
注意到第二个方法中的第二个参数:HttpStack,我们看看源码:
它是一个接口,在Volley包中有HttpClientStack和HurlStack两个实现类,这两个类就分别对应了我们上面说的HttpClient和HttpURLConnection两种网络连接方式。
当然,聪明的你在这里必须有一个很nice的想法:如果网络层想使用谷歌目前推荐的OKHttp的话,那么我们是不是可以模仿HttpClientStack和HurlStack,写一个类来实现HttpStack接口即可呢?又或者直接继承于HurlStack?
答应是肯定的,必须可以啊,这也是框架设计巧妙之处,利用接口来使得框架更具有扩展性,在这里就不相应展开了,我们今天是来学习Volley源码的。
我们看到上面的源码:
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));
}
}
当我们不指定HttpStack时,系统自动帮我们进行了区分,也就是说,如果API大于等于9的话,网络连接用的是HttpURLConnection,如果API小于9的话,网络连接用的就是HttpClient(不过目前估计没有API小于9的手机吧,老古董了)
我们目的在于创建RequestQueue,那我们就来看看RequestQueue的构造方法:
/** Number of network request dispatcher threads to start. */
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
public RequestQueue(Cache cache, Network network) {
this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
public RequestQueue(Cache cache, Network network, int threadPoolSize,ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
多个构造函数,一层调过一层,我直接看最后一个构造函数,简单说明一下:
1)Cache这个作为缓存的,Volley默认使用的是DiskBasedCache,里面使用了LinkedHashMap,那么说明遵照的是Lru算法,默认缓存大小为5M,当然,也提供了相应的构造函数可进行设置
2)Network这个是用来发送网络请求的,Volley默认使用的BasicNetwork,在这里进行网络请求的发送,统一返回了NetworkResponse对象,下面我们简单看看返回的这个对象是什么样子的,相关的说明已在注释中:
public NetworkResponse(int statusCode, byte[] data, Map<String, String> headers,
boolean notModified, long networkTimeMs) {
this.statusCode = statusCode;//返回Http状态码
this.data = data;//返回的请求体,一般我们从这里获取所需的数据
this.headers = headers;//返回的请求头,可为null
this.notModified = notModified;//如果是true的话,说明状态码为304,也就是数据已经存在于缓存中
this.networkTimeMs = networkTimeMs;//整个网络请求所消耗的时间
}
另外,在BasicNetwork中使用了ByteArrayPool来缓存网络请求获得的数据,有利于减少内存区域堆内存的波动和减少GC的回收,提高了性能,这是谷歌做的一个优化。
相关文章可参考:
https://blog.csdn.net/tiankongcheng6/article/details/57085806
https://blog.csdn.net/hfy8971613/article/details/81952375
3)new NetworkDispatcher[threadPoolSize],首先NetworkDispatcher继承于Thread,而这里很明显是创建一个线程数组,数组的大小默认为4,也就是说默认有4个线程用于网络请求,当然也可进行设置,NetworkDispatcher这个类是重要的类,下面我们会进一步进行分析。
4)ResponseDelivery这个主要用来进行线程的切换,将结果在主线程中返回,这就是为什么我们使用Volley框架后不用自己切回主线程更新UI的原因了,我们来看看它是如何做到的?注意上面的创建方法:
//创建
new ExecutorDelivery(new Handler(Looper.getMainLooper()))
//ExecutorDelivery的构造函数
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);
}
};
}
很明显,使用的是Handler机制,传了Main线程的Looper对象,这样一来, handler.post(command)就切回到主线程中去了。
好啦,这样我们完成了RequestQueue的创建,注意到在返回RequestQueue对象前,我们调用了start方法,我们继续看看源码:
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[i] = networkDispatcher;
networkDispatcher.start();
}
}
public void stop() {
if (mCacheDispatcher != null) {
mCacheDispatcher.quit();
}
for (int i = 0; i < mDispatchers.length; i++) {
if (mDispatchers[i] != null) {
mDispatchers[i].quit();
}
}
}
start方法其实也没什么可说的,CacheDispatcher和NetworkDispatcher是两个很重要的类,它们都继承于Thread,下面我们会对这两个类进行说明的,这里我们只需知道在RequestQueue类中的start方法就是将一个CacheDispatcher线程和一个NetworkDispatcher数组中的所有线程全部启动起来。
当然,如上注释所说的,在启动之前,先停止到所有线程。
2、CacheDispatcher和NetworkDispatcher分析
上面我们说了,这两个类是很重要的类,我们就对它们进行一下分析:
首先,它们都是在上面RequestQueue中的start方法创建的,我们看一下它们的创建及构造函数:
//CacheDispatcher的创建
CacheDispatcher mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
//NetworkDispatcher的创建
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,mCache, mDelivery);
//CacheDispatcher的构造函数
public CacheDispatcher(
BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,
Cache cache, ResponseDelivery delivery) {
mCacheQueue = cacheQueue;
mNetworkQueue = networkQueue;
mCache = cache;
mDelivery = delivery;
}
//NetworkDispatcher的构造函数
public NetworkDispatcher(BlockingQueue<Request<?>> queue,
Network network, Cache cache,
ResponseDelivery delivery) {
mQueue = queue;
mNetwork = network;
mCache = cache;
mDelivery = delivery;
}
其中,mCache、mNetwork和mDelivery我们在上面讲RequestQueue的构造函数时已经讲了,我们这里来看看mCacheQueue和mNetworkQueue,很明显,从名字看它们是一个队列,我们在RequestQueue的源码中可以找到这两个队列:
/** The cache triage queue. */
private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<?>>();
/** The queue of requests that are actually going out to the network. */
private final PriorityBlockingQueue<Request<?>> mNetworkQueue =new PriorityBlockingQueue<Request<?>>();
mCacheQueue和mNetworkQueue都是PriorityBlockingQueue,一种带优先级别的队列,我们直接看一下Request类中关于优先级的相关代码:
public enum Priority {
LOW,
NORMAL,
HIGH,
IMMEDIATE
}
public Priority getPriority() {
return Priority.NORMAL;
}
@Override
public int compareTo(Request<T> other) {
Priority left = this.getPriority();
Priority right = other.getPriority();
// High-priority requests are "lesser" so they are sorted to the front.
// Equal priorities are sorted by sequence number to provide FIFO ordering.
return left == right ?
this.mSequence - other.mSequence :
right.ordinal() - left.ordinal();
}
从上面代码我们知道:
1)Request有4种优先级,从低到高依次为:LOW、NORMAL、HIGH、IMMEDIATE
2)默认的优先级是NORMAL,当然,Request是一个抽象类,继承于Request的话可覆盖getPriority()方法,返回相应的优先级,比如ImageRequest的优先级就是LOW,比如ClearCacheRequest的优先级就是IMMEDIATE
3)我们都知道PriorityBlockingQueue是根据对象的compareTo返回值来判断优先级的,上面源码显示,如果优先级相等的话,那么按当前队列的排序依照FIFO先进先出原则进行;如果优先级不等,那么优先级高的排在前面。
既然CacheDispatcher和NetworkDispatcher都是线程,那么我们当然会更加关心其run方法了。
我们先来看看CacheDispatcher中的run方法:
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//设置线程优先级为后台,这样当多个线程并发后,很多无关紧要的线程分配的CPU时间将会减少,有利于主线程的处理
// Make a blocking call to initialize the cache.
mCache.initialize();//对DiskBasedCache进行对初始化
while (true) {//死循环
try {
// Get a request from the cache triage queue, blocking until
// at least one is available.
final Request<?> request = mCacheQueue.take();//PriorityBlockingQueue如果为空的话,会阻塞当前线程
request.addMarker("cache-queue-take");
// If the request has been canceled, don't bother dispatching it.
if (request.isCanceled()) {//如果请求已经取消,则继续下一次循环
request.finish("cache-discard-canceled");//完成请求,会调用相应RequestQueue中的finish方法
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);//如果缓存不存在,则将请求加到mNetworkQueue中去
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);//如果缓存存在,但过期,仍然将请求加到mNetworkQueue中去
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() {
//缓存需要刷新,我们将结果返回的同时,将请求加到mNetworkQueue中去刷新缓存
@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;
}
}
}
好啦,需要说明的地方已经用中文在代码中进行了相应的注释。
下面我们再来看看NetworkDispatcher中的run方法:
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//设置线程优先级为后台,这样当多个线程并发后,很多无关紧要的线程分配的CPU时间将会减少,有利于主线程的处理
while (true) {//死循环
long startTimeMs = SystemClock.elapsedRealtime();//获取网路请求开始的时间
Request<?> request;
try {
// Take a request from the queue.
request = mQueue.take();//PriorityBlockingQueue如果为空的话,会阻塞当前线程
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {//
return;
}
continue;
}
try {
request.addMarker("network-queue-take");
// If the request was cancelled already, do not perform the
// network request.
if (request.isCanceled()) {//如果请求已经取消,则继续下一次循环
request.finish("network-discard-cancelled");//完成请求,会调用相应RequestQueue中的finish方法
continue;
}
addTrafficStatsTag(request);
// Perform the network request.
NetworkResponse networkResponse = mNetwork.performRequest(request);//正式发送请求,返回结果
request.addMarker("network-http-complete");
// If the server returned 304 AND we delivered a response already,
// we're done -- don't deliver a second identical response.
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
//如果返回状态为304而且我们已经返回了结果,那么我们将不再进行第二次返回,这是一种优化
request.finish("not-modified");//完成请求,会调用相应RequestQueue中的finish方法
continue;
}
// Parse the response here on the worker thread.
Response<?> response = request.parseNetworkResponse(networkResponse);//对返回结果进行转换
request.addMarker("network-parse-complete");
// Write to cache if applicable.
// TODO: Only update cache metadata instead of entire record for 304s.
if (request.shouldCache() && response.cacheEntry != null) {//如果需要进行缓存且结果不为空的话,将数据进行本地缓存
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
// Post the response back.
request.markDelivered();
mDelivery.postResponse(request, response);//投递返回结果
} catch (VolleyError volleyError) {
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
VolleyError volleyError = new VolleyError(e);
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
mDelivery.postError(request, volleyError);
}
}
}
好啦,也详见上述的中文注释,这里也不多说了。
3、发送请求
上面我们已经将相关重要的类都说明清楚了,那么,接下来就来看看发送请求了,我们都知道我们用Volley发送一个请求很简单: mRequestQueue.add(request),我们接下来就来看看其源码:
public <T> Request<T> add(Request<T> request) {
// Tag the request as belonging to this queue and add it to the set of current requests.
request.setRequestQueue(this);//将request与当前的RequestQueue进行绑定
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
// Process requests in the order they are added.
request.setSequence(getSequenceNumber());//设置请求队列序号,使用了AtomicInteger的incrementAndGet
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();//默认URL作为CacheKey
if (mWaitingRequests.containsKey(cacheKey)) {//如果当前请求已经在请求的队列中
// There is already a request in flight. Queue up.
Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new LinkedList<Request<?>>();
}
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);//如果当前请求不在请求队列中,将请求加到mCacheQueue中去
}
return request;
}
}
相应注释代码也已经说明了,这里需要说明一点的就是,如果请求未在请求队列中,我们是直接将请求加到mCacheQueue中去,这里是mCacheQueue而不是mNetworkQueue,这样才合理嘛!
好啦,Volley的主要源码学习也就到此结束了,我看什么时候有空再根据源码补个流程图吧!
网友评论