Volley框架源碼解析

博文出處:Volley框架源碼解析,歡迎大家關注我的博客膘侮,謝謝!

0001B

在 2013 年的 Google I/O 大會上的榛,Volley 網(wǎng)絡通信框架正式發(fā)布琼了。Volley 框架被設計為適用于網(wǎng)絡請求非常頻繁但是數(shù)據(jù)量并不是特別大的情景,正如它的名字一樣。Volley 相比其他網(wǎng)絡框架而言雕薪,采用了在 Android 2.3 以下使用 HttpClient 昧诱,而 Android 2.3 及以上使用 HttpUrlConnection 的方案。這是因為在 Android 2.3 以下時所袁,HttpUrlConnection 并不完善盏档,有很多 bug 存在。因此在 Android 2.3 以下最好使用 HttpClient 來進行網(wǎng)絡通信燥爷;而在 Android 2.3 及以上蜈亩,HttpUrlConnection 比起 HttpClient 來說更加簡單易用,修復了之前的 bug 前翎。所以在 Android 2.3 及以上我們使用 HttpUrlConnection 來進行網(wǎng)絡通信勺拣。

除此之外,Volley 框架還具有優(yōu)先級處理鱼填、可擴展性強等特點药有。雖然現(xiàn)在有 Retrofit 、OkHttp 等十分優(yōu)秀的網(wǎng)絡通信框架苹丸,但是深入理解 Volley 框架內部的思想可以大大提高我們自身的技術水平愤惰,畢竟僅僅停留在只會使用的階段是不行的哦。那么赘理,下面就進入我們今天的正題吧;卵浴( ps :本文篇幅過長,可能會引起不適商模,請在家長的陪同下觀看)

0010B

Volley 使用方法

在長篇大論地解析 Volley 框架源碼之前奠旺,我們先來看看平時是怎樣使用 Volley 的。(大攀┝鳎可直接跳過 -_- )

RequestQueue mQueue = Volley.newRequestQueue(context);
JsonObjectRequest request = new JsonObjectRequest(url, null,
        new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject jsonObject) {
                // TODO 
            }
        }, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError volleyError) {
        // TODO 
    }
});
mQueue.add(request);

我們通過 Volley.newRequestQueue(context) 來得到一個請求隊列的對象 mQueue响疚,在隊列中暫存了我們所有 add 進去的 request ,之后一個個取出 request 來進行網(wǎng)絡通信瞪醋。一般來說忿晕,在一個應用程序中,只保持一個請求隊列的對象银受。

之后創(chuàng)建了 JsonObjectRequest 對象用來請求 JSON 數(shù)據(jù)践盼,并把它加入 mQueue 的隊列中。Volley 框架的使用方法非常簡單宾巍,并且有多種 request 請求方式可以選擇咕幻,使用方法都是和上面類似的。

0011B

在這先把 Volley 框架中幾個重要的類的作用講一下顶霞,以便看源碼時能夠更加明白:

  • RequestQueue :這個大家一看都明白肄程,用來緩存 request 的請求隊列,根據(jù)優(yōu)先級高低排列;
  • Request :表示網(wǎng)絡請求绷耍,本身是一個抽象類吐限,子類有 StringRequest 、JsonRequest 褂始、ImageRequest 等诸典;
  • Response :表示網(wǎng)絡請求后的響應,也是一個抽象類崎苗。內部定義了 Listener 狐粱、ErrorListener 接口;
  • NetworkResponse :對返回的 HttpResponse 內容進行了封裝胆数,雖然類名和 Response 差不多肌蜻,但是不是 Response 的子類;
  • CacheDispatcher :一個處理請求緩存的線程必尼。不斷從 RequestQueue 中取出 Request 蒋搜,然后取得該 Request 對應的緩存,若緩存存在就調用 ResponseDelivery 做后續(xù)分發(fā)處理判莉;如果沒有緩存或者緩存失效需要進入 NetworkDispatcher 中從網(wǎng)絡上獲取結果豆挽;
  • NetworkDispatcher :一個處理網(wǎng)絡請求的線程。和 CacheDispatcher 類似券盅,從網(wǎng)絡上得到響應后調用 ResponseDelivery 做后續(xù)分發(fā)處理帮哈。而且根據(jù)需求判斷是否需要做緩存處理;
  • ResponseDelivery :用作分發(fā)處理锰镀。利用 Handler 把結果回調到主線程中娘侍,即 Listener 、ErrorListener 接口泳炉。主要實現(xiàn)類為 ExecutorDelivery 憾筏;
  • HttpStack :主要作用就是發(fā)起 Http 請求。子類分為 HurlStack 和 HttpClientStack 胡桃,分別對應著 HttpUrlConnection 和 HttpClient 踩叭;
  • Network :處理 Stack 發(fā)起的 Http 請求,把 Request 轉換為 Response 翠胰,主要實現(xiàn)類為 BasicNetwork ;
  • RetryPolicy :請求重試策略自脯。主要實現(xiàn)類為 DefaultRetryPolicy 之景;
  • Cache :網(wǎng)絡請求的緩存。在 CacheDispatcher 中獲取 Cache 膏潮,在 NetworkDispatcher 中判斷是否保存 Cache 锻狗。主要實現(xiàn)類為 DiskBasedCache ,緩存在磁盤中。

Volley

看完了之后轻纪,我們就要開始源碼解析油额。我們入手點就是 Volley.newRequestQueue(context) 了。

public class Volley {

    /** 默認的磁盤緩存目錄名 */
    private static final String DEFAULT_CACHE_DIR = "volley";

    public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
        // 設置 UA
        String userAgent = "volley/0";
        try {
            String packageName = context.getPackageName();
            PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
            userAgent = packageName + "/" + info.versionCode;
        } catch (NameNotFoundException e) {
        }
        // 根據(jù) Android SDK 版本設置 HttpStack 刻帚,分為 HurlStack 和 HttpClientStack
        // 分別對應著 HttpUrlConnection 和 HttpClient
        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));
            }
        }
        // 得到 network
        Network network = new BasicNetwork(stack);
        // 創(chuàng)建 RequestQueue
        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();

        return queue;
    }

    public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, null);
    }
}

