關于 Volley 的最簡單講解,你想知道的都在這兒了

RakeeBwk 7年前發布 | 30K 次閱讀 Volley 網絡技術

眾所周知,Volley是google在2013年開源的一款異步異步異步http網絡請求庫,采用Volley進行網絡請求非常簡單。那么怎樣個異步法?最簡單的解釋就是:

使用Volley你不用再像原生的HttpUrlConnection一樣new Thread(new runable());新開工作線程這些事Volley都幫你做了。

Volley為http請求提供了如下的保障:

  1. response緩存。Volley內部維護了一個cache,用來對網絡請求的結果進行緩存,再次發起該請求時會首先訪問cache,當請求內容和上次相同時會直接從cache返回數據,避免重復的網絡請求。
  2. request請求隊列,Volley會將通過request.add()方法添加的request放置在請求隊列,并從該隊列中逐一取出請求進行請求。并支持請求優先級的設置。
  3. response分發。Volley采用異步的請求方式,請求成功并獲得返回值時會在工作線程中對結果進行解析,并將response分發到主線程,意味著在Volley的回調方法中,代碼直接運行在主線程,方便了UI更新等操作。這一點和okHttp有很大區別,okHttp異步方式的回調仍然運行在工作線程。這樣不同的實現也各有各的好處。
  4. 支持request的自定義,Volley原生request包括了JsonRequest、JsonArrayReuqest、ImageRequest和StringResquest四種,分別用于json對象、json數組以及字符串類型的返回值請求,除此以外,Volley允許繼承Request類實現自己的請求,比如你可以實現返回一個JavaBean的請求。

Volley適合頻繁但數據量不大的網絡請求,例如常見API調用,并不適合大文件的下載。Volley將整個response加載到內存并進行操作(可以是解析等操作)大文件可能會引起OOM

官網對Volley的介紹

Volley簡單使用

最簡單Volley的使用包括三個步驟:

  1. 創建RequestQueue
  2. 創建Request
  3. 將Request添加到RequestQueue

    RequestQueue requestQueue = Volley.newRequestQueue(context);
    StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {
        @Override
        public void onResponse(String response) {
         //這里可以更新UI
        }
            }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
    
        }
    });
    requestQueue.add(stringRequest);

本文的重點是分析Volley的工作流程,各種請求的具體使用可以參考以下鏈接:

Volley原理解析

先來一張Volley工作流程圖鎮樓:

Volley中包括以下幾種線程:

  • 緩沖讀取線程(cacheDispatcher)——用于從緩沖中獲取數據(繼承自Thread)
  • 網絡請求線程(networkDispatcher)——用于發送網絡請求來獲取數據(繼承自Thread)
  • response分發線程(mResponsePoster)——用于將請求結果(可能來自cache和網絡)分發到主線程(Executor對象)

1.RequestQueue的創建

既然使用Volley首先創建的是RequestQueue對象,那我們就從Volley.newRequestQueue(context)方法開始。該靜態方法通過調用重載的靜態方法,內部調用構造方法創建一個requestQueue對象:

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) {
        //API9及以上則使用HttpUrlConnection
        if (Build.VERSION.SDK_INT >= 9) {
            stack = new HurlStack();
        } else {
            //小于9則使用HttpClient
            // Prior to Gingerbread, HttpUrlConnection was unreliable.
            stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
        }
    }
    //用于后面的網絡請求
    Network network = new BasicNetwork(stack);
    //根據設置的緩存目錄創建請求隊列
    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
    //啟動請求隊列
    queue.start();
    return queue;
}

從上面的代碼可以發現,Volley內部是采用HttpUrlConnection或者HttpClient進行網絡請求的(HttpClient已經廢棄不再維護)。

我們重點關注一下這一行代碼:

RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);

以上代碼創建了一個新的請求隊列。并傳入DiskBasedCache(cacheDir)來根據相應目錄初始化緩存。

跟蹤上述構造方法,發現調用了另一個三個參數的構造方法:

public RequestQueue(Cache cache, Network network) {
    //調用三個參數的構造方法
    this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}

DEFAULT_NETWORK_THREAD_POOL_SIZE是聲明的常量=4,所以我們知道Volley的網絡請求線程數量默認為4個。上述構造方法調用了四個參數的構造方法:

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
    //調用四個參數對的構造方法
    this(cache, network, threadPoolSize,
            new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}

注意以上方法的最后一個參數,這是一個綁定了主線程handler的ExecutorDelivery對象,用處在后文進行說明

四個參數的構造方法的實現:

public RequestQueue(Cache cache, Network network, int threadPoolSize,
        ResponseDelivery delivery) {
    mCache = cache;
    mNetwork = network;
    mDispatchers = new NetworkDispatcher[threadPoolSize];
    mDelivery = delivery;
}

這樣requestqueue就創建完成,

2.網絡請求的執行

創建的requestqueue.queue.start()方法在創建的最后一行得到了調用。

queue.start()的具體實現:

public void start() {
    stop();  // 確保現有的dispatcher停止工作
    // 創建cacheDispatcher并啟動
    mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
    mCacheDispatcher.start();

    //根據線程池(mDispatchers)的大小創建網絡請求線程。
    for (int i = 0; i < mDispatchers.length; i++) {
        NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,mCache, mDelivery);
        mDispatchers[i] = networkDispatcher;
        //按創建順序啟動對應的線程
        networkDispatcher.start();
    }
}

