Volley V1.1.0 框架解析及線程池優(yōu)化思考

Volley是2013 Google I/O大會上發(fā)布的異步網(wǎng)絡(luò)請求框架和圖片加載框架,適合數(shù)據(jù)量小菇篡、通信頻繁的網(wǎng)絡(luò)操作漩符。最近正好在做框架優(yōu)化,借機會再溫習(xí)一遍這個老框架驱还。

Volley官方文檔:https://developer.android.com/training/volley
Volley項目:https://github.com/google/volley

一嗜暴、Volley整體框架解析

1.1 架構(gòu)圖
官方提供的volley架構(gòu)圖

從Volley整體架構(gòu)來看,主要分三層:

  • 請求封裝:支持自定義各種數(shù)據(jù)類型的請求议蟆。
  • 請求調(diào)度:分緩存和網(wǎng)絡(luò)兩類線程闷沥。
  • 數(shù)據(jù)獲取:內(nèi)存咐容、磁盤舆逃、網(wǎng)絡(luò)。
1.2 類圖
volley類圖

核心類:

Volley:Volley框架入口疟丙,初始化RequestQueue颖侄。

RequestQueue:Volley網(wǎng)絡(luò)請求核心管理類。Network享郊、Cache览祖、ResponseDelivery、CacheDispatcher炊琉、NetworkDispatcher都聚合在RequestQueue中展蒂,它們都是Volley初始化RequestQueue時候一起初始化的,并作為參數(shù)傳入RequestQueue作為全局變量苔咪。

  • CacheDispatcher 緩存線程*1
  • NetworkDispatcher 網(wǎng)絡(luò)請求線程*4
  • PriorityBlockingQueue:mCacheQueue 緩存線程任務(wù)隊列
  • PriorityBlockingQueue:mNetworkQueue 網(wǎng)絡(luò)請求線程任務(wù)隊列
  • ResponseDelivery:響應(yīng)分發(fā)調(diào)度

BasicNetwork:網(wǎng)絡(luò)請求處理

  • HttpStack:具體網(wǎng)絡(luò)請求執(zhí)行锰悼,封裝了HttpURLConnection和HttpClient按sdk9版本來選擇使用。
  • ByteArrayPool:網(wǎng)絡(luò)請求原始數(shù)據(jù)緩存池团赏。

DiskBaseCache:磁盤緩存處理

Request:網(wǎng)絡(luò)請求封裝類

  • 框架定義Request
  • 自定義Request

NetworkResponse:網(wǎng)絡(luò)請求原始數(shù)據(jù)響應(yīng)封裝類箕般。

Response:對網(wǎng)絡(luò)請求原始數(shù)據(jù)進行解析后的響應(yīng)封裝類。

1.3 請求時序圖

一次網(wǎng)絡(luò)請求流程時序

一次網(wǎng)絡(luò)請求流程:

1)Volley通過newRequestQueue初始化RequestQueue: 初始化HttpStack舔清、BasicNetwork丝里、ExecutorDelivery曲初,啟動CacheDispatcher和NetworkDispatcher線程。

2)RequestQueue通過add添加Request觸發(fā)數(shù)據(jù)獲取流程:

添加請求觸發(fā)邏輯

3)CacheDispatcher和NetworkDispatcher均為while(true)循環(huán)執(zhí)行杯聚,通過對應(yīng)的queue來阻塞任務(wù)臼婆,當(dāng)對應(yīng)的queue添加了request,會執(zhí)行如下流程:

獲取數(shù)據(jù)邏輯

二幌绍、Volley核心源碼分析

對整個框架結(jié)構(gòu)和執(zhí)行流程有了大致了解之后颁褂,來細(xì)化分析幾點核心功能:

  • Volley的線程管理
  • Volley的緩存邏輯
  • Volley的網(wǎng)絡(luò)請求原始數(shù)據(jù)緩存池優(yōu)化:ByteArrayPool
  • 請求cancel邏輯
2.1 Volley的線程管理

Volley默認(rèn)線程:CacheDispatcher *1,NetworkDispatcher *4傀广,多余的請求入隊PriorityBlockingQueue颁独。

源碼位置:com/android/volley/RequestQueue.java

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

com/android/volley/NetworkDispatcher.java

