Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> (干貨) Android Volley框架源碼詳細解析

(干貨) Android Volley框架源碼詳細解析

編輯:關於Android編程

前言

  經常接觸Android網絡編程的我們,對於Volley肯定不陌生,但我們不禁要問,對於Volley我們真的很了解嗎?Volley的內部是怎樣實現的?為什麼幾行代碼就能快速搭建好一個網絡請求?我們不但要知其然,也要知其所以然,抱著這樣的目的,本文主要詳細講述Volley的源碼,對內部流程進行詳細解析。

Part 1.從RequestQueue說起

  (1)還記得搭建請求的第一步是什麼嗎?是新建一個請求隊列,比如說這樣:

RequestQueue queue = Volley.newRequestQueue(context)

  雖然表面上只是一句代碼的事情,但是背後做了很多准備工作,我們追蹤源碼,找到Volley#newRequestQueue()方法:

 /**
   * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
   * You may set a maximum size of the disk cache in bytes.
   *
   * @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.
   * @param maxDiskCacheBytes the maximum size of the disk cache, in bytes. Use -1 for default size.
   * @return A started {@link RequestQueue} instance.
   */
public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
    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) {
    }

     /**
       * 根據不同的系統版本號實例化不同的請求類,如果版本號小於9,用HttpClient
       * 如果版本號大於9,用HttpUrlConnection
       */
    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));
        }
    }
      //把實例化的stack傳遞進BasicNetwork,實例化Network
    Network network = new BasicNetwork(stack);
    RequestQueue queue;
    if (maxDiskCacheBytes <= -1){
       // No maximum size specified
       //實例化RequestQueue類 
       queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
    }
    else {
       // Disk cache size specified
       queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
    }
     //調用RequestQueue的start()方法
    queue.start();
    return queue;
}

  首先我們看參數,有三個,實際上我們默認使用了只有一個參數context的方法,這個是對應的重載方法,最終調用的是三個參數的方法,context是上下文環境;stack代表需要使用的網絡連接請求類,這個一般不用設置,方法內部會根據當前系統的版本號調用不同的網絡連接請求類(HttpUrlConnection和HttpClient);最後一個參數是緩存的大小。接著我們看方法內部,這裡先創建了緩存文件,然後根據不同的系統版本號實例化不同的請求類,用stack引用這個類。接著又實例化了一個BasicNetwork,這個類在下面會說到。然後到了實際實例化請求隊列的地方:new RequestQueue(),這裡接收兩個參數,分別是緩存和network(BasicNetwork)。實例化RequestQueue後,調用了start()方法,最後返回這個RequestQueue。
  (2)我們跟著RequestQueue看看它的構造器做了哪些工作:

/**
     * Creates the worker pool. Processing will not begin until {@link #start()} is called.
     *
     * @param cache A Cache to use for persisting responses to disk
     * @param network A Network interface for performing HTTP requests
     * @param threadPoolSize Number of network dispatcher threads to create
     * @param delivery A ResponseDelivery interface for posting responses and errors
     */
    public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        //實例化網絡請求數組,數組大小默認是4
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

    public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                //ResponseDelivery是一個接口,實現類是ExecutorDelivery
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }

    public RequestQueue(Cache cache, Network network) {
        this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
    }

  可以看到,把傳遞過來的cache和network作為變量傳遞給了四個參數的構造器,在這裡,初始化了RequestQueue的幾個成員變量:mCache(文件緩存)、mNetwork(BasicNetwork實例)、mDispatchers(網絡請求線程數組)、以及mDelivery(派發請求結果的接口),具體意義可看上面的注解。
  (3)構造完RequestQueue後,從(1)可知,最後調用了它的start()方法,我們來看看這個方法,RequestQueue#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();
        }
    }

  首先實例化了CacheDispatcher,CacheDispatcher類繼承自Thread,接著調用了它的start()方法,開始了一條新的緩存線程。接著是一個for循環,根據設置的mDispatchers數組大小來開啟多個網絡請求線程,默認是4條網絡請求線程。
  到目前為止,Volley.newRequestQueue()方法完成了,即我們的網絡請求第一步,建立請求隊列完成。
  先小結一下:建立請求隊列所做的工作是,創建文件緩存(默認),實例化BasicNetwork,實例化Delivery用於發送線程請求,創建一條緩存線程和四條網絡請求線程(默認)並運行。

