Volley框架介紹
在開發(fā)一些調用服務器端API以獲得數據的應用時赤嚼,需要編寫繁瑣的基于HttpClient或HttpURLConnection的代碼與處理異步請求的線程管理操作演侯,如Android Studio自帶的LoginActivity模板旬昭,就實現(xiàn)了一個繼承自AsyncTask的臃腫內部類來處理一個簡單的登錄請求眯漩。
應運而生的諸多網絡通信框架中旧噪,Volley的優(yōu)點包括(翻譯自google官方文檔):
- 自動調度網絡請求
- 可以并發(fā)進行多個網絡通信
- 可操作的標準HTTP緩存(磁盤吨娜、內存)
- 支持請求優(yōu)先級
- 提供可以取消一個或多個請求的接口
- 可以簡易地自定義一些組件
- 通過排序從網絡異步獲取數據,正確地填充UI組件
- 提供調試與跟蹤工具
總的來說淘钟,Volley框架在輕量級與可拓展性上的優(yōu)越表現(xiàn)讓我們可以利用它進行一些頻繁但數據量小的網絡通信宦赠,利用內置的HTTP緩存功能也可以實現(xiàn)圖片資源的加載與緩存。
配置
Volley的開源地址為https://github.com/google/volley 米母,一般可以通過在項目里導入module的方式引入clone到本地的Volley庫勾扭,或者在項目的build.gradle中加入
dependencies {
...
compile 'com.android.volley:volley:1.0.0'
}
的方式添加對volley的依賴。
框架使用的基本方法
Volley的常規(guī)使用方法(來自Google官方文檔):
final TextView mTextView = (TextView) findViewById(R.id.text);
...
// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(this);
String url ="http://www.google.com";
// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// Display the first 500 characters of the response string.
mTextView.setText("Response is: "+ response.substring(0,500));
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
mTextView.setText("That didn't work!");
}
});
// Add the request to the RequestQueue.
queue.add(stringRequest);
相對來說調用過程十分簡潔铁瞒,主要的組件包括:用于調度妙色、并發(fā)執(zhí)行多個HTTP請求的RequestQueue,volley庫中的Request對象慧耍,傳入Request對象的Listener用于處理成功請求后的response身辨,傳入Request對象的ErrorListener用于執(zhí)行請求錯誤/異常后的相關操作。
宏觀來看芍碧,Volley框架的總體運行與調用結構為(圖片轉載自http://blog.csdn.net/geolo/article/details/43966171 ):
可以看到煌珊,傳入RequestQueue的對象可以是現(xiàn)有的,StringRequest(獲得String格式的response)泌豆,JsonObjectRequest(可以傳入一個json對象作為post請求的params定庵,并返回json格式的response),ImageRequest(前文提過volley框架因為緩存的存在對圖片資源的加載與管理有很好的表現(xiàn)),實現(xiàn)Request抽象類獲得的自定義請求對象洗贰,實現(xiàn)方式非常簡潔找岖,可以參考官方文檔https://developer.android.com/training/volley/request-custom.html 的示例,這里不做贅述敛滋。
而RequestQueue管理了兩種線程许布,NetworkDispatcher與CacheDispatcher,分別處理網絡請求與緩存管理绎晃,內部利用底層的HttpClient或HttpURLConnection實現(xiàn)網絡通信蜜唾,利用磁盤緩存(CacheDispathcer運行時讀取文件寫入HashMap中由內存管理)實現(xiàn)緩存數據的持久化。
上圖是來自Google官方文檔的庶艾,request對象的生命期示意圖袁余,這里進行了主線程、緩存線程咱揍、網絡線程的區(qū)分颖榜,首先檢查該請求是否命中緩存,若命中則返回緩存結果煤裙,若丟失則加入NetworkDispatcher線程池進行網絡通信掩完,返回結果、寫入緩存硼砰。
下面通過閱讀volley庫中的一些源碼來詳細解析這個過程的具體實現(xiàn)方式且蓬。
Volley源碼解析
首先我們一般通過調用Volley.newRequestQueue(Context context)靜態(tài)方法獲得RequestQueue對象的實例(當然可以直接傳入RequestQueue需要的一些構造參數對象new出來,自己實現(xiàn)一個單例工廠管理RequestQueue對象是一個不錯的選擇题翰,適用于經常需要網絡通信的應用)恶阴,方法具體實現(xiàn)為:
/**
* 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;
}
對于RequestQueue需要的兩個參數,Cache接口與Network接口豹障,volley庫中自帶了BasicNetwork與DiskBasedCache作為一種具體實現(xiàn)類冯事。
首先判斷設備的版本號是否為9及以上,若是則使用HurlStack作為Network實現(xiàn)類的參數沼填,否則使用HttpClientStack桅咆,前者內部封裝的網絡通信組件為HttpURLConnection括授,后者為HttpClient坞笙,這兩個基本組件的使用場景與android的版本迭代過程有關,因此這里不再做細致介紹薛夜。
public interface Network {
/**
* Performs the specified request.
* @param request Request to process
* @return A {@link NetworkResponse} with data and caching metadata; will never be null
* @throws VolleyError on errors
*/
NetworkResponse performRequest(Request<?> request) throws VolleyError;
}
Network接口提供了performRequest方法梯澜,很直觀地傳入request獲得response的過程晚伙,在BasicNetwork中漓帚,利用作為參數傳入的HttpStack接口尝抖,調用底層的網絡通信組件(前段已有介紹)獲得response昧辽,并添加了加入至緩存(后文會解釋)搅荞、異常處理取具、記錄長時請求暇检、嘗試重請求等封裝好的功能块仆。
public interface Cache {
Entry get(String key);
void put(String key, Entry entry);
void initialize();
void invalidate(String key, boolean fullExpire);
void remove(String key);
void clear();
class Entry {
public byte[] data;
public String etag;
public long serverDate;
public long lastModified;
public long ttl;
public long softTtl;
public Map<String, String> responseHeaders = Collections.emptyMap();
boolean isExpired() {
return this.ttl < System.currentTimeMillis();
}
boolean refreshNeeded() {
return this.softTtl < System.currentTimeMillis();
}
}
}
Cache接口提供了一個內部類Entry作為緩存的元數據,其中包括一個存儲responseHeaders的Map科汗,事實上Volley框架提供的緩存功能便是根據response的headers信息來管理緩存的刷新周期、有效時長等信息坤检,實現(xiàn)HTTP標準緩存早歇。DiskBasedCache利用讀寫文件實現(xiàn)了基于磁盤的緩存持久化晨另,CacheDispatcher線程會調用intialize()方法加數據從文件加載到內存的HashMap中拯刁,并對每個請求進行cache是否命中的操作。
Volley框架的這種設計方法支持了開發(fā)者對Cache與Network接口的自定義實現(xiàn)與擴展帚桩,我在這只對庫中自帶的兩個實現(xiàn)類簡單介紹账嚎,底層的實現(xiàn)方式并非是框架運作流程的重點所在郭蕉,因此不再貼上繁瑣的代碼與分析以免浪費閱讀者的時間召锈。
回到之前在Volley中創(chuàng)建的RequestQueue對象,在創(chuàng)建它的實例后梢薪,首先調用其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();
}
}
之前的介紹中提過,NetworkDispatcher與CacheDispatcher均為由ReuquestQueue管理的線程琐馆,他們都繼承自Thread類啡捶,同時運作的有一個CacheDispatcher與多個NetworkDispatcher(默認4個)×硕模可以看到兩種線程都傳入了mNetworkQueue(Request對象作為范型的優(yōu)先隊列)與mCache的引用勿她,因此它們可以對每個加入的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);
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) {
String cacheKey = request.getCacheKey();
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<>();
}
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);
}
return request;
}
}
以上為處理新加入的request的方法,首先根據request.shouldCache()判斷請求是否可以加入緩存,若不可以直接加入mNetworkQueue,若可以則加mCacheQueue翘贮,途中會有一個判斷阻塞中的請求是否與新請求相同的過濾判斷。
接下來回到NetworkDispatcher與CacheDispatcher線程芍耘,看看他們都在執(zhí)行什么工作:
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;
}
}
}
}
以上是CacheDispatcher的run()方法浸剩,首先initialize()持有的Cache對象,將緩存讀入內存重罪,接下來在while(true)的輪詢中,每次從mCacheQueue隊列中取出一個request,分別判斷是否命中緩存砸讳、緩存是否過期,若未命中或過期則加入mNetworkQueue常遂,否則直接返回緩存結果。
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);
}
}
}
以上為NetworkDispatcher的run()方法纬傲,同樣是通過while(true)輪詢叹括,每次從mNetworkQueue中取出request报咳,調用Network接口的處理請求方法继低,獲得response對象柴底,后續(xù)自然也有相應的異常處理、錯誤碼處理鸿脓、response交給request的parseNetworkResponse()解析等操作拨黔,到這里流程就已經比較清晰了,解析操作大部分情況自帶的StringRequest與JsonObjectRequest已經足夠,volley也支持直接重寫該方法,可以參考上文鏈接中的示例绍撞。
源碼的解析有些雜亂,宏觀上還是線程的管理實現(xiàn)異步請求得院,加上一個與計算機中的二級緩存機制十分相似的緩存層實現(xiàn)傻铣,可以回顧一下之前的request生命周期圖,再次理解下三種線程的相互關系祥绞。
雖然寫了很多非洲,但volley框架使用起來非常簡潔鸭限,功能也很完善,之后有心情也許會給出一些具體應用項目中的實現(xiàn)場景(你就是想應付第三次博客啊喂A教ぁ)败京。