從上面 Volley 類的源碼中可知潦嘶,Volley 類主要就是用來創(chuàng)建 RequestQueue 的。我們之前使用的 newRequestQueue(Context context) 方法最終會調用 newRequestQueue(Context context, HttpStack stack) 崇众。Volley 允許我們使用自定義的 HttpStack 掂僵,從這也可以看出 Volley 具有很強的擴展性。

RequestQueue

接下來繼續(xù)跟蹤 RequestQueue 構造方法的代碼顷歌。

// 默認線程池數(shù)量為 4
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;

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

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
    this(cache, network, threadPoolSize,
            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;
}

在構造方法中創(chuàng)建了 ExecutorDelivery 對象锰蓬,ExecutorDelivery 中傳入的 Handler 為主線程的,方便得到 Response 后回調眯漩;NetworkDispatcher[] 數(shù)組對象芹扭,默認數(shù)組的長度為 4 ,也就意味著默認處理請求的線程最多為 4 個赦抖。

Volley.newRequestQueue(Context context, HttpStack stack) 中創(chuàng)建完 RequestQueue 對象 queue 之后舱卡,還調用了 queue.start() 方法。主要用于啟動 queue 中的 mCacheDispatchermDispatchers 摹芙。

/** 請求緩存隊列 */
private final PriorityBlockingQueue<Request<?>> mCacheQueue =
    new PriorityBlockingQueue<Request<?>>();

/** 網(wǎng)絡請求隊列 */
private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
    new PriorityBlockingQueue<Request<?>>();

public void start() {
    stop();  // 確保當前 RequestQueue 中的 mCacheDispatcher 和 mDispatchers[] 是關閉的
    // 創(chuàng)建 mCacheDispatcher 灼狰,并且開啟
    mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
    mCacheDispatcher.start();

    // 根據(jù) mDispatchers[] 數(shù)組的長度創(chuàng)建 networkDispatcher ,并且開啟
    for (int i = 0; i < mDispatchers.length; i++) {
        NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                mCache, mDelivery);
        mDispatchers[i] = networkDispatcher;
        networkDispatcher.start();
    }
}

// 關閉當前的 mCacheDispatcher 和 mDispatchers[]
public void stop() {
    if (mCacheDispatcher != null) {
        mCacheDispatcher.quit();
    }
    for (int i = 0; i < mDispatchers.length; i++) {
        if (mDispatchers[i] != null) {
            mDispatchers[i].quit();
        }
    }
}

void finish(Request<?> request) {
    // Remove from the set of requests currently being processed.
    synchronized (mCurrentRequests) {
        // 從 mCurrentRequests 中移除該 request
        mCurrentRequests.remove(request);
    }
    // 如果 request 是可以被緩存的浮禾,那么從 mWaitingRequests 中移除交胚,加入到 mCacheQueue 中    
    if (request.shouldCache()) {
        synchronized (mWaitingRequests) {
            String cacheKey = request.getCacheKey();
            Queue<Request<?>> 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);
            }
        }
    }
}

那么看到這里我們意識到有必要看一下 CacheDispatcher 和 NetworkDispatcher 的代碼。我們先暫且放一下盈电,來看看 RequestQueue 的 add 方法蝴簇。add 方法就是把 Request 加入到 RequestQueue 中了:

private final Map<String, Queue<Request<?>>> mWaitingRequests =
        new HashMap<String, Queue<Request<?>>>();

// 當前正在請求的 Set 集合
private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();

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);
    synchronized (mCurrentRequests) {
        //加入到當前請求隊列中
        mCurrentRequests.add(request);
    }

    // 設置序列號,該序列號為 AtomInteger 自增的值
    request.setSequence(getSequenceNumber());
    request.addMarker("add-to-queue");

    // 如果該 request 不該緩存匆帚,則直接加入 mNetworkQueue 熬词,跳過下面的步驟
    if (!request.shouldCache()) {
        mNetworkQueue.add(request);
        return request;
    }
    
    synchronized (mWaitingRequests) {
        // 其實 cacheKey 就是 request 的 url
        String cacheKey = request.getCacheKey();
        // 如果該 mWaitingRequests 已經(jīng)包含了有該 cacheKey
        if (mWaitingRequests.containsKey(cacheKey)) {
            // 得到該 cacheKey 對應的 Queue
            Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
            if (stagedRequests == null) {
                stagedRequests = new LinkedList<Request<?>>();
            }
            stagedRequests.add(request);
            // 把該 request 加入到 mWaitingRequests
            mWaitingRequests.put(cacheKey, stagedRequests);
            if (VolleyLog.DEBUG) {
                VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
            }
        } else {
            // 如果沒有,那么將該 request 加入到 mCacheQueue 中
            mWaitingRequests.put(cacheKey, null);
            mCacheQueue.add(request);
        }
        return request;
    }
}

add(Request<T> request) 方法中吸重,額外使用了兩個集合來維護 Request 互拾,其中

  • mCurrentRequests :用來維護正在做請求操作的 Request;
  • mWaitingRequests :主要作用是如果當前有一個 Request 正在請求并且是可以緩存的嚎幸,那么 Volley 會去 mWaitingRequests 中根據(jù)該 cacheKey 查詢之前有沒有一樣的 Request 被加入到 mWaitingRequests 中颜矿。若有,那么該 Request 就不需要再被緩存了嫉晶;若沒有就加入到 mCacheQueue 中進行后續(xù)操作骑疆。

現(xiàn)在我們來看看 CacheDispatcher 和 NetworkDispatcher 類的源碼田篇。

CacheDispatcher

首先是 CacheDispatcher 的:

public class CacheDispatcher extends Thread {

    ...... // 省略部分源碼

    // 結束當前線程
    public void quit() {
        mQuit = true;
        interrupt();
    }