Part 2.網絡請求的實現原理

  在創建完請求隊列後,接著就是建立一個請求,請求的方式可以是StringRequest、JsonArrayRequest或者ImageRequest等,那麼這些請求的背後原理是什麼呢?我們拿最簡單的StringRequest來說,它繼承自Request,而Request則是所有請求的父類,所以說如果你要自定義一個網絡請求,就應該繼承自Request。接下來我們看看StringRequest的源碼,因為不管Request的子類是什麼,大體的實現思路都是一致的,所以我們弄懂了StringRequest,那麼對於其他的請求類的理解是相通的。如下是StringRequest源碼:

public class StringRequest extends Request {
    private Listener mListener;

    public StringRequest(int method, String url, Listener listener,
            ErrorListener errorListener) {
        super(method, url, errorListener);
        mListener = listener;
    }
    ...
    @Override
    protected void deliverResponse(String response) {
        if (mListener != null) {
            mListener.onResponse(response);
        }
    }

    @Override
    protected Response parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException e) {
            parsed = new String(response.data);
        }
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
    }
}

  源碼並不長,我們主要關注的是deliverResponse方法和parseNetworkResponse。可以看出,這兩個方法都是重寫的,我們翻看父類Request的對應方法,發現是抽象方法,說明這兩個方法在每一個自定義的Request中都必須重寫。這裡簡單說說這兩個方法的作用。先看deliverResponse方法:它內部調用了mListener.onResponse(response)方法,而這個方法正是我們在寫一個請求的時候,添加的listener所重寫的onResponse方法,也就是說,響應成功後在這裡調用了onResponse()方法。接著看pareNetworkResponse方法,可以看出這裡主要是對response響應做出一些處理。可以對比一下不同請求類的這個方法,都會不同的,所以說,這個方法是針對不同的請求類型而對響應做出不同的處理。比如說,如果是StringRequest,則將響應包裝成String類型;如果是JsonObjectRequest,則將響應包裝成JsonObject。那麼現在應該清楚了:對於想要得到某一種特殊類型的請求,我們可以自定義一個Request,重寫這兩個方法即可。
  這裡**小結一下:**Request類做的工作主要是初始化一些參數,比如說請求類型、請求的url、錯誤的回調方法;而它的任一子類重寫deliverResponse方法來實現成功的回調,重寫parseNetworkResponse()方法來處理響應數據;至此,一個完整的Request請求搭建完畢。

Part 3.添加請求

  前面已經完成了請求隊列的創建,Request請求的創建,那麼接下來就是把請求添加進隊列了。我們看RequestQueue#add()源碼

/**
 * Adds a Request to the dispatch queue.
 * @param request The request to service
 * @return The passed-in request
 */
public  Request add(Request request) {
    // Tag the request as belonging to this queue and add it to the set of current requests.
    //標記當前請求,表示這個請求由當前RequestQueue處理
    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.
            //如果有相同請求正在處理,那麼把這個請求放進mWaitingRequest中,等待。
            Queue> 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中,同時也放進mCacheQueue緩存隊列中
            //這代表這個請求已經開始在緩存線程中運行了
            mWaitingRequests.put(cacheKey, null);
            mCacheQueue.add(request);
        }
        return request;
    }
}

  結合相應的注釋,我們得出如下結論:在這個add方法中,主要判斷一個Request請求是否可以緩存(默認是可以緩存的),如果不可以則直接添加到網絡請求隊列開始網絡通信;如果可以,則進一步判斷當前是否有相同的請求正在進行,如果有相同的請求,則讓這個請求排隊等待,如果沒有相同的請求,則直接放進緩存隊列中。如果對此還有什麼疑問,可以看下面的流程圖(圖片來自網絡):
RequestQueue#add()方法流程圖