@Override
public void run() {
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
   Request<?> request;
   while (true) {
 ...
       try {
            // 從queue中獲取任務(wù)
           request = mQueue.take();
...
           // 執(zhí)行網(wǎng)絡(luò)請求
           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;
           }
            // 解析網(wǎng)絡(luò)請求數(shù)據(jù)
           Response<?> response = request.parseNetworkResponse(networkResponse);
           request.addMarker("network-parse-complete");
           // 寫緩存數(shù)據(jù)
           // TODO: Only update cache metadata instead of entire record for 304s.
           if (request.shouldCache() && response.cacheEntry.isCache) {
                mCache.put(request.getCacheKey(), response.cacheEntry);
               request.addMarker("network-cache-written");
           }
            // 分發(fā)response
           request.markDelivered();
           mDelivery.postResponse(request, response);
...
    }
}

源碼分析:

  • RequestQueue組合了CacheDispatcher1和NetworkDispatcher4,默認(rèn)5個常駐線程主儡,除非主動stop奖唯。

  • 線程是while(true)循環(huán),通過PriorityBlockingQueue阻塞糜值,同一線程循環(huán)獲取PriorityBlockingQueue任務(wù)丰捷,正常情況下,下一個任務(wù)的調(diào)度需要等上一次請求執(zhí)行完畢分發(fā)響應(yīng)后才會調(diào)度到寂汇。

  • PriorityBlockingQueue是一個支持優(yōu)先級的無界阻塞隊列病往,默認(rèn)容量11,擴容規(guī)則:舊容量小于64則翻倍骄瓣,舊容量大于64則增加一半停巷。雖然比LinkedBlockingQueue初始化默認(rèn)容量為Integer.MAX_VALUE要強點, 但是理論上還是可以一直添加request榕栏,直到系統(tǒng)資源耗盡畔勤。

2.2 Volley的緩存邏輯

源碼分析:

  • Volley通過Request.setShouldCache設(shè)置請求是否緩存。

  • 如果設(shè)置了緩存扒磁,整體邏輯是先從磁盤緩存獲取庆揪,如果沒有再進行網(wǎng)絡(luò)請求,網(wǎng)絡(luò)請求成功后數(shù)據(jù)再做磁盤緩存妨托。

這里主要看下磁盤緩存是怎么做的:

  • 路徑:data/data/packageName/cache/volley
  • 文件形式:
-rw------- 1 u0_a255 u0_a255_cache   6250 2020-11-17 14:57 -11505126341240133916
-rw------- 1 u0_a255 u0_a255_cache   1532 2020-11-17 14:57 -12302758661826148972
-rw------- 1 u0_a255 u0_a255_cache   1787 2020-11-17 14:57 -1391051118-579309601
-rw------- 1 u0_a255 u0_a255_cache   4877 2020-11-17 14:57 -13947998811265044989
  • 文件內(nèi)容即原始response數(shù)據(jù)缸榛。
  • 磁盤緩存大小:5M
  • 文件刪除規(guī)則:LRU
2.3 Volley字節(jié)流內(nèi)存優(yōu)化ByteArrayPool

com/android/volley/toolbox/BasicNetwork.java

public NetworkResponse performRequest(Request<?> request)
        throws VolleyError {
    while (true) {
   …        //具體執(zhí)行網(wǎng)絡(luò)請求
           httpResponse = mHttpStack.performRequest(request, headers);
   ...
           if (httpResponse.getEntity() != null) {
                 //HttpEntity內(nèi)容轉(zhuǎn)到內(nèi)存bytes[]中
                responseContents = entityToBytes(httpResponse.getEntity());
           } else {
                // Add 0 byte response as a way of honestly representing a
               // no-content request.
               responseContents = new byte[0];
           }
…           //返回原始response數(shù)據(jù)
            return new NetworkResponse(statusCode, responseContents,
                   responseHeaders, false,
                   SystemClock.elapsedRealtime() - requestStart);
...
    }
}

接下來看看entityToBytes方法:

/** Reads the contents of HttpEntity into a byte[]. */
private byte[] entityToBytes(HttpEntity entity)
        throws IOException, ServerError {
    PoolingByteArrayOutputStream bytes = new PoolingByteArrayOutputStream(
            mPool, (int) entity.getContentLength());
   byte[] buffer = null;
   try {
        InputStream in = entity.getContent();
       if (in == null) {
            throw new ServerError();
       }
        buffer = mPool.getBuf(1024);
       int count;
       while ((count = in.read(buffer)) != -1) {
            bytes.write(buffer, 0, count);
       }
        return bytes.toByteArray();
   } finally {
        try {
            // Close the InputStream and release the resources by "consuming
           // the content".
           entity.consumeContent();
       } catch (IOException e) {
            // This can happen if there was an exception above that left the
           // entity in
           // an invalid state.
           VolleyLog.v("Error occurred when calling consumingContent");
       }
        mPool.returnBuf(buffer);
       bytes.close();
   }
}