    @Override
    public void run() {
        if (DEBUG) VolleyLog.v("start new dispatcher");
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

        // 初始化 mCache ,讀取磁盤中的緩存文件箍铭,加載到 mCache 中的 map 中
        // 會造成線程阻塞泊柬,要在子線程中調用
        mCache.initialize();

        while (true) {
            try {
                // 從緩存隊列中取出 request ,若沒有則會阻塞
                final Request<?> request = mCacheQueue.take();
                request.addMarker("cache-queue-take");

                // 如果該 request 被標記為取消诈火,則跳過該 request 兽赁,不分發(fā)
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }

                // 根據(jù) request 的 url 去獲得緩存
                Cache.Entry entry = mCache.get(request.getCacheKey());
                if (entry == null) {
                    request.addMarker("cache-miss");
                    // 沒有緩存,把 Request 放入網(wǎng)絡請求隊列中 
                    mNetworkQueue.put(request);
                    continue;
                }

                // 若緩存失效柄瑰,也放入網(wǎng)絡請求隊列中
                if (entry.isExpired()) {
                    request.addMarker("cache-hit-expired");
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                }

                // 緩存存在闸氮,把緩存轉換為 Response
                request.addMarker("cache-hit");
                Response<?> response = request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));
                request.addMarker("cache-hit-parsed");
                // 判斷緩存是否需要刷新
                if (!entry.refreshNeeded()) {
                    // 不需要刷新就直接讓 mDelivery 分發(fā)
                    mDelivery.postResponse(request, response);
                } else {
                    // 需要刷新緩存
                    request.addMarker("cache-hit-refresh-needed");
                    request.setCacheEntry(entry);

                    // 先設置一個標志,表明該緩存可以先分發(fā)教沾,之后需要重新刷新
                    response.intermediate = true;

                    // 利用 mDelivery 先把 response 分發(fā)下去蒲跨,之后還要把該 request 加入到 mNetworkQueue 重新請求一遍
                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mNetworkQueue.put(request);
                            } catch (InterruptedException e) {
                                // Not much we can do about this.
                            }
                        }
                    });
                }

            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
        }
    }

}

CacheDispatcher 類主要的代碼就如上面所示了,在主要的 run() 方法中都添加了注釋授翻,閱讀起來應該比較簡單或悲。那么在這里就貢獻一張 CacheDispatcher 類的流程圖:

CacheDispatcher 類的流程圖

NetworkDispatcher

然后是 NetworkDispatcher 的代碼:

public class NetworkDispatcher extends Thread {

    ...... // 省略部分源碼

    public void quit() {
        mQuit = true;
        interrupt();
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    private void addTrafficStatsTag(Request<?> request) {
        // Tag the request (if API >= 14)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            TrafficStats.setThreadStatsTag(request.getTrafficStatsTag());
        }
    }

    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        Request<?> request;
        while (true) {
            try {
                // 取出 request
                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");

                // 判斷是否被取消,和 CacheDispatcher 中的步驟一致
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }
                // 設置線程標識
                addTrafficStatsTag(request);

                // 處理網(wǎng)絡請求
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                request.addMarker("network-http-complete");
                
                // 如果服務端返回 304 并且已經(jīng)分發(fā)了一個響應堪唐,那么不再進行二次分發(fā)
                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                    request.finish("not-modified");
                    continue;
                }

                // 得到 response
                Response<?> response = request.parseNetworkResponse(networkResponse);
                request.addMarker("network-parse-complete");

                // 如果該 request 需要進行緩存巡语,那么保存緩存
                // TODO: Only update cache metadata instead of entire record for 304s.
                if (request.shouldCache() && response.cacheEntry != null) {
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                    request.addMarker("network-cache-written");
                }

                // 標記該 request 對應的 response 正在分發(fā)中
                request.markDelivered();
                // 分發(fā)該 request 對應的 response
                mDelivery.postResponse(request, response);
            } catch (VolleyError volleyError) {
                // 分發(fā)該 request 對應的 error
                parseAndDeliverNetworkError(request, volleyError);
            } catch (Exception e) {
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
                // 分發(fā)該 request 對應的 error
                mDelivery.postError(request, new VolleyError(e));
            }
        }
    }

    private void parseAndDeliverNetworkError(Request<?> request, VolleyError error) {
        error = request.parseNetworkError(error);
        mDelivery.postError(request, error);
    }

}

同樣的,根據(jù) NetworkDispatcher 我們也可以梳理出一張流程圖:

NetworkDispatcher 類的流程圖

Request

到這里淮菠,我們把目光轉向 Request 男公。Request 是一個抽象類:

public abstract class Request<T> implements Comparable<Request<T>> {

    ...// 省略絕大部分源碼-_- !
    
    abstract protected Response<T> parseNetworkResponse(NetworkResponse response);
    
    abstract protected void deliverResponse(T response);
    
    protected VolleyError parseNetworkError(VolleyError volleyError) {
        return volleyError;
    }
    
    public void deliverError(VolleyError error) {
        if (mErrorListener != null) {
            mErrorListener.onErrorResponse(error);
        }
    }
    
    // request 完成響應分發(fā)后調用 
    void finish(final String tag) {
        if (mRequestQueue != null) {
            // 跳轉到 RequestQueue.finish 方法
            mRequestQueue.finish(this);
        }
        if (MarkerLog.ENABLED) {
            final long threadId = Thread.currentThread().getId();
            if (Looper.myLooper() != Looper.getMainLooper()) {
                // If we finish marking off of the main thread, we need to
                // actually do it on the main thread to ensure correct ordering.
                Handler mainThread = new Handler(Looper.getMainLooper());
                mainThread.post(new Runnable() {
                    @Override
                    public void run() {
                        mEventLog.add(tag, threadId);
                        mEventLog.finish(this.toString());
                    }
                });
                return;
            }
    
            mEventLog.add(tag, threadId);
            mEventLog.finish(this.toString());
        } else {
            long requestTime = SystemClock.elapsedRealtime() - mRequestBirthTime;
            if (requestTime >= SLOW_REQUEST_THRESHOLD_MS) {
                VolleyLog.d("%d ms: %s", requestTime, this.toString());
            }
        }
    }
    