Part 4.緩存線程

  在part1的最後實例化了緩存線程並開始運行,一直處於等待狀態,而上面把請求添加進了緩存線程,此時緩存線程就開始真正的工作了。我們來看緩存線程的源碼,主要看它的run()方法,CacheDispatcher#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();

    Request request;
    while (true) {
        // release previous request object to avoid leaking request object when mQueue is drained.
        request = null;
        try {
            // Take a request from the queue.
            //從緩存隊列中取出請求
            request = mCacheQueue.take();
        } ...
        try {
            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");
            //先將響應的結果包裝成NetworkResponse,然後調用Request子類的
            //parseNetworkResponse方法解析數據
            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.
                //調用ExecutorDelivey#postResponse方法
                mDelivery.postResponse(request, response);
            } else {
                ....
            }
        } catch (Exception e) {
            VolleyLog.e(e, "Unhandled exception %s", e.toString());
        }
    }
}

  在run()方法中,我們可以看到最開始有一個while(true)循環,表示它一直在等待緩存隊列的新請求的出現。接著,先判斷這個請求是否有對應的緩存結果,如果沒有則直接添加到網絡請求隊列;接著再判斷這個緩存結果是否過期了,如果過期則同樣地添加到網絡請求隊列;接下來便是對緩存結果的處理了,我們可以看到,先是把緩存結果包裝成NetworkResponse類,然後調用了Request的parseNetworkResponse,這個方法我們在part2說過,子類需要重寫這個方法來處理響應數據。最後,把處理好的數據post到主線程,這裡用到了ExecutorDelivery#postResponse()方法,下面會分析到。
  小結:CacheDispatcher線程主要對請求進行判斷,是否已經有緩存,是否已經過期,根據需要放進網絡請求隊列。同時對相應結果進行包裝、處理,然後交由ExecutorDelivery處理。這裡以一張流程圖顯示它的完整工作流程:

CacheDispatcher線程工作流程

Part 5.網絡請求線程

  上面提到,請求不能緩存、緩存結果不存在、緩存過期的時候會把請求添加進請求隊列,此時一直等待的網絡請求線程由於獲取到請求,終於要開始工作了,我們來看NetworkDispatcher#run()方法

@Override
    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 {
                // Take a request from the queue.
                request = mQueue.take();
            } ...

            try {
                ...
                // Perform the network request.
                //調用BasicNetwork實現類進行網絡請求,並獲得響應 1
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                ...

                // Parse the response here on the worker thread.
                //對響應進行解析
                Response response = request.parseNetworkResponse(networkResponse);
                request.addMarker("network-parse-complete");
                ...
                request.markDelivered();
                mDelivery.postResponse(request, response);

                // Write to cache if applicable.
                //將響應結果寫進緩存
                if (request.shouldCache() && response.cacheEntry != null) {    
                   mCache.put(request.getCacheKey(), response.cacheEntry);   
                   request.addMarker("network-cache-written");
                }
            } ...
        }
    }

  源碼做了適當的刪減,大體上和CacheDispatcher的邏輯相同,這裡關注①號代碼,這裡調用了BasicNetwork#perfromRequest()方法,把請求傳遞進去,可以猜測,這個方法內部實現了網絡請求的相關操作,那麼我們進去看看,BasicNetwork#perfromRequest()