源碼分析:

  • BasicNetwork的performRequest執(zhí)行具體網(wǎng)絡(luò)請求兰伤,總共三步内颗,http核心庫執(zhí)行網(wǎng)絡(luò)請求獲取HttpEntry、Entry轉(zhuǎn)byte[]敦腔、byte[]內(nèi)容封裝入NetworkResponse中返回均澳。由于volley是輕量級頻次高的網(wǎng)絡(luò)請求框架,因此在這里會頻繁創(chuàng)建和銷毀byte[],為了提高性能负懦,volley定義了一個byte[]緩沖池筒捺,即ByteArrayPool 。

  • PoolingByteArrayOutputStream其實就是一個輸出流纸厉,只不過系統(tǒng)的輸出流在使用byte[]時,如果大小不夠五嫂,會自動擴大byte[]的大小颗品。而PoolingByteArrayOutputStream則是使用了上面的字節(jié)數(shù)組緩沖池,從池中獲取byte[] 使用完畢后再歸還沃缘。 在BasicNetwork中對響應(yīng)進行解析的時候使用到了該輸出流躯枢。

2.4 請求cancel邏輯

請求cancel邏輯主要分兩個部分:RequestQueue對Request進行統(tǒng)一cancel,Request自己標(biāo)記cancel槐臀。

com/android/volley/Request.java

/**
* Mark this request as canceled.  No callback will be delivered.
*/
public void cancel() {
    mCanceled = true;
}
/**
* Returns true if this request has been canceled.
*/
public boolean isCanceled() {
    return mCanceled;
}

com/android/volley/NetworkDispatcher.java

public void run() {
...
            // Take a request from the queue.
           request = mQueue.take();
...
           // If the request was cancelled already, do not perform the
           // network request.
           if (request.isCanceled()) {
                request.finish("network-discard-cancelled");
               continue;
           }
...
}

Request被線程調(diào)度到了锄蹂,會先判斷cancel標(biāo)記來決定是否執(zhí)行任務(wù)。

com/android/volley/RequestQueue.java

//保存正在被調(diào)度的request以及queue中等待的request的集合
private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();

/**
* Cancels all requests in this queue for which the given filter applies.
*
* @param filter The filtering function to use
*/
public void cancelAll(RequestFilter filter) {
   synchronized (mCurrentRequests) {
       for (Request<?> request : mCurrentRequests) {
           if (filter.apply(request)) {
               request.cancel();
           }
       }
   }
}
/**
* Cancels all requests in this queue with the given tag. Tag must be non-null
* and equality is by identity.
*/
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;
       }
   });
}

RequestQueue支持按過濾條件和標(biāo)簽進行request的批量刪除水慨。

很明顯得糜,Volley對Request的cancel只能作用于還沒走網(wǎng)絡(luò)請求的任務(wù),沒法對正在進行網(wǎng)絡(luò)請求的Request進行cancel晰洒。

Volley核心源碼分析完之后朝抖,來解析下為什么Volley適合做數(shù)據(jù)量小、通信頻繁的網(wǎng)絡(luò)操作谍珊,不適合高并發(fā)治宣、不適合大文件上傳下載。
個人理解如下:

  • Volley默認(rèn)是4個常駐loop線程來并行執(zhí)行任務(wù)砌滞,整體并發(fā)性不高侮邀。因此數(shù)據(jù)量大而頻繁的任務(wù)會阻塞隊列中排隊的任務(wù)。
  • Volley常駐loop線程帶來的好處是在通信頻繁的網(wǎng)絡(luò)操作場景下能減少線程創(chuàng)建銷毀的開銷贝润。
  • Volley在執(zhí)行網(wǎng)絡(luò)請求過程中會一個網(wǎng)絡(luò)請求數(shù)據(jù)的內(nèi)存byte[]轉(zhuǎn)換绊茧,對于頻繁的網(wǎng)絡(luò)請求,會頻繁創(chuàng)建和銷毀byte[]题暖,為了提高性能按傅,volley定義了一個byte[]緩沖池,即ByteArrayPool 胧卤。它的問題在于大文件上傳下載會擠大緩沖池的byte[]內(nèi)存占用唯绍,從而造成內(nèi)存壓力。