    @Override
    public int compareTo(Request<T> other) {
        Priority left = this.getPriority();
        Priority right = other.getPriority();
    
        // request 優(yōu)先級高的比低的排在隊列前面,優(yōu)先被請求
        // 如果優(yōu)先級一樣合陵,按照 FIFO 的原則排列
        return left == right ?
                this.mSequence - other.mSequence :
                right.ordinal() - left.ordinal();
    }

}

Request 實現(xiàn)了 Comparable 接口枢赔,這是因為 Request 是有優(yōu)先級的,優(yōu)先級高比優(yōu)先級低的要先響應拥知,排列在前踏拜。默認有四種優(yōu)先級:

public enum Priority {
    LOW,
    NORMAL,
    HIGH,
    IMMEDIATE
}

另外,子類繼承 Request 還要實現(xiàn)兩個抽象方法:

  • parseNetworkResponse :把 NetworkResponse 轉換為合適類型的 Response低剔;
  • deliverResponse :把解析出來的類型分發(fā)給監(jiān)聽器回調速梗。

另外,Request 還支持八種請求方式:

/**
 * Supported request methods.
 */
public interface Method {
    int DEPRECATED_GET_OR_POST = -1;
    int GET = 0;
    int POST = 1;
    int PUT = 2;
    int DELETE = 3;
    int HEAD = 4;
    int OPTIONS = 5;
    int TRACE = 6;
    int PATCH = 7;
}

在 Volley 中襟齿,Request 的子類眾多姻锁,有 StringRequest 、JsonObjectRequest(繼承自 JsonRequest ) 猜欺、JsonArrayRequest(繼承自 JsonRequest ) 和 ImageRequest 等屋摔。當然這些子類并不能滿足全部的場景要求,而這就需要我們開發(fā)者自己動手去擴展了替梨。

下面我就分析一下 StringRequest 的源碼钓试,其他子類的源碼都是類似的,可以回去自行研究副瀑。

public class StringRequest extends Request<String> {

    private final Listener<String> mListener;

    public StringRequest(int method, String url, Listener<String> listener,
            ErrorListener errorListener) {
        super(method, url, errorListener);
        mListener = listener;
    }

    public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
        this(Method.GET, url, listener, errorListener);
    }

    @Override
    protected void deliverResponse(String response) {
        // 得到相應的 response 后弓熏,回調 Listener 的接口
        mListener.onResponse(response);
    }

    @Override
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            // 把字節(jié)數(shù)組轉化為字符串
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));(response.headers));
        } catch (UnsupportedEncodingException e) {
            parsed = new String(response.data);
        }
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
    }

}

我們發(fā)現(xiàn) StringRequest 的源碼十分簡潔。在 parseNetworkResponse 方法中主要把 response 中的 data 轉化為對應的 String 類型糠睡。然后回調 Response.success 即可挽鞠。

看完了 Request 之后,我們來分析一下 Network 狈孔。

Network

Network 是一個接口信认,里面就一個方法 performRequest(Request<?> request) :

public interface Network {

    public NetworkResponse performRequest(Request<?> request) throws VolleyError;

}

光看這個方法的定義就知道這個方法是用來干什么了!就是根據(jù)傳入的 Request 執(zhí)行均抽,轉換為對應的 NetworkResponse 的嫁赏,并且該 NetworkResponse 不為空。我們就跳到它的實現(xiàn)類中看看該方法具體是怎么樣的油挥。

public class BasicNetwork implements Network {
    protected static final boolean DEBUG = VolleyLog.DEBUG;

    private static int SLOW_REQUEST_THRESHOLD_MS = 3000;

    private static int DEFAULT_POOL_SIZE = 4096;

    protected final HttpStack mHttpStack;

    protected final ByteArrayPool mPool;

    public BasicNetwork(HttpStack httpStack) {
        // 使用 ByteArrayPool 可以實現(xiàn)復用潦蝇,節(jié)約內存
        this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
    }