正如代碼的注釋一樣,先創建并啟動緩存線程用來讀取緩存內容,再創建網絡請求線程用于網絡請求。mDispatchers為線程數組,默認大小為4,networkDispatcher網絡請求執行線程。

我們重點分析一下網絡請求線程的創建:

NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,mCache, mDelivery);

我們著重關注一下mDelivery對象,它是一個綁定了主線程handler的ExecutorDelivery對象

由于NetworkDispatcher本身是一個線程,調用start()方式實際會執行它的run()方法,我們來到run()方法內部:

@Override
public void run() {
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    while (true) {
        long startTimeMs = SystemClock.elapsedRealtime();
        Request<?> request;
        try {
               //從請求隊列中取出網絡請求
            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 (request.isCanceled()) {
                request.finish("network-discard-cancelled");
                continue;
            }

            addTrafficStatsTag(request);

            // 發送網絡請求
            NetworkResponse networkResponse = mNetwork.performRequest(request);
            ...
             // 在子線程解析返回結果
            Response<?> response = request.parseNetworkResponse(networkResponse);
               ...
            省略的代碼用于請求成功的結果進行緩存
            ...
            //將請求的結果發送出去
            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);
        }
    }
}

代碼比較多,由于主要研究網絡請求部分,上述代碼的請求結果緩存部分我進行了省略,你可以自行查看源碼。

上述代碼的核心在于以下三個方法:

mNetwork.performRequest()
Response<?> response = request.parseNetworkResponse()
mDelivery.postResponse(request, response);

避免代碼過多我就不貼源代碼了,作用:

  • mNetwork.performRequest()——最終調用HurlStack().performRequest()方法采用httpUrlConnection的方式進行網絡請求。
  • request.parseNetworkResponse()——根據parseNetworkResponse()的具體邏輯實現在子線程對結果進行解析( 這也是自定義Request需要實現的方法之一,比如需要返回javaBean就需要該方法中進行解析 )
  • mDelivery.postResponse(request, response)——將解析結果分發到 主線程

3.Response的分發

最后我們來看看Response的分發。

mDelivery是一個ExecutorDelivery對象,內部存在一個成員對象叫做mResponsePoster(為Executor類型),最終調用這個mResponsePoster.execute()方法對response進行分發。

具體過程如下:

mDelivery.postResponse(request, response)方法怎么把結果分發到主線程:

該方法實現如下,調用了重載的postResponse(request, response, null)方法:

public void postResponse(Request<?> request, Response<?> response) {
    postResponse(request, response, null);
}

源碼如下:

public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
    request.markDelivered();
    request.addMarker("post-response");
    //執行分發
    mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}

mResponsePoster也是一個Executor對象,通過execute()方法來執行Runnable對象的run()方法:它的execute()方法實現如下:

public ExecutorDelivery(final Handler handler) {
    // 采用handler.post()方法將runnable對象運行在主線程
    mResponsePoster = new Executor() {
        @Override
        public void execute(Runnable command) {
            handler.post(command);
        }
    };
}

可以看到這里使用了handler.post()來使得runnable對象運行在與該handler所綁定的線程中

那么這個handler是在哪里賦值的呢?

往上翻,那就是在前文中兩次注明的mDelivery對象,它是一個綁定了主線程handler的ExecutorDelivery對象,所以這里的handler是和主線程綁定的,在分發response的時候,我們的回調方法最終會運行在主線程。

ResponseDeliveryRunnable類的run()方法部分代碼實現如下:

// 分發請求結果或者錯誤消息
if (mResponse.isSuccess()) {
    mRequest.deliverResponse(mResponse.result);
} else {
    mRequest.deliverError(mResponse.error);
}

deliverResponse(mResponse.result)也是在自定義Request的時候可以覆蓋的方法。在Volley自帶的StringRequest類中,該方法實現如下:

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

這里就調用了我們在使用Volley創建StringRequest對象時設置的監聽器的onResponse(reponse)方法。然后就可以在改方法中更新UI了。

Volley用到線程池了嗎?

直觀地看來,Volley是沒有用到線程池的,它采用的是一個默認長度為4的線程數組(名為mDispatcher),在具體的網絡請求線程(networkDispatcher)的run()方法中,Volley采用了如下的結構:

public void run(){
    while(true){
    //從請求隊列中取出請求
    requestQueue.take();
    ...
    }
}

這個死循環使得run()方法永遠不會結束,對應的線程也就不會被銷毀,而當requestQueue.take()沒有獲取到請求的時候(即請求隊列已空),該線程就會被阻塞而暫時停止運行。有新的請求的時候又得到運行進而發送新的網絡請求。

可以發現,Volley沒有使用線程池來管理網絡請求線程,而是采用死循環的方式來避免了不斷地創建、銷毀線程帶來的開銷,達到了和線程池同樣的效果。

最后

這里只是簡單梳理了一遍Volley的網絡請求流程,對緩存部分沒有作過多的講解,若想了解更多,還請讀者自行查看源碼。

 

來自:http://www.distancelin.cn/2017/02/28/你想知道的volley使用及原理/

 

 本文由用戶 RakeeBwk 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!