/ Android源码学习  

Volley源码学习笔记

转载请附原文链接: Volley 源码学习笔记

这几天开启了疯狂的源码学习模式,今天学习的是网络框架 — Volley。依旧按照一贯的思路,从用法入手,再全面分析。

其实把 Volley 理解成为一个 Request (请求)的调度器可能更容易学习。

本次分析主要涉及到的类:

  • Volley — 获取队列的帮助类
  • RequestQueue — 请求队列,维护请求调度
  • NetWorkDispatcher — 网络请求线程
  • CacheDispatcher — 缓存线程
  • Request — 请求

流程图

volley流程图

上面是一张 Volley 处理请求的流程图,简单介绍一下有一个大体的认识,再看源码的时候理解起来会相对容易很多。从上到下依次介绍

  • 首先会把这个请求加入到一个当前请求的队列中,在这个队列中的请求都表示被 Volley 处理过。在后面结束或者取消请求的时候需要用到
  • 接着判断是否需要缓存,需要走左边,不需要走右边直接加入 网络请求队列中
  • 左边是一个等待队列,用于判断当前是否有相同的请求正在进行,如果有则相同的请求都放到这个等待队列中不马上执行。如果没有则向下放到缓存队列中,同时在等待队列中标记该请求
  • 如果请求被加入到等待队列中了,当正在进行的那个请求被执行完毕后,会将等待队列中重复的请求都放到缓存队列中,也就是直接使用缓存数据而不请求网络了。

这是大致的流程,实际上的处理还有很多小细节,后面几节慢慢的来学习。

创建 RequestQueue

用过 Volley 的同学都知道,使用前应该先获取一个请求队列,然后创建请求,再把请求加入到队列当中去。一般在 Android 中会获取一个全局单例的队列,便于对请求的统一管理。首先来看获取队列的代码。

RequestQueue mRequestQueue = Volley.newRequestQueue(this);

来看看 Volley 是怎么创建请求队列的,上面的这个方法调用下面这个方法,传入一个空的 HttpStack。

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) {
}
// 如果没有指定 HttpStack
// 1.在 api9 以上的情况使用 HurlStack -- 实际上就是 HttpUrlConnection
// 2.api9 一下则使用 HttpClient 作为网络请求
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));
}
}
// 通过 stack 创建 Network 处理网络请求
Network network = new BasicNetwork(stack);
// 创建一个请求队列
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
// 开始运行队列
queue.start();
return queue;
}

上面的代码就是创建了一个 RequestQueue 对象,指定了缓存策略和网络请求处理。这里要插一句,为什么区分了一下 SDK 的版本呢?

HttpUrlConnection 和 HttpClient 的区别

HttpClient 是一个网络框架,封装得较为完善,提供了大量的 API 给调用者,性能上也比较稳定。但是这也导致 HttpClient 在保持兼容的情况下扩展性并不是很好,官方也没有很积极的去维护。甚至在 6.0 上将这个框架给废弃。

相反 HttpUrlConnection 就是一个十分轻量的框架,使用上虽然麻烦一点,扩展性很强。但是在 API9 以前这个框架的 bug 很多,表现不稳定。因此在 API9 以前 Volley 是使用的 HttpClient。

在 API9 以后,HttpUrlConnection 修复了许多 bug,并且支持响应压缩 gzip,甚至在 4.0 上提供了缓存机制。因此在 API9 以上 Volley 是使用的 HttpUrlConnection

RequestQueue 构造方法

RequestQueue 创建的时候初始化了几个变量。可以看到默认的网络线程数是四个,而 mDisPatchers 则是一个长度为线程数的数组,而默认的 ExecutorDelivery 是传入了一个主线程的 Handler 。

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; // 分发 response
}

构造方法中看不出什么特别的端倪,接着看 start() 方法

start()

在 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 <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 的队列设置为该队列(在 request中后面需要调用队列中的方法)
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) {
// 获取到缓存的 key
String cacheKey = request.getCacheKey();
// 如果暂存集合中只要能找到这个 key,说明当前有一个相同的请求正在处理中
if (mWaitingRequests.containsKey(cacheKey)) {
// There is already a request in flight. Queue up.
// 获取到暂存集合中这个 key 对应的请求队列(重复请求可能会有多个)
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 {
// 到这里就说明当前没有相同的请求在等待处理
// 将 cacheKey 放入到暂存集合中来标记当前已经有一个请求在等待处理了
// 只做标记,值为空
mWaitingRequests.put(cacheKey, null);
// 将请求放到缓存队列中去
mCacheQueue.add(request);
}
return request;
}
}

注意这几件事情

  • 只要进入到了 add () 方法中的 Request 就表示被处理过的,都要放到 当前请求的集合中,这个集合中就是目前整个队列中存在的请求
  • 如果不需要缓存,则请求都应该请求网络,因此放到网络请求的队列中
  • 暂存集合中只要能找到相同的 key 则说明有相同的请求正在等待执行,即便通过 key 获取的值是空
  • 重复的请求需要待在暂存集合中,并不会马上被加入到队列中

