在Android开发中,网络请求除了自己封装HttpUtils工具类,就是使用开源的第三方网络请求框架了。出于学习的目的,阅读一下google亲儿子Volley框架的源码。选Volley阅读的主要原因还是Volley代码量小,阅读起来不会那么繁琐。
1.Volley的基本用法
在阅读一个框架之前,首先得了解该框架的基本用法,下面简单介绍Volley框架的使用:
// 1.首先实例化一个请求队列
RequestQueue mQueue = Volley.newRequestQueue(context);
// 2.实例化一个Request,此处以StringRequest为例
StringRequest stringRequest = new StringRequest("http://www.xxx.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);
}
});
// 3.将请求加入请求队列中
mQueue.add(stringRequest);
2.Volley源码分析
顺着Volley框架Api调用顺序开始分析源码。
Step 1:调用Volley.newRequestQueue(context)来实例化一个RequestQueue对象,代码如下:
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, (BaseHttpStack) null);
}
public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {
BasicNetwork network;
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
// Android 2.3及以上版本,HurlStack内部http请求实现使用的是HttpURLConnection
network = new BasicNetwork(new HurlStack());
} else {
// Android 2.3以下版本,HttpClientStack内部http请求实现使用的是HttpClient
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, /* flags= */ 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;
}
插个支线话题,因为以前大致瞄过Volley源码,原先实例化RequestQueue的方法应该是newRequestQueue(Context context, HttpStack stack),但是HttpStack已经被废弃,原因是HttpStack依赖了已经被Apache弃用的Apache HTTP库。
在newRequestQueue()方法执行完后,会默认帮你调用RequestQueue.start()方法,让我们看看start()的内部实现:
/** Starts the dispatchers in this queue. */
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();
}
}
可以看到start()方法中创建了一个CacheDispatcher实例和若干个NetworkDispatcher实例(默认是4个),其实这些每个实例都相当于一个线程,其中CacheDispatcher是缓存线程,NetworkDispatcher是网路请求的线程。后台运行这5个线程,等待RequestQueue.add()方法被调用,添加请求到请求队列去给后台线程去执行。
到此,RequestQueue mQueue = Volley.newRequestQueue(context)的源码分析结束。
Step 2:接着看StringRequest的内部实现
emmm...其实StringRequest好像没啥东西可以说的,要看的话可以看看StringRequest的构造方法,不看也不影响主线流程,我们便直接看StringRequest的父类:
public abstract class Request<T> implements Comparable<Request<T>> {
....
// 表示请求优先级
public enum Priority {
LOW,
NORMAL,
HIGH,
IMMEDIATE
}
/**
* Our comparator sorts from high to low priority, and secondarily by sequence number to provide
* FIFO ordering.
*/
@Override
public int compareTo(Request<T> other) {
// 1.先获取请求优先级
Priority left = this.getPriority();
Priority right = other.getPriority();
// 2.两个Request优先级一样时,序号小的放前面; 两个Request优先级不一致时,优先级高的在前面
return left == right ? this.mSequence - other.mSequence : right.ordinal() - left.ordinal();
}
....
}
因为是个基类貌似代码有点多,Request实现了Comparable接口,为什么?
原因是RequestQueue存储请求队列的是:
/** The cache triage queue. */
private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<>();
/** The queue of requests that are actually going out to the network. */
private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<>();
PriorityBlockingQueue 优先级阻塞队列,放入该队列的元素需要实现Comparable接口,用于队列元素排序。
Step 3:接着是分析RequestQueue.add(stringRequest),先看源码:
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);
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;
}
mCacheQueue.add(request);
return request;
}
代码似乎很简单:
- 首先把Request添加到所有的请求集合中,为确保线程安全使用了同步代码块;
- 给请求设置序号,序号是使用AtomicInteger.incrementAndGet()获取的,确保序号的累加并发不会出现问题;
- Reuest若是需要进行缓存,则添加到缓存队列mChcheQueue,若不需要缓存则直接添加到网络请求队列mNetwordQueue。
将请求添加到队列后,RequestQueue中的NetworkDispatcher和CacheDispatcher线程就开始干活了。
Step 5:接下来看真正干活的NetworkDispatcher和CacheDispatcher是如何实现的?
先看一下NetworkDispatcher的主要代码:
public class NetworkDispatcher extends Thread {
@Override
public void run() {
// 设置当前线程优先级为后台线程
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
try {
processRequest();
} catch (InterruptedException e) {
// 在线程阻塞状态时,调用interrupt()方法,那么它的“中断状态”会被清除并且会收到一个InterruptedException异常
if (mQuit) {
Thread.currentThread().interrupt();
return;
}
}
}
}
// Extracted to its own method to ensure locals have a constrained liveness scope by the GC.
// This is needed to avoid keeping previous request references alive for an indeterminate amount
// of time. Update consumer-proguard-rules.pro when modifying this. See also
// https://github.com/google/volley/issues/114
private void processRequest() throws InterruptedException {
// Take a request from the queue.
Request<?> request = mQueue.take();
processRequest(request);
}
void processRequest(Request<?> request) {
long startTimeMs = SystemClock.elapsedRealtime();
try {
// 部分代码省略
......
// 在此处发出请求,调用Network去发送请求处理
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
......
// 如果请求是需要进行缓存的,把请求结果添加进缓存
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);
request.notifyListenerResponseReceived(response);
}
}
}
NetworkDispatcher是负责网络请求队列的执行,主要逻辑已在代码中注释,接下来看CacheDispatcher是如何实现的?
public class CacheDispatcher extends Thread {
void processRequest(final Request<?> request) throws InterruptedException {
// 省略部分代码
......
// 1. 先尝试从缓存中获取请求结果,不存在则将请求放到网络请求队列中
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
mNetworkQueue.put(request);
}
return;
}
// 2. 如果从缓存中获取到请求结果,则判断是否是否超时失效,失效则将请求放到网络请求队列中
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
mNetworkQueue.put(request);
}
return;
}
// 3.从缓存取到请求结果
.....
}
}
从processRequest()代码中提取出该方法执行的步骤:
- 先尝试从缓存中获取请求结果,不存在则将请求放到网络请求队列中;
- 如果从缓存中获取到请求结果,则判断是否是否超时失效,失效则将请求放到网络请求队列中;
从上面可以看出,CacheDispatcher和NetworkDispatcher是有联系的,若是缓存失效的话,则会将请求重新加入到网络请求队列,让NetworkDispatcher线程去执行。
到此,Volley源码的主线流程分析完毕
网友评论