    public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
        mHttpStack = httpStack;
        mPool = pool;
    }

    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            Map<String, String> responseHeaders = new HashMap<String, String>();
            try {
                // 得到請求頭
                Map<String, String> headers = new HashMap<String, String>();
                // 添加緩存頭部信息
                addCacheHeaders(headers, request.getCacheEntry());
                httpResponse = mHttpStack.performRequest(request, headers);
                // 得到響應行
                StatusLine statusLine = httpResponse.getStatusLine();
                int statusCode = statusLine.getStatusCode();
                // 轉化得到響應頭
                responseHeaders = convertHeaders(httpResponse.getAllHeaders());
                // 如果返回的狀態(tài)碼是304(即:HttpStatus.SC_NOT_MODIFIED)
                // 那么說明服務端的數(shù)據(jù)沒有變化,就直接從之前的緩存中取
                if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,
                            request.getCacheEntry() == null ? null : request.getCacheEntry().data,
                            responseHeaders, true);
                }

                // Some responses such as 204s do not have content.  We must check.
                // 有一些響應可能沒有內容深寥,比如攘乒,所以要判斷一下
                if (httpResponse.getEntity() != null) {
                    // 把 entiity 轉為 byte[]
                  responseContents = entityToBytes(httpResponse.getEntity());
                } else {
                  // Add 0 byte response as a way of honestly representing a
                  // no-content request.
                  responseContents = new byte[0];
                }

                // 如果該請求的響應時間超過 SLOW_REQUEST_THRESHOLD_MS(即 3000ms) ,會打印相應的日志
                long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
                logSlowRequests(requestLifetime, request, responseContents, statusLine);
                // 響應碼不是在 200-299 之間就拋異常
                if (statusCode < 200 || statusCode > 299) {
                    throw new IOException();
                }
                return new NetworkResponse(statusCode, responseContents, responseHeaders, false);
            } catch (SocketTimeoutException e) {
                // 啟動重試策略, 超時錯誤
                attemptRetryOnException("socket", request, new TimeoutError());
            } catch (ConnectTimeoutException e) {
                // 啟動重試策略, 超時錯誤
                attemptRetryOnException("connection", request, new TimeoutError());
            } catch (MalformedURLException e) {
                throw new RuntimeException("Bad URL " + request.getUrl(), e);
            } catch (IOException e) {
                int statusCode = 0;
                NetworkResponse networkResponse = null;
                if (httpResponse != null) {
                    statusCode = httpResponse.getStatusLine().getStatusCode();
                } else {
                    throw new NoConnectionError(e);
                }
                VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
                if (responseContents != null) {
                    networkResponse = new NetworkResponse(statusCode, responseContents,
                            responseHeaders, false);
                    if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
                            statusCode == HttpStatus.SC_FORBIDDEN) {
                        // 啟動重試策略, 認證錯誤
                        attemptRetryOnException("auth",
                                request, new AuthFailureError(networkResponse));
                    } else {
                        // TODO: Only throw ServerError for 5xx status codes.
                        throw new ServerError(networkResponse);
                    }
                } else {
                    throw new NetworkError(networkResponse);
                }
            }
        }
    }

    /**
     * 如果請求的響應時間超過 SLOW_REQUEST_THRESHOLD_MS 惋鹅,就打印相應的日志
     */
    private void logSlowRequests(long requestLifetime, Request<?> request,
            byte[] responseContents, StatusLine statusLine) {
        if (DEBUG || requestLifetime > SLOW_REQUEST_THRESHOLD_MS) {
            VolleyLog.d("HTTP response for request=<%s> [lifetime=%d], [size=%s], " +
                    "[rc=%d], [retryCount=%s]", request, requestLifetime,
                    responseContents != null ? responseContents.length : "null",
                    statusLine.getStatusCode(), request.getRetryPolicy().getCurrentRetryCount());
        }
    }

    /**
     * 進行重試策略则酝,如果不滿足重試的條件會拋出異常
     */
    private static void attemptRetryOnException(String logPrefix, Request<?> request,
            VolleyError exception) throws VolleyError {
        RetryPolicy retryPolicy = request.getRetryPolicy();
        int oldTimeout = request.getTimeoutMs();

        try {
            retryPolicy.retry(exception);
        } catch (VolleyError e) {
            request.addMarker(
                    String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));
            throw e;
        }
        request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
    }

    // 在請求行中添加緩存相關
    private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) {
        // If there's no cache entry, we're done.
        if (entry == null) {
            return;
        }

        if (entry.etag != null) {
            headers.put("If-None-Match", entry.etag);
        }

        if (entry.serverDate > 0) {
            Date refTime = new Date(entry.serverDate);
            headers.put("If-Modified-Since", DateUtils.formatDate(refTime));
        }
    }

    protected void logError(String what, String url, long start) {
        long now = SystemClock.elapsedRealtime();
        VolleyLog.v("HTTP ERROR(%s) %d ms to fetch %s", what, (now - start), url);
    }

    /**把 HttpEntity 的內容讀取到 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 occured when calling consumingContent");
            }
            mPool.returnBuf(buffer);
            bytes.close();
        }
    }

    /**
     * 把 Headers[] 轉換為 Map<String, String>.
     */
    private static Map<String, String> convertHeaders(Header[] headers) {
        Map<String, String> result = new HashMap<String, String>();
        for (int i = 0; i < headers.length; i++) {
            result.put(headers[i].getName(), headers[i].getValue());
        }
        return result;
    }
}

我們把 BasicNetwork 的源碼全部看下來,發(fā)現(xiàn) BasicNetwork 干的事情就如下:

  • 利用 HttpStack 執(zhí)行請求闰集,把響應 HttpResponse 封裝為 NetworkResponse ;
  • 如果在這過程中出錯沽讹,會有重試策略。

至于 NetworkResponse 的源碼在這里就不分析了返十,主要是一個相對于 HttpResponse 的封裝類遭垛,可以自己去看其源碼涣雕。

得到 NetworkResponse 之后,在 NetworkDispatcher 中經(jīng)過 Request 的 parseNetworkResponse 方法把 NetworkResponse 轉化為了 Response 。(具體可參考上面分析的 NetworkDispatcher 和 StringRequest 源碼)

那么接下來就把目光轉向 Response 吧疆拘。

Response

Response 類的源碼比較簡單,一起來看看:

public class Response<T> {

    /** 分發(fā)響應結果的接口 */
    public interface Listener<T> {
        /** Called when a response is received. */
        public void onResponse(T response);
    }

    /** 分發(fā)響應錯誤的接口 */
    public interface ErrorListener {
        /**
         * Callback method that an error has been occurred with the
         * provided error code and optional user-readable message.
         */
        public void onErrorResponse(VolleyError error);
    }

    /** 通過這個靜態(tài)方法構造 Response */
    public static <T> Response<T> success(T result, Cache.Entry cacheEntry) {
        return new Response<T>(result, cacheEntry);
    }

    /**
     * 通過這個靜態(tài)方法構造錯誤的 Response
     */
    public static <T> Response<T> error(VolleyError error) {
        return new Response<T>(error);
    }

    /** Parsed response, or null in the case of error. */
    public final T result;

    /** Cache metadata for this response, or null in the case of error. */
    public final Cache.Entry cacheEntry;

    /** Detailed error information if <code>errorCode != OK</code>. */
    public final VolleyError error;

    /** True if this response was a soft-expired one and a second one MAY be coming. */
    public boolean intermediate = false;

    /**
     * 是否成功
     */
    public boolean isSuccess() {
        return error == null;
    }


    private Response(T result, Cache.Entry cacheEntry) {
        this.result = result;
        this.cacheEntry = cacheEntry;
        this.error = null;
    }

    private Response(VolleyError error) {
        this.result = null;
        this.cacheEntry = null;
        this.error = error;
    }
}

Response 類主要通過 successerror 兩個方法分別來構造正確的響應結果和錯誤的響應結果狭园。另外缀蹄,在 Response 類中還有 Listener 和 ErrorListener 兩個接口。在最終的回調中會使用到它們排拷。

在得到了 Response 之后侧漓,就要使用 ResponseDelivery 來分發(fā)了。那下面就輪到 ResponseDelivery 了监氢,go on !!

ResponseDelivery

