Volley概述
Volley是Google 2013年I/O大會推出的針對Android的HTTP網絡請求框架,讓網絡請求更簡單,更快。
今天我們來點乾貨 分析下Volley源碼 不介紹Volley基本用法 概念等等
因爲 張鴻洋 郭霖 等大神
博客都寫的很清晰了 非常通俗易懂 贊!
IDE AndroidStudio 直接在AS上分析就行
開始分析
1.緩存文件與網絡
我們一個 mRequestQueue 是通過 newRequestQueue拿到的 所以我們直接點進去
他裏面調用的是這個方法 直接點
點擊進去 我們看到了這樣一個方法
private static final String DEFAULT_CACHE_DIR = "volley";
/**
* Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
*
* @param context A {@link Context} to use for creating the cache dir.
* @param stack An {@link HttpStack} to use for the network, or null for default.
* @return A started {@link RequestQueue} instance.
*/
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;
}
不要慌 我們一行一行分析 看看這個方法到底做了些什麼 從第一行開始
Volley 默認的緩存地址是從這裏設置的
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
首先我們先來看 這個局部變量的名字 cacheDir 緩存的的文件夾
getCacheDir 指向於 data/data/包名/cache/ 加DEFAULT_CACHE_DIR ==volley
這裏就得到了Volley的緩存地址爲 data/data/包名/cache/volley
Volley官方文檔說明 Volley 易於定製 擴展性強 所以說我們可以隨便改源碼
我們拿到了緩存文件夾 在來看他用在哪裏了
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
通過代碼我們看到他傳給了 DiskBasedCache
每一個響應有一個緩存文件 而這些都是由DiskBasedCache 來提供支持
官方文檔說明 你的RequestQueue 需要兩個東西去維持他的工作 緩存與網絡
現在我們拿到了緩存
網絡
Network network = new BasicNetwork(stack);
由BasicNetwork 來提供網絡的傳輸
BasicNetwork 可以根據你喜歡的http client 來選擇
官方說明
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);
接着繼續看stack Volley內部做了個判斷 如果你的當前SDK版本大於9(2.3.3)則使用 HurlStack HttpURLConnection
如果小於2.3 則使用 HttpClientStack
源碼中有個鏈接是 谷歌開發人員的一個博客 介紹了利弊
大概意思就是說在2.3以前用HttpClient最好 因爲BUG少
2.3之後的HttpURLConnection 是最好的選擇 其簡單的API和代碼量很少使其非常適合Android 透明的壓縮和緩存緩存減少網絡使用,提高速度並節省電池
HttpClient都被google廢棄了 在6.0 SDK中直接拿掉了
接着又回到上頭 我們說過可以自定義Stack 選擇自己喜歡的http 但其實這個參數 一般都爲空 …
倒序回到調用
Stack 默認爲null
我們傳的是單參數的方法 傳到源碼就是一個上下文 所以說還是會走系統默認 通過SDK自行選擇
mRequestQueue = Volley.newRequestQueue(this);
我們的volley 是通過一個 queue.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前他先進行了stop 其實這是一種很好的代碼實現方式
避免你這個應用被start兩次
接着創建緩存調度程序並啓動它。
爲什麼這麼說 new CacheDispatcher 緩存線程 我們點進去看下
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();
}
我們來找一找 for循環的length值爲多少吧 點擊mDispatchers
Shift+F 全局查找
來到了這裏 threadPoolSize 是通過構造方法傳過來的
Alt+F7 查看RequestQueue在哪裏被調用了
到了這 在快捷鍵
找到了調用地址 雙擊點進去
上面可能有點花眼 重點記住快捷鍵的使用
也就是說當queue.start() 開啓的時候我們 創建了一個緩存線程 並創建了
4個網絡線程 並且分別把它們給開啓
12.30了 睡覺了 寫博客真費時間…. 好在是週末
我們接着回到緩存線程 CacheDispatcher繼承於 Thread 肯定會執行run方法
我們來看看 緩存線程的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.
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;
}
}
}
我們看到 他維護了一個while true 的死循環
// 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");
從mCacheQueue 中拿到request
他這個死循環不是一直運行的而是一個阻塞式運行take()
如果有我就取 沒有就等着
// If the request has been canceled, don't bother dispatching it.
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
他拿到了request 做了一個判斷你的request是不是被人給取消掉了 如果取消掉了就不做任何操作 如果沒有接着往下走
// 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;
}
通過 request.getCacheKey() 嘗試從緩存的集合中拿到他緩存的數據
getCacheKey getUrl 通過這個url服務器地址爲一個key 進行判斷
/**
* Returns the cache key for this request. By default, this is the URL.
*/
public String getCacheKey() {
return getUrl();
}
如果沒有request緩存數據 就把這個request添加到網絡隊列裏
mNetworkQueue.put(request);
如果有緩存數據則不會走之前的if判斷 就走到了這裏
// 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;
}
entry.isExpired() 判斷你緩存數據是否過期了 如果過期
接這把你添加到網絡對列裏 mNetworkQueue.put(request); 等待網絡請求
如果有緩存並且沒有過期就接着往下走
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
解析與響應 他把你緩存的數據存進去 並最終拿到了response 想要的數據
Volley可以把原生的響應數據 entry.data 解析成你想要的數據 也就是io流帶過來的二進制
接着繼續走到了這句
mDelivery.postResponse(request, response);
把request響應的數據分發到主線程 這就是緩存大概流程
接着我們看網絡線程的run方法
網絡線程的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 = 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()) {
request.finish("not-modified");
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);
}
}
}
首先看到的依舊是一個 阻塞 死循環 依舊是從一個mQueue取request
// Take a request from the queue.
request = mQueue.take();
也做了一個判斷 取消了不做任何操作
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}
沒取消就走到了這裏 去執行一個網絡請求 拿到了networkResponse
// Perform the network request.
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
如果沒有304異常就把networkResponse 解析成了一個response
// 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");
}
如果我的請求是被需要緩存的request.shouldCache() 那就寫緩存
mCache.put(request.getCacheKey(), response.cacheEntry);
寫完了緩存就走到了這裏 分發到主線程
mDelivery.postResponse(request, response);
我們來看看postResponse怎麼發送到主線程的把
我們來到了這裏 正常的線程池execute 裏面應該是開線程 處理事件 Volley重寫了這個方法 直接分發到主線程
因此,我們的Response響應最終會在主線程被調用方接收並進行處理。
總結圖片
Volley只是個框架從源碼就能看到 最底層的操作還是由 HttpClient HttpURLConnection 來實現的 其實選擇網絡可以改成okhttp 可自行改造 自由度非常高
最後我感覺我寫的挺清晰了 有什麼不對的地方 希望留言 我會進行修改 詳細到寫的我快吐血了….