@Override
    public NetworkResponse performRequest(Request request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            Map responseHeaders = Collections.emptyMap();
            try {
                // Gather headers.
                Map headers = new HashMap();
                addCacheHeaders(headers, request.getCacheEntry());

                httpResponse = mHttpStack.performRequest(request, headers);  // 1
                StatusLine statusLine = httpResponse.getStatusLine();
                int statusCode = statusLine.getStatusCode();

                responseHeaders = convertHeaders(httpResponse.getAllHeaders());
                // Handle cache validation.
                if (statusCode == HttpStatus.SC_NOT_MODIFIED) {

                    Entry entry = request.getCacheEntry();
                    if (entry == null) {
                        return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,  
                                responseHeaders, true,
                                SystemClock.elapsedRealtime() - requestStart);  // 2
                    }

                    // A HTTP 304 response does not have all header fields. We
                    // have to use the header fields from the cache entry plus
                    // the new ones from the response.
                    // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
                    entry.responseHeaders.putAll(responseHeaders);
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
                            entry.responseHeaders, true,
                            SystemClock.elapsedRealtime() - requestStart);
                }       
                ...
        }
    }

  我們主要看①號代碼,mHttpStack.performRequest();這裡調用了mHttpStack的performRequest方法,那麼mHttpStack是什麼呢?我們可以翻上去看看part1處實例化BasicNetwork的時候傳遞的stack值,該值就是根據不同的系統版本號而實例化的HttpStack對象(版本號大於9的是HurlStack,小於9的是HttpClientStack),由此可知,這裡實際調用的是HurlStack.performRequest()方法,方法的內部基本是關於HttpUrlConnection的邏輯代碼,這裡就不展開說了。可以這麼說:HurlStack封裝好了HttpUrlConnection,而HttpClientStack封裝了HttpClient。該方法返回了httpResponse,接著把這個響應交由②處處理,封裝成NetworkResponse對象並返回。在NetworkDispatcher#run()方法獲取返回的NetworkResponse對象後,對響應解析,通過ExecutorDelivery#postResponse()方法回調解析出來的數據,這個過程和CacheDispatcher相同。

Part 6.ExecutorDelivery 通知主線程

  在CacheDispatcher和NetworkDispatcher中最後都有調用到ExecutorDelivery#postResponse()方法,那麼這個方法到底是做什麼呢?由於緩存線程和網絡請求線程都不是主線程,所以主線程需要有“人”通知它網絡請求已經完成了,而這個“人”正是由ExecutorDelivery充當。在完成請求後,通過ExecutorDelivery#postResponse()方法,最終會回調到主線程中重寫的onResponse()方法。我們看看這個方法的源碼ExecutorDelivery#postResponse()

    @Override
    public void postResponse(Request request, Response response) {
        postResponse(request, response, null);
    }

    @Override
    public void postResponse(Request request, Response response, Runnable runnable) {
        request.markDelivered();
        request.addMarker("post-response");
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
    }

  在方法內部調用了mResponsePoster#execute()方法,那麼,這個mResponsePoster是在哪裡來的呢?其實這個成員變量是在ExecutorDelivery實例化的時候同時實例化的,而ExecutorDelivery則是在RequestQueue實例化的時候同時實例化的,讀者可以自行查看相應的構造方法,其實這些工作在par 1建立請求隊列的時候已經全部做好了。接著我們可以看以下代碼,ExecutorDelivery#ExecutorDelivery():

public ExecutorDelivery(final Handler handler) {
        // Make an Executor that just wraps the handler.
        //實例化Executor,並且重寫execute方法
        mResponsePoster = new Executor() {
            @Override
            public void execute(Runnable command) {
                //這裡獲取的handler是主線程的handler,可看part 1 (2)
                handler.post(command);
            }
        };
    }

  execute()方法接收一個Runnable對象,那麼我們回到上面的

mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));

  可以看出這裡實例化了一個ResponseDeliveryRunnable對象作為Runnable對象。而這裡的ResponseDeliveryRunnable則是當前類Executor的一個內部類,實現了Runnable接口,我們來看看:

/**
     * A Runnable used for delivering network responses to a listener on the
     * main thread.
     */
    @SuppressWarnings("rawtypes")
    private class ResponseDeliveryRunnable implements Runnable {
        private final Request mRequest;
        private final Response mResponse;
        private final Runnable mRunnable;

        //構造方法
        ...
        @Override
        public void run() {
            // If this request has canceled, finish it and don't deliver.
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }

            // Deliver a normal response or error, depending.
            if (mResponse.isSuccess()) {
                mRequest.deliverResponse(mResponse.result);  // 1
            } else {
                mRequest.deliverError(mResponse.error);
            }
            ...
            }
       }
    }

  在上面,①號代碼回調到了Request的deliverResponse方法,即此時通知了主線程,請求已經完成,此時在Request子類重寫的deliverResponse方法則會調用onResponse()方法,那麼我們的Activity就能方便調用解析好的請求結果了。
  到目前為止,關於Volley的源碼解析完畢。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved