1. 程式人生 > >Android框架原始碼解析之(一)Volley

Android框架原始碼解析之(一)Volley

前幾天面試CVTE,HR面掛了。讓內部一個學長幫我查看了一下面試官評價,發現二面面試官的評價如下:

  1. 廣度OK,但缺乏深究能力,深度與實踐不足
  2. 原始碼:只能說流程,細節程式碼不清楚,retrofit和volley都是。

感覺自己一方面:自己面試技巧有待提高吧(框架只說了流程,而沒說原始碼,原始碼實在是不知道怎麼說)
另一方面:原始碼雖然說系統的看過,但是細節不夠深入。

所以,就開個專欄,分析框架原始碼吧

第一篇Volley解析,雖然Volley已經很少有人使用了,但是內部設計三級快取機制值得我們學習。

先看一下volley的基本使用:

//1、建立RequestQueen
RequestQueue mQueue = Volley.newRequestQueue(context); //2、建立請求Request StringRequest stringRequest = new StringRequest("http://www.baidu.com", new Response.Listener<String>() { @Override public void onResponse(String response) { Log.d("TAG"
, response); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.e("TAG", error.getMessage(), error); } }); //3、將請求新增到RequestQueen中
mQueue.add(stringRequest);

Volley的原始碼解析:

下載地址:https://github.com/google/volley
下載後直接匯入Android studio中即可

1、從RequestQueen入手

RequestQueue mQueue = Volley.newRequestQueue(context);

原始碼:

public class Volley {

    /** Default on-disk cache directory. */

    // /預設快取資料夾,檔名為Volley,預設最大為5M
    private static final String DEFAULT_CACHE_DIR = "volley";

    /**
     * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
     *
     * @param context A {@link Context} to use for creating the cache dir.
     * @param stack A {@link BaseHttpStack} to use for the network, or null for default.
     * @return A started {@link RequestQueue} instance.
     */
    public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {

        //BasicNetwork 網路請求類
        BasicNetwork network;



        //建立網路請求實現類
        // stack 預設網路請求實現類,如果Build.VERSION.SDK_INT >= 9  則stack =HttpURLConnecton,否則 stack =HttpClient

        if (stack == null) {


            if (Build.VERSION.SDK_INT >= 9) {
                network = new BasicNetwork(new HurlStack());
            } else {
                // Prior to Gingerbread, HttpUrlConnection was unreliable.
                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
                // At some point in the future we'll move our minSdkVersion past Froyo and can
                // delete this fallback (along with all Apache HTTP code).
                String userAgent = "volley/0";
                try {
                    String packageName = context.getPackageName();
                    PackageInfo info =
                            context.getPackageManager().getPackageInfo(packageName, /* flags= */ 0);
                    userAgent = packageName + "/" + info.versionCode;
                } catch (NameNotFoundException e) {
                }

                network =
                        new BasicNetwork(
                                new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));
            }
        } else {
            network = new BasicNetwork(stack);
        }

        //呼叫下面的回撥方法
        return newRequestQueue(context, network);
    }

    /**
     * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
     *
     * @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.
     * @return A started {@link RequestQueue} instance.
     * @deprecated Use {@link #newRequestQueue(Context, BaseHttpStack)} instead to avoid depending
     *     on Apache HTTP. This method may be removed in a future release of Volley.
     */
    @Deprecated
    @SuppressWarnings("deprecation")
    public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        if (stack == null) {
            return newRequestQueue(context, (BaseHttpStack) null);
        }
        return newRequestQueue(context, new BasicNetwork(stack));
    }

    private static RequestQueue newRequestQueue(Context context, Network network) {

        //建立磁碟快取檔案
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
        //建立請求佇列
        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        //開啟請求佇列
        queue.start();
        return queue;
    }

    /**
     * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
     *
     * @param context A {@link Context} to use for creating the cache dir.
     * @return A started {@link RequestQueue} instance.
     */
    public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, (BaseHttpStack) null);
    }
}

可以看到,Volley.newRequestQueue(context);中主要做了以下操作:
1、建立網路請求實現類
2、建立快取
3、建立請求佇列,並開啟請求佇列

2、接下來看RequestQueen的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.
        //建立一個快取排程執行緒,並開啟
        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();
        }
    }

做了以下操作:
1、建立一個快取排程執行緒和4個網路排程執行緒
2、開啟這些執行緒

一會分析而這些執行緒

3、Request.add(stringRequest);

原始碼:

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

        // 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.

        //判斷request是否可以快取,如果不可以快取,直接加入網路請求佇列
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }

        //如果可以快取,將請求加入快取請求佇列
        mCacheQueue.add(request);
        return request;
    }

判斷request是否可以快取,如果不能快取,直接將請求新增到網路請求佇列。若能,將請求新增到快取請求佇列。預設是可以快取的。

如何判斷request是否可以快取:
看原始碼:
Request.java




    /** Whether or not responses to this request should be cached. */
    // TODO(#190): Turn this off by default for anything other than GET requests.

//預設是可以快取的
    private boolean mShouldCache = true;


    /** Returns true if responses to this request should be cached. */
    public final boolean shouldCache() {
        return mShouldCache;
    }

4、CacheDispatcher快取排程執行緒
先看一下類的定義

public class CacheDispatcher extends Thread {

說明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();

        while (true) {
            try {
//重點在這
                processRequest();
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    //如果quit,則中斷執行緒
                    Thread.currentThread().interrupt();
                    return;
                }
                VolleyLog.e(
                        "Ignoring spurious interrupt of CacheDispatcher thread; "
                                + "use quit() to terminate it");
            }
        }
    }

初始化了快取,呼叫了processRequest();
檢視processRequest();原始碼

 private void processRequest() throws InterruptedException {
        // Get a request from the cache triage queue, blocking until
        // at least one is available.
        final Request<?> request = mCacheQueue.take();
        //呼叫下面回撥
        processRequest(request);
    }

    @VisibleForTesting
    void processRequest(final Request<?> request) throws InterruptedException {
        request.addMarker("cache-queue-take");

        // If the request has been canceled, don't bother dispatching it.
        //如果request請求取消,則停止分發
        if (request.isCanceled()) {
            request.finish("cache-discard-canceled");
            return;
        }

        // Attempt to retrieve this item from cache.
        Cache.Entry entry = mCache.get(request.getCacheKey());
        //嘗試讀取快取,如果快取為空,則將Request新增到網路請求佇列

        if (entry == null) {
            request.addMarker("cache-miss");
            // Cache miss; send off to the network dispatcher.
            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                mNetworkQueue.put(request);
            }
            return;
        }

        // If it is completely expired, just send it to the network.

        //如果快取過期,則將Request新增到網路請求佇列


        /*
        判斷快取過期的條件 Cache.java

                /*
        public boolean isExpired() {
            return this.ttl < System.currentTimeMillis();
            面試被問到,無奈,過期時間是否小於當前時間
        }

         */


        if (entry.isExpired()) {
            request.addMarker("cache-hit-expired");
            request.setCacheEntry(entry);
            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                mNetworkQueue.put(request);
            }
            return;
        }

        // We have a cache hit; parse its data for delivery back to the request.


        //存在快取,且快取沒有過期,並命中,則將快取結構解析,並回調給主執行緒


        request.addMarker("cache-hit");
        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.

            //回撥給主執行緒,一會分析這個類
            mDelivery.postResponse(request, response);
        } else {
            // Soft-expired cache hit. We can deliver the cached response,
            // but we need to also send the request to the network for
            // refreshing.
            request.addMarker("cache-hit-refresh-needed");
            request.setCacheEntry(entry);
            // Mark the response as intermediate.
            response.intermediate = true;

            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                // Post the intermediate response back to the user and have
                // the delivery then forward the request along to the network.
                mDelivery.postResponse(
                        request,
                        response,
                        new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    mNetworkQueue.put(request);
                                } catch (InterruptedException e) {
                                    // Restore the interrupted status
                                    Thread.currentThread().interrupt();
                                }
                            }
                        });
            } else {
                // request has been added to list of waiting requests
                // to receive the network response from the first request once it returns.
                mDelivery.postResponse(request, response);
            }
        }
    }

總結:
首先從快取佇列中取出請求,判斷請求是否取消,若果沒有取消,則判斷快取是否有響應,若果有響應,並且沒有過期,則解析快取並回調給主執行緒。如果沒有快取或者快取過期,則將請求新增到網路排程執行緒。接下來檢視網路排程執行緒。

5、網路排程執行緒

public class NetworkDispatcher extends Thread {

同樣整合Thread,直接檢視run方法;

    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (true) {
            try {

                //重點在這
                processRequest();
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    //如果quit,則中斷執行緒
                    Thread.currentThread().interrupt();
                    return;
                }
                VolleyLog.e(
                        "Ignoring spurious interrupt of NetworkDispatcher thread; "
                                + "use quit() to terminate it");
            }
        }
    }