public interface ResponseDelivery {
    /**
     * 分發(fā)該 request 的 response
     */
    public void postResponse(Request<?> request, Response<?> response);

    /**
     * 分發(fā)該 request 的response 布蔗,runnable 會在分發(fā)之后執(zhí)行
     */
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable);

    /**
     * 分發(fā)該 request 錯誤的 response
     */
    public void postError(Request<?> request, VolleyError error);
}

ResponseDelivery 的接口就定義了三個方法藤违,我們需要在其實現(xiàn)類中看看具體的實現(xiàn):

public class ExecutorDelivery implements ResponseDelivery {
    /** 用來分發(fā) Response , 一般都是在主線程中*/
    private final Executor mResponsePoster;

    /**
     * 傳入的 Handler 為主線程的
     */
    public ExecutorDelivery(final Handler handler) {
        // Make an Executor that just wraps the handler.
        mResponsePoster = new Executor() {
            @Override
            public void execute(Runnable command) {
                handler.post(command);
            }
        };
    }

    /**
     * Creates a new response delivery interface, mockable version
     * for testing.
     * @param executor For running delivery tasks
     */
    public ExecutorDelivery(Executor executor) {
        mResponsePoster = executor;
    }

    @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));
    }

    @Override
    public void postError(Request<?> request, VolleyError error) {
        request.addMarker("post-error");
        Response<?> response = Response.error(error);
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
    }

    /**
     * A Runnable 用來分發(fā) response 到主線程的回調接口中
     */
    @SuppressWarnings("rawtypes")
    private class ResponseDeliveryRunnable implements Runnable {
        private final Request mRequest;
        private final Response mResponse;
        private final Runnable mRunnable;

        public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
            mRequest = request;
            mResponse = response;
            mRunnable = runnable;
        }

        @SuppressWarnings("unchecked")
        @Override
        public void run() {
            // 如果 request 被標記為取消,那么不用分發(fā)
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }

            // 根據(jù) mResponse 是否成功來分發(fā)到不同的方法
            if (mResponse.isSuccess()) {
                mRequest.deliverResponse(mResponse.result);
            } else {
                mRequest.deliverError(mResponse.error);
            }

            // If this is an intermediate response, add a marker, otherwise we're done
            // and the request can be finished. 
            if (mResponse.intermediate) {
                mRequest.addMarker("intermediate-response");
            } else {
                mRequest.finish("done");
            }

            // 運行 mRunnable
            if (mRunnable != null) {
                mRunnable.run();
            }
       }
    }
}

ResponseDelivery 將根據(jù) mResponse 是否成功來調用不同的方法 mRequest.deliverResponsemRequest.deliverError 纵揍。在 mRequest.deliverResponse 中會回調 Listener 的 onResponse 方法顿乒;而在 mRequest.deliverError 中會回調 ErrorListener 的 onErrorResponse 方法。至此泽谨,一個完整的網(wǎng)絡請求及響應流程走完了璧榄。

HttpStack

現(xiàn)在回過頭來看看 Volley 框架中是如何發(fā)起網(wǎng)絡請求的。在本文的開頭中說過吧雹,Volley 是會根據(jù) Android 的版本來選擇對應的 HttpStack骨杂。那么下面我們來深入看一下 HttpStack 的源碼。

public interface HttpStack {
    /**
     * 通過所給的參數(shù)執(zhí)行請求
     */
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
        throws IOException, AuthFailureError;

}

HttpStack 接口中定義的方法就只有一個雄卷。我們要分別來看看 HurlStack 和 HttpClientStack 各自的實現(xiàn)搓蚪。

HurlStack :

@Override
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
        throws IOException, AuthFailureError {
    String url = request.getUrl();
    HashMap<String, String> map = new HashMap<String, String>();
    // 把請求頭放入 map 中
    map.putAll(request.getHeaders());
    map.putAll(additionalHeaders);
    if (mUrlRewriter != null) {
        String rewritten = mUrlRewriter.rewriteUrl(url);
        if (rewritten == null) {
            throw new IOException("URL blocked by rewriter: " + url);
        }
        url = rewritten;
    }
    URL parsedUrl = new URL(url);
    // 使用 HttpURLConnection 來發(fā)起請求
    HttpURLConnection connection = openConnection(parsedUrl, request);
    // 設置請求頭
    for (String headerName : map.keySet()) {
        connection.addRequestProperty(headerName, map.get(headerName));
    }
    // 設置請求方法
    setConnectionParametersForRequest(connection, request);
    // Initialize HttpResponse with data from the HttpURLConnection.
    ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
    int responseCode = connection.getResponseCode();
    if (responseCode == -1) {
        // -1 is returned by getResponseCode() if the response code could not be retrieved.
        // Signal to the caller that something was wrong with the connection.
        throw new IOException("Could not retrieve response code from HttpUrlConnection.");
    }
    // 把響應封裝進 response 中
    StatusLine responseStatus = new BasicStatusLine(protocolVersion,
            connection.getResponseCode(), connection.getResponseMessage());
    BasicHttpResponse response = new BasicHttpResponse(responseStatus);
    response.setEntity(entityFromConnection(connection));
    for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
        if (header.getKey() != null) {
            Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
            response.addHeader(h);
        }
    }
    return response;
}

HttpClientStack :

@Override
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
        throws IOException, AuthFailureError {
    // 根據(jù)請求方法生成對應的 HttpUriRequest
    HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders);
    // 添加請求頭
    addHeaders(httpRequest, additionalHeaders);
    addHeaders(httpRequest, request.getHeaders());
    onPrepareRequest(httpRequest);
    HttpParams httpParams = httpRequest.getParams();
    int timeoutMs = request.getTimeoutMs();
    // TODO: Reevaluate this connection timeout based on more wide-scale
    // data collection and possibly different for wifi vs. 3G.
    HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
    HttpConnectionParams.setSoTimeout(httpParams, timeoutMs);
    return mClient.execute(httpRequest);
}

在這里只給出 HurlStack 和 HttpClientStack 的 performRequest 方法。我們可以看到 HurlStack 和 HttpClientStack 已經(jīng)把 HttpUrlConnection 和 HttpClient 封裝得很徹底了龙亲,以后哪里有需要的地方可以直接使用陕凹。