三枝誊、Volley線程池方案分析
Volley線程池方案

Volley線程池整體架構(gòu)有幾個關(guān)鍵點:

  • PriorityBlockingQueue是一個支持優(yōu)先級的無界阻塞隊列况芒,默認(rèn)容量11,擴容規(guī)則:舊容量小于64則翻倍,舊容量大于64則增加一半绝骚。雖然比LinkedBlockingQueue初始化默認(rèn)容量為Integer.MAX_VALUE要強點耐版, 但是理論上還是可以一直添加request,直到系統(tǒng)資源耗盡压汪,因此隊列阻塞任務(wù)過多有oom風(fēng)險粪牲。

  • 線程本身是一個while(true)的死循環(huán),通過queue.take()來做阻塞止剖,queue中添加了任務(wù)腺阳,線程會馬上take任務(wù)來處理,處理流程包括:1)performRequest:通過HttpUrlConnection/HttpClient執(zhí)行網(wǎng)絡(luò)請求穿香。2)parseNetworkResponse:對網(wǎng)絡(luò)請求返回的response進行解析封裝亭引。3)postResponse:分發(fā)response出去。也就是說一個線程執(zhí)行任務(wù)的生命周期是從獲取任務(wù)到分發(fā)response出去皮获,一個任務(wù)執(zhí)行完成才重新去queue中take新任務(wù)焙蚓,所以上一個任務(wù)中網(wǎng)絡(luò)請求、數(shù)據(jù)解析耗時都有可能造成后面的任務(wù)delay洒宝。

  • 默認(rèn)1個cache線程和4個network線程购公,常駐線程5個。常駐線程目的是在通信頻繁場景下能減少線程頻繁創(chuàng)建消耗的開銷待德。

四君丁、Volley線程池優(yōu)化思考

首先Volley本身設(shè)計方案是否有優(yōu)化空間?
有些項目會按請求類型來構(gòu)造多個RequestQueue來處理任務(wù)将宪,增加并發(fā)性绘闷。
例如:
常見的網(wǎng)絡(luò)請求類型包括:普通數(shù)據(jù)請求、埋點及時上報较坛、大文件上傳下載印蔗。那么就做三個RequestQueue,然后根據(jù)業(yè)務(wù)的并發(fā)性來配置合理的常駐線程數(shù)丑勤,比如:4:4:1华嘹。
優(yōu)點是:增大了并發(fā)性,也降低了不同業(yè)務(wù)請求之間相互影響的概率法竞。比如扎堆的數(shù)據(jù)上報和大文件上傳下載阻塞頁面UI刷新數(shù)據(jù)的網(wǎng)絡(luò)請求耙厚。
缺點:Volley的線程池方案增加并發(fā)性需要以犧牲內(nèi)存為代價,畢竟是固定的常駐線程岔霸。JDK1.5+ -Xss配置是1M薛躬,因此一個線程的開銷差不多是1M內(nèi)存。對于低內(nèi)存設(shè)備來說還是不太友好的呆细。之前就遇到過如下問題:

java.lang.OutOfMemoryError: thread creation failed at java.lang.VMThread.create(Native Method)
  at java.lang.Thread.start(Thread.java:1050)
  at com.mgtv.tv.ad.library.network.android.volley.RequestQueue.start(RequestQueue.java:7)
  at com.mgtv.tv.ad.library.network.android.volley.toolbox.Volley.newRequestQueue(Volley.java:10)
  at com.mgtv.tv.ad.library.network.android.volley.toolbox.Volley.newRequestQueue(Volley.java:11)

所以這里考慮用線程池替換Volley固定數(shù)量常駐loop線程方案型宝。主要有兩種線程池方案可供參考:

4.1 Okhttp線程池方案

private int maxRequests = 64;//最大并發(fā)數(shù)。
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();//等待任務(wù)
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();//執(zhí)行任務(wù)

executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
   new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));

okhttp使用的是類CacheThreadPool方案,無核心線程趴酣,純非核心線程梨树。線程池配置上非核心線程數(shù)量為:Integer.MAX_VALUE,但實際是通過maxRequests來從代碼層面做了限制岖寞。在不滿足最大請求數(shù)時抡四,任務(wù)直接入running隊列,被執(zhí)行慎璧。超過最大請求數(shù)床嫌,任務(wù)暫時先入ready隊列等待,當(dāng)有任務(wù)結(jié)束時胸私,會嘗試同步ready隊列任務(wù)到running隊列中,并執(zhí)行鳖谈。

