編輯:關於Android編程
盜張網上的流程圖
構建RequestQueue
Volley 的調用比較簡單,通過 newRequestQueue(…) 函數新建並啟動一個請求隊列RequestQueue後,只需要往這個RequestQueue不斷 add Request 即可。我們來看看newRequestQueue(…)的代碼:
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) { // 當不顯示指定HttpStack時,若SDK版本9以上使用HttpUrlConnection,9以下使用HttpClient 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時,若SDK版本9以上使用HttpUrlConnection,9以下使用HttpClient,當然我們可以在此處傳入OkHttpClient。(HttpClient的Api太多不好維護,現在已被廢棄,建議使用HttpUrlConnection)
Request這個類 代表一個網絡請求的抽象類。
public abstract class Requestimplements Comparable > `
我們通過構建一個Request類的非抽象子類(StringRequest、JsonRequest、ImageRequest 或自定義)對象,並將其加入到RequestQueue中來完成一次網絡請求操作。
Volley 支持 8 種 Http 請求方式 GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE, PATCH
Request 類中包含了請求 url,請求請求方式,請求 Header,請求 Body,請求的優先級等信息(實現了Comparable接口,這裡的優先級,僅僅是保證一個請求比另外一個請求先處理,而並不能保證一個高優先級請求一定會比低優先級的請求先回來
)。
因為是抽象類,子類必須重寫的兩個方法。
protected abstract ResponseparseNetworkResponse(NetworkResponse var1);
子類重寫此方法,將網絡返回的原生字節內容,轉換成合適的類型。此方法會在工作線程中被調用。
protected abstract void deliverResponse(T var1);
子類重寫此方法,將解析成合適類型的內容傳遞給它們的監聽回調。
RequestQueue中有如下元素:
一個Request被提交之後有幾個去處:
1。mCurrentRequests對應所有請求隊列。所有調用add的Request必然都會添加到這裡面來。
2.mNetworkQueue 對應網絡隊列。如果一個Request不需要緩存,那麼add之後會被直接添加到網絡隊列中。
3.mCacheQueue對應緩存請求。如果一個Request需要緩存,並且當前的RequestQueue中並沒有一個Request的getCacheKey和當前Request相同(可以認為一個請求),那麼加入緩存隊列,讓緩存工作線程來處理。
4.mWaitingRequests對應等待隊列。如果RequestQueue中已經有一個相同請求在處理,這裡只需要將這個Request放到等待隊列中,等之前的Request結果回來之後,進行處理即可(我們同時發出了三個一模一樣的Request,此時底層其實不必真正走三個網絡請求,而只需要走一個請求即可。所以Request1被add之後會被調度執行,而Request2 和Request3被加進來時,如果Request1還未執行完畢,那麼Request2和 Request3只需要等著Request1的結果即可。)
此外還有默認的一個緩存線程和四個網絡線程
private final PriorityBlockingQueue啟動隊列:mCacheQueue; private final PriorityBlockingQueue mNetworkQueue; private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4; private NetworkDispatcher[] mDispatchers; private CacheDispatcher mCacheDispatcher; private final Map > mWaitingRequests; private final Set mCurrentRequests;
public void start() { this.stop(); this.mCacheDispatcher = new CacheDispatcher(this.mCacheQueue, this.mNetworkQueue, this.mCache, this.mDelivery); this.mCacheDispatcher.start(); for(int i = 0; i < this.mDispatchers.length; ++i) { NetworkDispatcher networkDispatcher = new NetworkDispatcher(this.mNetworkQueue, this.mNetwork, this.mCache, this.mDelivery); this.mDispatchers[i] = networkDispatcher; networkDispatcher.start(); } } public void stop() { if(this.mCacheDispatcher != null) { this.mCacheDispatcher.quit(); } for(int i = 0; i < this.mDispatchers.length; ++i) { if(this.mDispatchers[i] != null) { this.mDispatchers[i].quit(); } } }
首先停止當前正在運行的線程,開啟一個緩存調度線程CacheDispatcher和 n 個網絡調度線程NetworkDispatcher,這裡 n 默認為 4,存在優化的余地,比如可以根據 CPU 核數以及網絡類型計算更合適的並發數。緩存調度線程不斷的從緩存請求隊列中取出 Request 去處理,網絡調度線程不斷的從網絡請求隊列中取出 Request 去處理。
緩存線程的run():
@Override public void run() { //初始化Cache mCache.initialize(); Request request; while (true) { //阻塞 獲取一個Cache任務 request = mCacheQueue.take(); try { //已經被取消 if (request.isCanceled()) { request.finish("cache-discard-canceled"); continue; } //如果拿cache未果,放入網絡請求隊列 Cache.Entry entry = mCache.get(request.getCacheKey()); if (entry == null) { request.addMarker("cache-miss"); mNetworkQueue.put(request); continue; } //緩存超時,硬過期,放入網絡請求隊列 if (entry.isExpired()) { request.addMarker("cache-hit-expired"); request.setCacheEntry(entry); mNetworkQueue.put(request); continue; } //根據Cache構造Response Response response = request.parseNetworkResponse( new NetworkResponse(entry.data, entry.responseHeaders)); //是否超過軟過期 if (!entry.refreshNeeded()) { // 直接返回Cache mDelivery.postResponse(request, response); } else { request.setCacheEntry(entry); //設置中間結果 response.intermediate = true; //發送中間結果 final Request finalRequest = request; mDelivery.postResponse(request, response, new Runnable() { @Override public void run() { try { //返回中間結果後,將請求放入網絡隊列 mNetworkQueue.put(finalRequest); } catch (InterruptedException e) { // Not much we can do about this. } } }); } } catch (Exception e) { } } }
這裡的Cache分為硬過期和軟過期:
public interface Cache { /** * Retrieves an entry from the cache. * @param key Cache key * @return An {@link Entry} or null in the event of a cache miss */ public Entry get(String key); /** * Adds or replaces an entry to the cache. * @param key Cache key * @param entry Data to store and metadata for cache coherency, TTL, etc. */ public void put(String key, Entry entry); /** * Performs any potentially long-running actions needed to initialize the cache; * will be called from a worker thread. */ public void initialize(); /** * Invalidates an entry in the cache. * @param key Cache key * @param fullExpire True to fully expire the entry, false to soft expire */ public void invalidate(String key, boolean fullExpire); /** * Removes an entry from the cache. * @param key Cache key */ public void remove(String key); /** * Empties the cache. */ public void clear(); /** * Data and metadata for an entry returned by the cache. */ public static class Entry { /** The data returned from cache. */ public byte[] data; /** ETag for cache coherency. */ public String etag; /** Date of this response as reported by the server. */ public long serverDate; /** TTL for this record. */ public long ttl; /** Soft TTL for this record. */ public long softTtl; /** Immutable response headers as received from server; must be non-null. */ public MapresponseHeaders = Collections.emptyMap(); /** True if the entry is expired. */ public boolean isExpired() { return this.ttl < System.currentTimeMillis(); } /** True if a refresh is needed from the original data source. */ public boolean refreshNeeded() { return this.softTtl < System.currentTimeMillis(); } } }
softTtl字段對應軟過期,ttl字段對應硬過期。如果ttl過期,那麼這個緩存永遠不會被使用了;如果softTtl沒有過期,這個數據直接返回;如果softTtl過期,那麼這次請求將有兩次返回,第一次返回這個Cahce,第二次返回網絡請求的結果:先進入頁面展示緩存,然後再刷新頁面
接下來看網絡線程的run():
執行網絡請求的工作線程,默認有4個線程,它不停地從網絡隊列中取任務執行。
public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); Request request; while (true) { long startTimeMs = SystemClock.elapsedRealtime(); // release previous request object to avoid leaking request object when mQueue is drained. request = null; try { request = mQueue.take(); } catch (InterruptedException e) { if (mQuit) { return; } continue; } try { request.addMarker("network-queue-take"); //取消 if (request.isCanceled()) { request.finish("network-discard-cancelled"); continue; } //通過Http棧實現客戶端發送網絡請求 NetworkResponse networkResponse = mNetwork.performRequest(request); request.addMarker("network-http-complete"); // 如果緩存軟過期,那麼會重新走網絡;如果server返回304,表示上次之後請求結果數據本地並沒有過期,所以可以直接用本地的,因為之前Volley已經發過一次Response了,所以這裡就不需要再發送Response結果了。 if (networkResponse.notModified && request.hasHadResponseDelivered()) { request.finish("not-modified"); continue; } Response response = request.parseNetworkResponse(networkResponse); request.addMarker("network-parse-complete"); //更新緩存 if (request.shouldCache() && response.cacheEntry != null) { mCache.put(request.getCacheKey(), response.cacheEntry); request.addMarker("network-cache-written"); } //發送結果 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); } } }請求完成:
/** * Called from {@link Request#finish(String)}, indicating that processing of the given request * has finished. * * Releases waiting requests for request.getCacheKey() if * request.shouldCache(). */ void finish(Request request) { // Remove from the set of requests currently being processed. synchronized (mCurrentRequests) { mCurrentRequests.remove(request); } if (request.shouldCache()) { synchronized (mWaitingRequests) { String cacheKey = request.getCacheKey(); Queue 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); } } } }
(1). 首先從正在進行中請求集合mCurrentRequests中移除該請求。
(2). 然後查找請求等待集合mWaitingRequests中是否存在等待的請求,如果存在,則將等待隊列移除,並將等待隊列所有的請求添加到緩存請求隊列中,讓緩存請求處理線程CacheDispatcher自動處理。
請求取消:
public void cancelAll(RequestFilter filter)
public void cancelAll(final Object tag)
取消當前請求集合中所有符合條件的請求。
filter 參數表示可以按照自定義的過濾器過濾需要取消的請求。
tag 表示按照Request.setTag設置好的 tag 取消請求,比如同屬於某個 Activity 的。
NetworkImageView自動管理請求
@Override
protected void onDetachedFromWindow() {
if (mImageContainer != null) {
// If the view was bound to an image request, cancel it and clear
// out the image from the view.
mImageContainer.cancelRequest();
setImageBitmap(null);
// also clear out the container so we can reload the image if necessary.
mImageContainer = null;
}
super.onDetachedFromWindow();
}
此時自動觸發事件取消之前的請求。
private void loadImageIfNecessary(final boolean isInLayoutPass) {
***
if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
if (mImageContainer.getRequestUrl().equals(mUrl)) {
// if the request is from the same URL, return.
return;
} else {
// if there is a pre-existing request, cancel it if it's fetching a different URL.
mImageContainer.cancelRequest();
setDefaultImageOrNull();
}
}
***
}
相同的請求直接返回,不同的url則取消之前的請求
一、引言 想實現一個空白的畫板,上面可以畫出手滑動的軌跡,就這麼一個小需求
眾所周知,當需要文字進入走馬燈狀態的時候,需要設置屬性 android:ellipsize="marquee" 但是有時候並不能啟
首先給出效果圖 中間的色塊是因為視頻轉成GIF造成的失真,自動忽略哈。大家知道,橫向的跑馬燈android自帶的TextView就可以實現,詳情請百度【Andr
android 6.0權限全面詳細分析和解決方案Marshmallow版本權限修改 android的權限系統一直是首要的安全概念,因為這些權限只在安裝的時候被詢問一次。