RetryPolicy

RetryPolicy 接口主要的作用就是定制重試策略,我們從下面的源碼可以看出該接口有三個抽象方法:

  • getCurrentTimeout :得到當前超時時間鳄炉;
  • getCurrentRetryCount :得到當前重試的次數(shù)杜耙;
  • retry :是否進行重試,其中的 error 參數(shù)為異常的信息拂盯。若在 retry 方法中跑出 error 異常佑女,那 Volley 就會停止重試。
public interface RetryPolicy {

    /**
     * Returns the current timeout (used for logging).
     */
    public int getCurrentTimeout();

    /**
     * Returns the current retry count (used for logging).
     */
    public int getCurrentRetryCount();

    /**
     * Prepares for the next retry by applying a backoff to the timeout.
     * @param error The error code of the last attempt.
     * @throws VolleyError In the event that the retry could not be performed (for example if we
     * ran out of attempts), the passed in error is thrown.
     */
    public void retry(VolleyError error) throws VolleyError;
}

RetryPolicy 接口有一個默認的實現(xiàn)類 DefaultRetryPolicy 谈竿,DefaultRetryPolicy 的構造方法有兩個:

/** The default socket timeout in milliseconds */
public static final int DEFAULT_TIMEOUT_MS = 2500;

/** The default number of retries */
public static final int DEFAULT_MAX_RETRIES = 1;

/** The default backoff multiplier */
public static final float DEFAULT_BACKOFF_MULT = 1f;

public DefaultRetryPolicy() {
    this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
}

/**
 * Constructs a new retry policy.
 * @param initialTimeoutMs The initial timeout for the policy.
 * @param maxNumRetries The maximum number of retries.
 * @param backoffMultiplier Backoff multiplier for the policy.
 */
public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
    mCurrentTimeoutMs = initialTimeoutMs;
    mMaxNumRetries = maxNumRetries;
    mBackoffMultiplier = backoffMultiplier;
}

從上面可以看到团驱,在 Volley 內部已經(jīng)有一套默認的參數(shù)配置了。當然空凸,你也可以通過自定義的形式來設置重試策略嚎花。

@Override
public void retry(VolleyError error) throws VolleyError {
    // 重試次數(shù)自增
    mCurrentRetryCount++;
    // 超時時間自增,mBackoffMultiplier 為超時時間的因子
    mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
    // 如果超過最大次數(shù)呀洲,拋出異常
    if (!hasAttemptRemaining()) {
        throw error;
    }
}

/**
 * Returns true if this policy has attempts remaining, false otherwise.
 */
protected boolean hasAttemptRemaining() {
    return mCurrentRetryCount <= mMaxNumRetries;
}

Cache

分析完了前面這么多的類紊选,終于輪到了最后的 Cache 。Cache 接口中定義了一個內部類 Entry 道逗,還有定義了幾個方法:

  • get(String key) :根據(jù)傳入的 key 來獲取 entry 兵罢;
  • put(String key, Entry entry) :增加或者替換緩存;
  • initialize() :初始化滓窍,是耗時的操作卖词,在子線程中調用;
  • invalidate(String key, boolean fullExpire) :根據(jù) key 使之對應的緩存失效吏夯;
  • remove(String key) :根據(jù) key 移除某個緩存此蜈;
  • clear() :清空緩存即横。
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();

}

內部類 Entry ,Entry 中有一個屬性為 etag 舶替,上面的源碼中也有 etag 的身影令境。如果你對 ETag 不熟悉,可以查看這篇文章《Etag與HTTP緩存機制》

public static class Entry {
    /** 緩存中數(shù)據(jù)顾瞪,即響應中的 body */
    public byte[] data;

    /** HTTP頭部的一個定義,允許客戶端進行緩存協(xié)商 */
    public String etag;

    /** 服務端響應的時間 */
    public long serverDate;

    /** 緩存過期的時間抛蚁,若小于當前時間則過期 */
    public long ttl;

    /** 緩存的有效時間陈醒,若小于當前時間則可以進行刷新操作 */
    public long softTtl;

    /** 響應的頭部信息 */
    public Map<String, String> responseHeaders = Collections.emptyMap();

    /** 判斷緩存是否有效,若返回 true 則緩存失效. */
    public boolean isExpired() {
        return this.ttl < System.currentTimeMillis();
    }

    /** 判斷緩存是否需要刷新 */
    public boolean refreshNeeded() {
        return this.softTtl < System.currentTimeMillis();
    }
}

看完了 Cache 接口之后瞧甩,我們來看一下實現(xiàn)類 DiskBasedCache 钉跷。首先是它的構造方法:

private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;

public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
    mRootDirectory = rootDirectory;
    mMaxCacheSizeInBytes = maxCacheSizeInBytes;
}

public DiskBasedCache(File rootDirectory) {
    this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
}

從構造方法中傳入的參數(shù)可知,Volley 默認最大磁盤緩存為 5M 肚逸。

DiskBasedCache 的 get(String key) 方法:

@Override
public synchronized Entry get(String key) {
    // 得到對應的緩存摘要信息
    CacheHeader entry = mEntries.get(key);
    // if the entry does not exist, return.
    if (entry == null) {
        return null;
    }
    // 得到緩存文件
    File file = getFileForKey(key);
    // CountingInputStream 為自定義的 IO 流爷辙,繼承自 FilterInputStream
    // 具有記憶已讀取的字節(jié)數(shù)的功能
    CountingInputStream cis = null;
    try {
        cis = new CountingInputStream(new FileInputStream(file));
        CacheHeader.readHeader(cis); // eat header
        // 得到緩存中的數(shù)據(jù) data[] 
        byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));
        return entry.toCacheEntry(data);
    } catch (IOException e) {
        VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
        // 若出錯則移除緩存
        remove(key);
        return null;
    } finally {
        if (cis != null) {
            try {
                cis.close();
            } catch (IOException ioe) {
                return null;
            }
        }
    }
}