来看看网络队列和缓存队列是什么鬼,发现两个都是优先阻塞队列,这个后面会讲到。

/** 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<?>>();

到这里好像请求就添加完成了,那么请求是怎么被处理的呢?

CacheDispatcher 缓存调度

如果有缓存的情况下,请求被加入到了缓存队列中,那就先来看看缓存调度器是怎么工作的。

CacheDispatcher 继承自 Thread 类,复写了 run() 方法。先看看构造方法,在请求队列 start() 方法中把缓存队列,网络队列,缓存策略,以及发送器都传了进来。

public CacheDispatcher(BlockingQueue<Request<?>> cacheQueue,BlockingQueue<Request<?>> networkQueue,Cache cache, ResponseDelivery delivery) {
mCacheQueue = cacheQueue;
mNetworkQueue = networkQueue;
mCache = cache;
mDelivery = delivery;
}

接下来就是 run() 方法,这段代码比较长,但是逻辑上十分清晰,作者的注释不得不说,写的真是非常详尽。

@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.
// 通过 mCache 去获取该请求在磁盘上的缓存
Cache.Entry entry = mCache.get(request.getCacheKey());
// 如果获取缓存失败 -- miss,则将这个 request 加入到网络队列中
// 进入下一次循环
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<?> 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.
// 这里的逻辑是缓存没有过期,但是缓存需要更新了
// 1.拿到的缓存依然需要直接分发出去
// 2.同时将这个请求扔到网络队列中去获取新的数据
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;
}
}
}

总结一下缓存调度器的四种状态

  • miss — 失败,请求网络
  • expire — 过期,请求网络
  • hit — 成功,分发数据
  • needRefresh — 成功并需要更新,先分发数据,再请求网络

NetworkDispatcher 网络调度

NetWorkDispatcher 同样继承自 Thread 类,重写了 run() 方法。因为在这里不需要关心缓存,所以构造方法中不需要传入缓存队列

public NetworkDispatcher(BlockingQueue<Request<?>> queue,Network network, Cache cache,ResponseDelivery delivery) {
mQueue = queue;
mNetwork = network;
mCache = cache;
mDelivery = delivery;
}

run() 里面的代码也比较长,不过逻辑上也很清晰,依然是一个死循环,不断地从网络队列中获取请求。

@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
// 请求开始时间
long startTimeMs = SystemClock.elapsedRealtime();
Request<?> request;
try {
// Take a request from the queue.
// 从队列中获取一个请求,也是阻塞方法
request = mQueue.take();
} 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");
continue;
}
addTrafficStatsTag(request);
// Perform the network request.
// 这里是请求网络,获取到 networkResponse
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.
// 如果服务器返回了304 并且数据已经发送过一次了,不要再发送第二次,干掉
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
// Parse the response here on the worker thread.
// 解析response
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);
}
}
}

分发响应数据

请求处理完了,返回的数据应该怎么返回呢?在上面的代码中通过这个方法 mDelivery.postResponse(request, response) 来发送数据

@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));
}

还记得之前构造请求队列的时候初始化了这货,handler 是跟主线程 looper 绑定的

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);
}
};
}

这里的 command 就是上面代码中的 ResponseDeliveryRunnable ,点进去瞧一眼实现了 Runnable 接口,到这里还判断了一次是否被取消了。这段代码没什么好说的

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();
}
}

mRequest.deliverResponse(mResponse.result),这是 Request 中的一个抽象方法,在子类中被实现,挑一个 JsonReqeust 实现的随意感受下,是不是就明白了。

@Override
protected void deliverResponse(T response) {
mListener.onResponse(response);
}

请求完成及取消

数据是分发完了,别忘了请求还在各种乱七八糟的集合里面,得处理一下。先来看 request.finish() 方法,下面的代码只贴了核心代码,另外一部分跟 log 相关的没有贴出

void finish(final String tag) {
if (mRequestQueue != null) {
mRequestQueue.finish(this);
}
}

调用的是请求队列里的 finish()

<T> void finish(Request<T> request) {
// Remove from the set of requests currently being processed.
// 首先从现有的请求集合中移除掉该请求
synchronized (mCurrentRequests) {
mCurrentRequests.remove(request);
}
// 回调监听
synchronized (mFinishedListeners) {
for (RequestFinishedListener<T> listener : mFinishedListeners) {
listener.onRequestFinished(request);
}
}
// 如果这个请求有缓存
if (request.shouldCache()) {
synchronized (mWaitingRequests) {
// 从暂存集合中,把重复的请求都取出来
String cacheKey = request.getCacheKey();
Queue<Request<?>> 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);
}
}
}
}

finish() 干了三件事情

  • 把这个请求从现有集合中移除掉,因为已经结束了
  • 回调所有的监听
  • 把暂存集合中的重复请求加入到缓存队列中

如果是取消呢?cancelAll 里面是怎么处理的,下面这个是我们常用的方法,传入一个tag,取消所有该tag的请求

public void cancelAll(final Object tag) {
if (tag == null) {
throw new IllegalArgumentException("Cannot cancelAll with a null tag");
}
cancelAll(new RequestFilter() {
@Override
public boolean apply(Request<?> request) {
return request.getTag() == tag;
}
});
}

创建一个 RequestFilter 过滤器,条件是 request 的 tag 与传入 tag 相同,然后依次调用 cancel(),还记得前面多处地方判断当前请求是否被 cancel ,是就直接调用 finish。

public void cancelAll(RequestFilter filter) {
synchronized (mCurrentRequests) {
for (Request<?> request : mCurrentRequests) {
if (filter.apply(request)) {
request.cancel();
}
}
}
}

优先级

我们知道如果要给一个请求设置优先级,需要重写一个方法 getPriority,默认返回的是 NORMAL ,

public Priority getPriority() {
return Priority.NORMAL;
}
public enum Priority {
LOW,
NORMAL,
HIGH,
IMMEDIATE
}

那入队的时候是怎么判断优先级的呢?Volley 好像也没有处理啊。

Volley 确实没有处理,还记得一开头介绍两个队列的时候提了一嘴,优先阻塞队列 — PriorityBlockingQueue,这个类实现了 BlockingQueue 接口,首先是一个阻塞队列

public boolean add(E e) {
return offer(e);
}
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock();
int n, cap;
Object[] array;
while ((n = size) >= (cap = (array = queue).length))
tryGrow(array, cap);
try {
Comparator<? super E> cmp = comparator;
// 判断是否有对比器
if (cmp == null)
// 没有设置对比器,直接对比 comparable
siftUpComparable(n, e, array);
else
// 利用对比器对比
siftUpUsingComparator(n, e, array, cmp);
size = n + 1;
notEmpty.signal();
} finally {
lock.unlock();
}
return true;
}

如果没有传入对比器,那么被存储的对象应该实现了 Comparable 接口,在这个方法里面会根据 compareTo 方法来比对两个元素的大小,决定放在队列的哪个位置

private static <T> void siftUpComparable(int k, T x, Object[] array) {
Comparable<? super T> key = (Comparable<? super T>) x;
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = array[parent];
if (key.compareTo((T) e) >= 0)
break;
array[k] = e;
k = parent;
}
array[k] = key;
}

Request 正是实现了 Comparable 接口,并且按照优先级重写了 compareTo 方法

@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();
}

NetWork 网络传输

Volley 默认是使用 BasicNetWork(HttpStack) 来做网络传输,重写了 performRequest 这个方法,在这个方法里面调用 HttpStack 的 performRequest 方法。

httpResponse = mHttpStack.performRequest(request, headers);

贴出了 HurlStack 的代码,可以看到实际上就是使用的 HttpUrlConnection, 而且我猜 Hurl 是简称吧~

@Override
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
String url = request.getUrl();
HashMap<String, String> map = new HashMap<String, String>();
map.putAll(request.getHeaders());
map.putAll(additionalHeaders);
if (mUrlRewriter != null) {
String rewritten = mUrlRewriter.rewriteUrl(url);
if (rewritten == null) {
throw new IOException("URL blocked by rewriter: " + url);
}
url = rewritten;
}
URL parsedUrl = new URL(url);
HttpURLConnection connection = openConnection(parsedUrl, request);
for (String headerName : map.keySet()) {
connection.addRequestProperty(headerName, map.get(headerName));
}
setConnectionParametersForRequest(connection, request);
// Initialize HttpResponse with data from the HttpURLConnection.
ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
int responseCode = connection.getResponseCode();
if (responseCode == -1) {
// -1 is returned by getResponseCode() if the response code could not be retrieved.
// Signal to the caller that something was wrong with the connection.
throw new IOException("Could not retrieve response code from HttpUrlConnection.");
}
StatusLine responseStatus = new BasicStatusLine(protocolVersion,
connection.getResponseCode(), connection.getResponseMessage());
BasicHttpResponse response = new BasicHttpResponse(responseStatus);
if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) {
response.setEntity(entityFromConnection(connection));
}
for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
if (header.getKey() != null) {
Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
response.addHeader(h);
}
}
return response;
}

这一节没什么可讲的,其实就是说明 Volley 其实理解为一个调度器可能更贴近于它的功能,如果你需要完全可以把网络传输进行自定义,把线程调度交给 Volley,这也是它灵活强大的地方。

结语

通过这么久的源码学习,我发现看源码是消除误区和误解的最佳办法。并且能学习到大牛的编码风格,设计思路等等。

下一篇学习笔记中,我将分析 Volley 中的 ImageLoader 以及 LruCache 这个两个类,看看在 Android 处理图片的相关技术。