可以看出呼叫了processRequest();方法
processRequest()原始碼:

 private void processRequest() throws InterruptedException {
        // Take a request from the queue.
        //從佇列中取出請求
        Request<?> request = mQueue.take();
        processRequest(request);
    }

    @VisibleForTesting
    void processRequest(Request<?> request) {
        long startTimeMs = SystemClock.elapsedRealtime();
        try {
            request.addMarker("network-queue-take");

            // If the request was cancelled already, do not perform the
            // network request.

            //如果request被取消,中斷網路請求

            if (request.isCanceled()) {
                request.finish("network-discard-cancelled");
                request.notifyListenerResponseNotUsable();
                return;
            }

            addTrafficStatsTag(request);

            // Perform the network request.

            //具體的網路請求在這裡實現mNetwork.performRequest(request);
            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.

            //如果伺服器返回304並且,request已經分發出去,則不進行第二次網路請求
            if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                request.finish("not-modified");
                request.notifyListenerResponseNotUsable();
                return;
            }

            // Parse the response here on the worker thread.
            //解析請求
            Response<?> response = request.parseNetworkResponse(networkResponse);
            request.addMarker("network-parse-complete");

            // Write to cache if applicable.
            // 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");
            }

            // Post the response back.
            request.markDelivered();

            //回撥給主執行緒進行顯示
            mDelivery.postResponse(request, response);
            request.notifyListenerResponseReceived(response);
        } catch (VolleyError volleyError) {
            volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
            parseAndDeliverNetworkError(request, volleyError);
            request.notifyListenerResponseNotUsable();
        } 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);
            request.notifyListenerResponseNotUsable();
        }
    }

總結:
判斷請求是否取消,若沒被取消,則請求網路,得到響應,回撥給主執行緒並將快取存入快取。

6、真正的網路請求實現類BasicNetwork實現方法為performRequest()

看原始碼:

    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            List<Header> responseHeaders = Collections.emptyList();
            try {
                // Gather headers.
                Map<String, String> additionalRequestHeaders =
                        getCacheHeaders(request.getCacheEntry());


                //真真真      的網路請求實現
                httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);


                int statusCode = httpResponse.getStatusCode();

                responseHeaders = httpResponse.getHeaders();
                // Handle cache validation.
                if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
                    Entry entry = request.getCacheEntry();
                    if (entry == null) {
                        return new NetworkResponse(
                                HttpURLConnection.HTTP_NOT_MODIFIED,
                                /* data= */ null,
                                /* notModified= */ true,
                                SystemClock.elapsedRealtime() - requestStart,
                                responseHeaders);
                    }
                    // Combine cached and response headers so the response will be complete.
                    List<Header> combinedHeaders = combineHeaders(responseHeaders, entry);
                    return new NetworkResponse(
                            HttpURLConnection.HTTP_NOT_MODIFIED,
                            entry.data,
                            /* notModified= */ true,
                            SystemClock.elapsedRealtime() - requestStart,
                            combinedHeaders);
                }

                // Some responses such as 204s do not have content.  We must check.
                InputStream inputStream = httpResponse.getContent();
                if (inputStream != null) {
                    responseContents =
                            inputStreamToBytes(inputStream, httpResponse.getContentLength());
                } else {
                    // Add 0 byte response as a way of honestly representing a
                    // no-content request.
                    responseContents = new byte[0];
                }

                // if the request is slow, log it.
                long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
                logSlowRequests(requestLifetime, request, responseContents, statusCode);

                if (statusCode < 200 || statusCode > 299) {
                    throw new IOException();
                }
                return new NetworkResponse(
                        statusCode,
                        responseContents,
                        /* notModified= */ false,
                        SystemClock.elapsedRealtime() - requestStart,
                        responseHeaders);
            } catch (SocketTimeoutException e) {
                attemptRetryOnException("socket", request, new TimeoutError());
            } catch (MalformedURLException e) {
                throw new RuntimeException("Bad URL " + request.getUrl(), e);
            } catch (IOException e) {
                int statusCode;
                if (httpResponse != null) {
                    statusCode = httpResponse.getStatusCode();
                } else {
                    throw new NoConnectionError(e);
                }
                VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
                NetworkResponse networkResponse;
                if (responseContents != null) {
                    networkResponse =
                            new NetworkResponse(
                                    statusCode,
                                    responseContents,
                                    /* notModified= */ false,
                                    SystemClock.elapsedRealtime() - requestStart,
                                    responseHeaders);
                    if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED
                            || statusCode == HttpURLConnection.HTTP_FORBIDDEN) {
                        attemptRetryOnException(
                                "auth", request, new AuthFailureError(networkResponse));
                    } else if (statusCode >= 400 && statusCode <= 499) {
                        // Don't retry other client errors.
                        throw new ClientError(networkResponse);
                    } else if (statusCode >= 500 && statusCode <= 599) {
                        if (request.shouldRetryServerErrors()) {
                            attemptRetryOnException(
                                    "server", request, new ServerError(networkResponse));
                        } else {
                            throw new ServerError(networkResponse);
                        }
                    } else {
                        // 3xx? No reason to retry.
                        throw new ServerError(networkResponse);
                    }
                } else {
                    attemptRetryOnException("network", request, new NetworkError());
                }
            }
        }
    }

