Volley原始碼分析(二)

被程式碼淹沒的小夥子發表於2017-08-22

1.Volley原始碼分析(一)
2.Volley原始碼分析(二)
3.Volley原始碼分析(三)
4.XVolley-基於Volley的封裝的工具類

上一篇分析完了Volley.newRequestqueue()方法。方法最後執行到了requestqueue.start()方法

 /**
     * 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.
        //建立一個緩衝執行緒,並start
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        // Create network dispatchers (and corresponding threads) up to the pool size.
        //建立4個網路請求執行緒
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

首先看stop方法

/**
     * Stops the cache and network dispatchers.
     */
    public void stop() {
        if (mCacheDispatcher != null) {
            mCacheDispatcher.quit();
        }
        for (final NetworkDispatcher mDispatcher : mDispatchers) {
            if (mDispatcher != null) {
                mDispatcher.quit();
            }
        }
    }

可以看到,stop方法裡將所有的執行緒都quit掉了。

stop方法執行完畢後,會建立一個CacheDispatcher物件和NetworkDispatcher物件的陣列,這裡先提前說明一下,這兩個物件都是繼承的Thread類(後面會單獨分析這兩個類)再通過名字就很好理解了,這裡stop後會建立一個快取執行緒和4個網路執行緒,並呼叫start方法。

4個執行緒的來歷:可以看下RequestQueue是我們在建立RequestQueue時的構造方法,預設呼叫的是第一個構造方法,對應的DEFAULT_NETWORK_THREAD_POOL_SIZE=4

public RequestQueue(Cache cache, Network network) {
        /**
         * 預設執行緒池大小=4
         */
        this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
    }

    public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                //Looper.getMainLooper()對應主執行緒,所以請求成功後的介面回撥對應是在主執行緒中執行。
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }

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

分析完start方法,現在分析requestqueue的add方法。

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);
        //mCurrentRequest是一個HashSet,不是執行緒安全的,所以進行加鎖操作,保證同時只能加一個
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }

        // Process requests in the order they are added.
        //新增序列號,這裡用到了AtomicInteger,是一個執行緒安全的Integer,適用於高併發的Integer加減
        request.setSequence(getSequenceNumber());
        //新增一個Log資訊
        request.addMarker("add-to-queue");

        // If the request is uncacheable, skip the cache queue and go straight to the network.
        //判斷request是否需要快取,預設是需要的
        if (!request.shouldCache()) {
            //不需要快取的話直接加入佇列,使用的是PriorityBlockingQueue---一個基於優先順序堆的無界的併發安全的優先順序佇列
            mNetworkQueue.add(request);
            return request;
        }

        // Insert request into stage if there's already a request with the same cache key in flight.
        //mWaitingRequest 對應一個map<key,queue<request>>
        synchronized (mWaitingRequests) {
            //key對應著url
            String cacheKey = request.getCacheKey();
            if (mWaitingRequests.containsKey(cacheKey)) {
                //如果已經有一個相同的請求已經在等待佇列裡,則將現在這個請求放入相同key的等待佇列中
                // There is already a request in flight. Queue up.
                Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                //沒有則new一個
                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.
                //沒有的話則插入一個key-null的資訊,當為null表明這個key對應的請求就這一個,由於需要快取,則加入快取佇列
                mWaitingRequests.put(cacheKey, null);
                mCacheQueue.add(request);
            }
            return request;
        }
    }

第一步:首先將request加入mCurrentRequests。這裡注意:
mCurrentRequests是一個HashSet,HashSet底層是一個HashMap,所以不是執行緒安全的,這裡為了執行緒安全,利用synchronized關鍵字實現了加鎖操作。
第二步:給request新增了序列號。
這裡用到了AtomicInteger,是一個執行緒安全的Integer,適用於高併發的Integer加減

/**
     * Gets a sequence number.
     */
    public int getSequenceNumber() {
        return mSequenceGenerator.incrementAndGet();
    }
    /**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        return U.getAndAddInt(this, VALUE, 1) + 1;
    }

可以看到,這裡利用AtomicInteger,每次獲取的序列號的時候,增加1。
第三步:判斷request是否需要快取,每一個新建的request預設都是需要快取的,如果不需要,則需要顯式的調研request的setShouldCache方法。這裡如果不需要快取,則直接將request加入網路請求佇列(如下程式碼所示)。這裡使用的是PriorityBlockingQueue—一個基於優先順序堆的無界的併發安全的優先順序佇列

if (!request.shouldCache()) {
            //不需要快取的話直接加入佇列,使用的是PriorityBlockingQueue---一個基於優先順序堆的無界的併發安全的優先順序佇列
            mNetworkQueue.add(request);
            return request;
        }

第四步:如果需要快取,這裡對應需要插入到兩個地方mWaitingRequests和mCacheQueue,這裡由於mWaitingRequests是一個HashMap,所以同樣,需要通過synchronized關鍵字進行加鎖操作。這裡分細一點看:

String cacheKey = request.getCacheKey();
            if (mWaitingRequests.containsKey(cacheKey)) {
                //如果已經有一個相同的請求已經在等待佇列裡,則將現在這個請求放入相同key的等待佇列中
                // There is already a request in flight. Queue up.
                Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                //沒有則new一個
                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);
                }
            }

1)這裡mWaitingRequest對應的資料結構是map<key,queue<request>>,key對應的是url。首先判斷mWaitingRequest中是否存在相同的url的request,如果存在,則取出存放這種url的requestqueue,存在這個url,但對應的queue為空,則new一個,並且將這個request加入queue,並將queue加入mWatingRequest。

 else {
                // Insert 'null' queue for this cacheKey, indicating there is now a request in
                // flight.
                //沒有的話則插入一個key-null的資訊,當為null表明這個key對應的請求就這一個,由於需要快取,則加入快取佇列
                mWaitingRequests.put(cacheKey, null);
                mCacheQueue.add(request);
            }

2)如果不存在,則插入一個key-null到mWaitingRequest中,並將這個請求加入mCacheQueue快取佇列。

所以這裡一個需要快取的request進入情況就很好分析了,一個新的request加入進來,對應的,mWaitingRequest存放一個key-null。當同樣的一個url的request進入的時候,就會放到mWaitingRequest中等待,而這時候mWaitingRequest存在該url的佇列,只不過queue為null,這時候就會new一個新的queue放入mWaitingRequest,等下次有同樣的url進入的時候,就會直接加入這個佇列中等待。

相關文章