該方案并發(fā)可控岁疼,線程完全可回收,線程池本身使用的是SynchronousQueue缆娃,該隊列本身不存儲元素捷绒,因此任務(wù)排隊需求需要單獨實現(xiàn)。

那么這里有個問題贯要,為什么要單獨實現(xiàn)隊列暖侨?

線程池執(zhí)行流程

類CacheThreadPool方案核心在于要及時滿足任務(wù)的調(diào)度,它是并發(fā)性最好的線程池方案崇渗,但是同時它自身也存在明顯問題字逗,因為線程創(chuàng)建是無限的,很容易造成oom, 所以在移動端是需要限制并發(fā)數(shù)量的宅广,怎么限制呢葫掉?
首先SynchronousQueue是不能替換的,它是高并發(fā)的保證跟狱,它自身不存儲元素俭厚,只是做一個阻塞,那如果改成ArrayBlockingQueue呢驶臊?按線程池執(zhí)行流程挪挤,在沒有核心線程情況下,會先入隊关翎,隊滿再創(chuàng)建非核心線程來執(zhí)行任務(wù)扛门,這樣肯定不行。所以比較好的辦法是從外部單獨實現(xiàn)隊列

看看Okhttp如何單獨實現(xiàn)隊列:
okhttp3/Dispatcher.java

synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

發(fā)起一個網(wǎng)絡(luò)請求任務(wù)笤休,沒到最大請求數(shù)直接執(zhí)行尖飞,并添加到runningAsyncCalls中,否則進readyAsyncCalls中排隊。

 void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }

一個任務(wù)結(jié)束會觸發(fā)finish,這里runningAsyncCalls先移除當(dāng)前任務(wù),然后通過promoteCalls將readyAsyncCalls的任務(wù)同步到runningAsyncCalls中來,如下所示:

 private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

借用兩個Deque來從外部限制最大并發(fā)線程數(shù)嘶朱,非常簡單珍促,值得借鑒學(xué)習(xí)。

4.2 自定義線程池方案

mPool = new ThreadPoolExecutor(CORE_POOL_SIZE, 
                            MAXIMUM_POOL_SIZE, //CORE_POOL_SIZE *2
                            KEEP_ALIVE_TIME,
                            TimeUnit.SECONDS,
                            new LinkedBlockingQueue<Runnable>(QUEUE_CAPACITY),//限制queue容量
                            new ThreadPoolExecutor.CallerRunsPolicy()//queue滿之后的飽和策略

該方案是核心線程+非核心線程的線程池拆座,整體方案還是屬于IO密集型線程池配置,常駐的核心線程避免線程頻繁創(chuàng)建和銷毀的內(nèi)存開銷,非核心線程提供一定的并發(fā)性拓展酱畅,總線程數(shù)有限,且非核心線程可回收江场,因此在核心線程數(shù)量不大的情況下內(nèi)存開銷也同樣可控纺酸,過多的任務(wù)只能入LinkedBlockingQueue隊列等待執(zhí)行,入隊任務(wù)過多會導(dǎo)致OOM址否,因此需要限制隊列容量以及配合拒絕策略餐蔬,這里同樣也可以用ArrayBlockingQueue<Runnable>(QUEUE_CAPACITY)。

注:ArrayBlockingQueue 與LinkedBlockingQueue區(qū)別:

  • 有界性:ArrayBlockingQueue有界佑附,LinkedBlockingQueue可有界可無界樊诺;
  • 數(shù)據(jù)結(jié)構(gòu):ArrayBlockingQueue采用的是數(shù)組作為數(shù)據(jù)存儲容器,而LinkedBlockingQueue采用的則是以Node節(jié)點作為連接對象的鏈表音同。由于ArrayBlockingQueue采用的是數(shù)組的存儲容器词爬,因此在插入或刪除元素時不會產(chǎn)生或銷毀任何額外的對象實例,而LinkedBlockingQueue則會生成一個額外的Node對象权均。這可能在長時間內(nèi)需要高效并發(fā)地處理大批量數(shù)據(jù)的時顿膨,對于GC可能存在較大影響。
  • 并發(fā)性:ArrayBlockingQueue添加刪除是用的一把鎖螺句,而LinkedBlockingQueue這兩個操作是分別加鎖的虽惭。在高并發(fā)情況下,LinkedBlockingQueue的生產(chǎn)者和消費者可以并行地操作隊列中的數(shù)據(jù)蛇尚,并發(fā)性更好芽唇。

4.3 框架外部傳入項目已有線程池復(fù)用
這個方案也考慮過,好處是減少線程池數(shù)量取劫,本身也是一種線程開銷的優(yōu)化匆笤,但是這樣會讓網(wǎng)絡(luò)請求任務(wù)和項目中各中各樣的任務(wù)糅雜在一起,還是有互相影響到的問題谱邪。另外也不好為一類工作線程統(tǒng)一命名炮捧。

總體來說,如果Volley要做線程池改造的話惦银,可以考慮引入Okhttp線程池方案:


Volley線程池優(yōu)化后方案

換了這種方案之后咆课,Volley的并發(fā)痛點能得到緩解末誓,尤其是極端情況下:
例如:
項目中數(shù)據(jù)上報很頻繁且大部分都是及時上報,某些頁面數(shù)據(jù)請求接口又比較多书蚪,因此進入該類頁面會觸發(fā)比較多的網(wǎng)絡(luò)請求喇澡,因此頻繁切換此類頁面并發(fā)性要求相對來說會比較高,一旦遇到比如:任務(wù)數(shù)據(jù)量大殊校、網(wǎng)絡(luò)超時晴玖、弱網(wǎng)環(huán)境,Volley的表現(xiàn)就很糟糕为流。

在網(wǎng)絡(luò)超時場景下:

request:
1970-01-01 08:22:29.582 4295-4295/com.xxx.xxx D/NetWorkVolleyImpl: requestID:185031429
1970-01-01 08:22:30.085 4295-4295/com.xxx.xxx I/NetWorkVolleyImpl: requestID:164326795
1970-01-01 08:22:30.203 4295-4295/com.xxx.xxx I/NetWorkVolleyImpl: requestID:58808167
1970-01-01 08:22:30.713 4295-4295/com.xxx.xxx I/NetWorkVolleyImpl: requestID:262858173
1970-01-01 08:22:30.986 4295-4295/com.xxx.xxx D/NetWorkVolleyImpl: requestID:123129723
1970-01-01 08:22:30.992 4295-4295/com.xxx.xxx I/NetWorkVolleyImpl: requestID:114437590
1970-01-01 08:22:30.999 4295-4295/com.xxx.xxx D/NetWorkVolleyImpl: requestID:219127620
1970-01-01 08:22:31.001 4295-4295/com.xxx.xxx I/NetWorkVolleyImpl: requestID:102453602
1970-01-01 08:22:31.033 4295-4295/com.xxx.xxx I/NetWorkVolleyImpl: requestID:86987358

response:
1970-01-01 08:22:34.739 4295-4295/com.xxx.xxx D/NetWorkVolleyImpl: requestID:185031429呕屎,error:java.net.SocketTimeoutException: connect timed out
1970-01-01 08:22:31.716 4295-4295/com.xxx.xxx I/NetWorkVolleyImpl: requestID:164326795,error:java.net.SocketTimeoutException: connect timed out
1970-01-01 08:22:45.334 4295-4295/com.xxx.xxx I/NetWorkVolleyImpl: requestID:58808167敬察,error:java.net.SocketTimeoutException: connect timed out
1970-01-01 08:22:32.459 4295-4295/com.xxx.xxx I/NetWorkVolleyImpl: requestID:262858173秀睛,error:java.net.SocketTimeoutException: connect timed out
1970-01-01 08:22:39.696 4295-4295/com.xxx.xxx D/NetWorkVolleyImpl: requestID:123129723,error:java.net.SocketTimeoutException: connect timed out
1970-01-01 08:22:46.832 4295-4295/com.xxx.xxx I/NetWorkVolleyImpl: requestID:114437590莲祸,error:java.net.SocketTimeoutException: connect timed out
1970-01-01 08:22:51.034 4295-4295/com.xxx.xxx D/NetWorkVolleyImpl: requestID:219127620琅催,error:java.net.SocketTimeoutException: connect timed out
1970-01-01 08:22:47.484 4295-4295/com.xxx.xxx I/NetWorkVolleyImpl: requestID:102453602,error:java.net.SocketTimeoutException: connect timed out
1970-01-01 08:22:50.351 4295-4295/com.xxx.xxx I/NetWorkVolleyImpl: requestID:86987358虫给,error:java.net.SocketTimeoutException: timeout

超時時間設(shè)置是5s,很明顯看到侠碧,在過了并發(fā)之后抹估,后續(xù)的超時反饋越來越慢。超時時間是從任務(wù)獲取到線程執(zhí)行網(wǎng)絡(luò)請求開始計算的弄兜,因此如果是隊列阻塞狀態(tài)药蜻,上一個任務(wù)耗時會delay到當(dāng)前任務(wù)的執(zhí)行,因此在超時回調(diào)上會是一個累加狀態(tài)替饿,讓后續(xù)的網(wǎng)絡(luò)請求遲遲沒有回調(diào)语泽,有些頁面無法刷新UI,一直處于黑屏或者loading狀態(tài)视卢。該問題在okhttp的線程池中得到很大緩解踱卵。

那有人就問了,為什么不直接換okhttp呢据过?因為這篇文章寫的是Volley惋砂,它畢竟也是一個非常經(jīng)典的網(wǎng)絡(luò)框架,有很多設(shè)計思想是值得學(xué)習(xí)的绳锅,同時嘗試解決老框架的痛點問題西饵,也是一種自我提升。

好了鳞芙,就寫到這眷柔,文章有不對之處還望批評指正期虾,如果有更好的想法也歡迎溝通交流。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載驯嘱,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者镶苞。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市宙拉,隨后出現(xiàn)的幾起案子宾尚,更是在濱河造成了極大的恐慌,老刑警劉巖谢澈,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件煌贴,死亡現(xiàn)場離奇詭異,居然都是意外死亡锥忿,警方通過查閱死者的電腦和手機牛郑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來敬鬓,“玉大人淹朋,你說我怎么就攤上這事《ご穑” “怎么了础芍?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長数尿。 經(jīng)常有香客問我仑性,道長,這世上最難降的妖魔是什么右蹦? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任诊杆,我火速辦了婚禮,結(jié)果婚禮上何陆,老公的妹妹穿的比我還像新娘晨汹。我一直安慰自己,他們只是感情好贷盲,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布淘这。 她就那樣靜靜地躺著,像睡著了一般晃洒。 火紅的嫁衣襯著肌膚如雪慨灭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天球及,我揣著相機與錄音氧骤,去河邊找鬼。 笑死吃引,一個胖子當(dāng)著我的面吹牛筹陵,可吹牛的內(nèi)容都是我干的刽锤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼朦佩,長吁一口氣:“原來是場噩夢啊……” “哼并思!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起语稠,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤宋彼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后仙畦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體输涕,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年慨畸,在試婚紗的時候發(fā)現(xiàn)自己被綠了莱坎。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡寸士,死狀恐怖檐什,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情弱卡,我是刑警寧澤乃正,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站婶博,受9級特大地震影響烫葬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜凡蜻,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望垢箕。 院中可真熱鬧划栓,春花似錦、人聲如沸条获。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽帅掘。三九已至委煤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間修档,已是汗流浹背碧绞。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吱窝,地道東北人讥邻。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓迫靖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親兴使。 傳聞我的和親對象是個殘疾皇子系宜,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 一、網(wǎng)絡(luò)請求核心 1.1 Network Volley與網(wǎng)絡(luò)請求相關(guān)的接口有兩個: 1.2 HttpStack 1...
    澤毛閱讀 1,299評論 0 1
  • 整體的執(zhí)行流程 執(zhí)行流程這一塊我們基本上可以分為兩大塊1. Volley.newRequestQueue(Cont...
    azu_test閱讀 206評論 0 0
  • 抽時間看了下Volley的源碼发魄,感覺這個框架還是很不錯的盹牧,這里對其源碼進行分析。 GitHub鏈接 主要從以下幾個...
    木有粗面_9602閱讀 231評論 0 0
  • Volley 的中文翻譯為“齊射励幼、并發(fā)”汰寓,是在2013年的Google大會上發(fā)布的一款A(yù)ndroid平臺網(wǎng)絡(luò)通信庫...
    萬劍閱讀 1,277評論 0 3
  • 注:本文轉(zhuǎn)自http://codekk.com/open-source-project-analysis/deta...
    Ten_Minutes閱讀 1,287評論 1 16