主要是通過mBaseHttpStack.executeRequest(request, additionalRequestHeaders);請求網路,
接下來根據不同相應的餓狀態碼,返回不同的Response。
HttpStack具體的實現類是HttpUrlConnection和HttpClient,因此Volley具體底層的實現操作還是HttpUrlConnection和HttpClient。

7、如何將response回撥到主執行緒 ?ResponseDelivery的postResponse()方法

public interface ResponseDelivery {
    /** Parses a response from the network or cache and delivers it. */
    void postResponse(Request<?> request, Response<?> response);

    /**
     * Parses a response from the network or cache and delivers it. The provided Runnable will be
     * executed after delivery.
     */
    void postResponse(Request<?> request, Response<?> response, Runnable runnable);

    /** Posts an error for the given request. */
    void postError(Request<?> request, VolleyError error);
}

ResponseDelivery 是一個介面,來看它的實現的類ExecutorDelivery

原始碼:


/** Delivers responses and errors. */
public class ExecutorDelivery implements ResponseDelivery {
    /** Used for posting responses, typically to the main thread. */
    private final Executor mResponsePoster;

    /**
     * Creates a new response delivery interface.
     *
     * @param handler {@link Handler} to post responses on
     */

    //內部例項化了Handler,用於工作執行緒和UI執行緒的切換

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


    //利用handler進行轉發  handler.post(Runable)
    @Override
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
        request.markDelivered();
        request.addMarker("post-response");
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
    }

    //利用handler進行轉發  handler.post(Runable)
    @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 used for delivering network responses to a listener on the main thread. */
    @SuppressWarnings("rawtypes")
    private static 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() {
            // NOTE: If cancel() is called off the thread that we're currently running in (by
            // default, the main thread), we cannot guarantee that deliverResponse()/deliverError()
            // won't be called, since it may be canceled after we check isCanceled() but before we
            // deliver the response. Apps concerned about this guarantee must either call cancel()
            // from the same thread or implement their own guarantee about not invoking their
            // listener after cancel() has been called.

            // 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()) {



                //呼叫Request進行轉發

                mRequest.deliverResponse(mResponse.result);
            } else {

                //呼叫Request進行轉發
                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");
            }

            // If we have been provided a post-delivery runnable, run it.
            if (mRunnable != null) {
                mRunnable.run();
            }
        }
    }
}

呼叫Request.deliverResponse(mResponse.result)進行轉發

Request是一個抽象類,看看他的子類StringRequest 的實現方法

8、StringRequest原始碼

    @Override
    protected void deliverResponse(String response) {
        Response.Listener<String> listener;
        synchronized (mLock) {
            listener = mListener;
        }
        if (listener != null) {

//重點在這
            listener.onResponse(response);
//


        }
    }

在deliverResponse方法中呼叫了listener.onResponse(response);,最終將Response回撥給了Response.Listener的onResponse()方法。
看看我們剛開始的網路請求操作

StringRequest stringRequest = new StringRequest("http://www.baidu.com",

                        //最終會將事件回撥到這塊
                        new Response.Listener<String>() {
                            @Override
                            public void onResponse(String response) {
                                Log.d("TAG", response);
                            }
                        }, new Response.ErrorListener() {
                            @Override
                            public void onErrorResponse(VolleyError error) {
                                Log.e("TAG", error.getMessage(), error);
                            }
                        });

最後來張圖總結一下吧這裡寫圖片描述

總結:

Volley分為三類執行緒,分別是主執行緒,快取排程執行緒和網路排程執行緒,其中網路排程執行緒預設開啟四個。首先請求會加入快取佇列,快取排程執行緒從快取佇列中取出請求。如果找到該請求的快取響應就直接讀取快取響應並回調給主執行緒。如果沒有快取效應,或者快取已過期,則將請求加入到網路佇列,然後網路排程執行緒會輪詢取出網路佇列中的請求,取出後傳送HTTP請求,解析響應回撥給主執行緒,並將相應存入主執行緒。