// 把 url 分成兩半,分別得到對應的 hashcode 朦促,拼接后得到對應的文件名
private String getFilenameForKey(String key) {
    int firstHalfLength = key.length() / 2;
    String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode());
    localFilename += String.valueOf(key.substring(firstHalfLength).hashCode());
    return localFilename;
}

/**
 * Returns a file object for the given cache key.
 */
public File getFileForKey(String key) {
    return new File(mRootDirectory, getFilenameForKey(key));
}

DiskBasedCache 的 putEntry(String key, CacheHeader entry) 方法:

@Override
public synchronized void put(String key, Entry entry) {
    // 檢查磁盤空間是否足夠膝晾,若不夠會刪除一些緩存文件
    pruneIfNeeded(entry.data.length);
    File file = getFileForKey(key);
    try {
        FileOutputStream fos = new FileOutputStream(file);
        CacheHeader e = new CacheHeader(key, entry);
        e.writeHeader(fos);
        fos.write(entry.data);
        fos.close();
        putEntry(key, e);
        return;
    } catch (IOException e) {
    }
    boolean deleted = file.delete();
    if (!deleted) {
        VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
    }
}

private void putEntry(String key, CacheHeader entry) {
    // 計算總緩存的大小
    if (!mEntries.containsKey(key)) {
        mTotalSize += entry.size;
    } else {
        CacheHeader oldEntry = mEntries.get(key);
        mTotalSize += (entry.size - oldEntry.size);
    }
    // 增加或者替換緩存
    mEntries.put(key, entry);
}

initialize() 方法:

@Override
public synchronized void initialize() {
    if (!mRootDirectory.exists()) {
        if (!mRootDirectory.mkdirs()) {
            VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
        }
        return;
    }

    File[] files = mRootDirectory.listFiles();
    if (files == null) {
        return;
    }
    // 讀取緩存文件
    for (File file : files) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
            // 得到緩存文件的摘要信息
            CacheHeader entry = CacheHeader.readHeader(fis);
            entry.size = file.length();
            // 放入 map 中
            putEntry(entry.key, entry);
        } catch (IOException e) {
            if (file != null) {
               file.delete();
            }
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
            } catch (IOException ignored) { }
        }
    }
}

invalidate(String key, boolean fullExpire) 方法:

@Override
public synchronized void invalidate(String key, boolean fullExpire) {
    Entry entry = get(key);
    if (entry != null) {
        // 有效時間置零
        entry.softTtl = 0;
        if (fullExpire) {
            entry.ttl = 0;
        }
        put(key, entry);
    }

}

remove(String key)clear() 方法比較簡單,就不需要注釋了:

@Override
public synchronized void remove(String key) {
    boolean deleted = getFileForKey(key).delete();
    removeEntry(key);
    if (!deleted) {
        VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
                key, getFilenameForKey(key));
    }
}

@Override
public synchronized void clear() {
    File[] files = mRootDirectory.listFiles();
    if (files != null) {
        for (File file : files) {
            file.delete();
        }
    }
    mEntries.clear();
    mTotalSize = 0;
    VolleyLog.d("Cache cleared.");
}

0100B

至此务冕,Volley 源碼解析差不多已經(jīng)結束了血当。基本上在整個 Volley 框架中至關重要的類都講到了禀忆。當然臊旭,還有一些 NetworkImageView 、ImageLoader 等源碼還沒解析箩退。由于本篇文章內容太長了(有史以來寫過最長的一篇─=≡Σ((( つ??ω??)つ)离熏,只能等到下次有機會再給大家補上了。

在這還給出了一張整個 Volley 框架大致的網(wǎng)絡通信流程圖戴涝,對上面源碼沒看懂的童鞋可以參考這張圖再看一遍:

Volley框架大致流程圖

最后滋戳,只剩下總結了。從頭到尾分析了一遍喊括,發(fā)現(xiàn) Volley 真的是一款很優(yōu)秀的框架胧瓜,面向接口編程在其中發(fā)揮到極致。其中有不少值得我們借鑒的地方郑什,但是 Volley 并不是沒有缺點的府喳,對于大文件傳輸 Volley 就很不擅長,搞不好會 OOM 蘑拯。另外钝满,在源碼中還有不少可以繼續(xù)優(yōu)化的地方兜粘,有興趣的同學可以自定義一個屬于自己的 Volley 。

好了弯蚜,如果你對本文哪里有問題或者不懂的地方孔轴,歡迎留言一起交流。

0101B

References

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末路鹰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子收厨,更是在濱河造成了極大的恐慌晋柱,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诵叁,死亡現(xiàn)場離奇詭異雁竞,居然都是意外死亡,警方通過查閱死者的電腦和手機拧额,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門碑诉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人侥锦,你說我怎么就攤上這事进栽。” “怎么了捎拯?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵泪幌,是天一觀的道長。 經(jīng)常有香客問我署照,道長祸泪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任建芙,我火速辦了婚禮没隘,結果婚禮上,老公的妹妹穿的比我還像新娘禁荸。我一直安慰自己右蒲,他們只是感情好,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布赶熟。 她就那樣靜靜地躺著瑰妄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪映砖。 梳的紋絲不亂的頭發(fā)上间坐,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機與錄音,去河邊找鬼竹宋。 笑死劳澄,一個胖子當著我的面吹牛,可吹牛的內容都是我干的蜈七。 我是一名探鬼主播秒拔,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼飒硅!你這毒婦竟也來了砂缩?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤狡相,失蹤者是張志新(化名)和其女友劉穎梯轻,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體尽棕,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年彬伦,在試婚紗的時候發(fā)現(xiàn)自己被綠了滔悉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡单绑,死狀恐怖回官,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情搂橙,我是刑警寧澤歉提,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站区转,受9級特大地震影響苔巨,放射性物質發(fā)生泄漏。R本人自食惡果不足惜废离,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一侄泽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蜻韭,春花似錦悼尾、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至俯画,卻和暖如春析桥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工烹骨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留翻伺,地道東北人。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓沮焕,卻偏偏與公主長得像吨岭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子峦树,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內容