Volley設計與實現分析 頂 原

Volley設計與實現分析


我們平時在開發Android應用的時候,不可避免地經常要通過網絡來進行數據的收發,而多數情況下都是會用HTTP協議來做這些事情。Android系統主要提供了HttpURLConnection和Apache HttpClient這兩種方式來幫我們進行HTTP通信。對於這兩種方式,Google官方的一份文檔 Android’s HTTP Clients 有做一個對比說明。是說,Apache HttpClient提供的API非常多,實現穩定,bug也比較少,正因如此,爲了保持API兼容性而非常難以做優化。HttpURLConnection的API比較少,故而比較容易做優化。但在Android 2.3之前,HttpURLConnection的實現又有一些比較嚴重的問題。Google官方建議在2.2及之前的Android上,用Apache HttpClient來執行HTTP請求,在2.3及之後的Android上,則用HttpURLConnection接口。

另外,HttpURLConnection和HttpClient的用法還是有些複雜的,提供的功能也比較基礎,如果不進行適當封裝的話,很容易寫出大量重複代碼。於是乎,一些Android網絡通信框架也就應運而生,比如說AsyncHttpClient等,它把HTTP所有的通信細節全部封裝在內部,同時提供更爲強大的API,我們只需簡單調用幾行代碼就可以完成通信操作。

Volley是Google提供的一個HTTP網絡庫,其功能大體是提供對通信細節的封裝,以方便網絡操作的調用,volley在內部實現中,會根據運行的android的版本,來決定是使用HttpURLConnection和Apache HttpClient接口;提供緩存機制,以加速網絡訪問;提供HTTP請求異步執行的能力。這裏我們就來看一下Volley的設計和實現。

Volley的獲取

我們先來了解一些怎麼下載到volley的代碼。我們可以通過如下的命令,下載的volley的代碼:

git clone https://android.googlesource.com/platform/frameworks/volley

下載了volley之後,將代碼導入到Android Studio中,根據volley工程的配置對於工具版本的要求,下載必要的工具,比如Android SDK platform,SDK Build tools,Gradle插件,或者根據本地工具鏈的版本,適當修改volley工程的設置,隨後就可以對volley進行編譯,產生aar包了。

Volley的使用

這裏我們通過一個簡單的例子來看一下volley的使用。比如,我們利用淘寶的接口抓取某一個IP地址的相關信息:

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "myapplication";
    private TextView mWeatherDataText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mWeatherDataText = (TextView)findViewById(R.id.weather_data);
        getIpData();
    }

    private void getIpData() {
        String RegionServiceUrl = "http://ip.taobao.com/service/getIpInfo.php?ip=112.65.189.212";
        RequestQueue requestQueue = Volley.newRequestQueue(this);
        StringRequest request = new StringRequest(RegionServiceUrl, new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                Log.i(TAG, "response = " + response);
                mWeatherDataText.setText(response);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.i(TAG, "error = " + error.getMessage());
                mWeatherDataText.setText(error.getMessage());
            }
        });
        requestQueue.add(request);
    }
}

主要關注getIpData(),實際是在這個方法中利用volley,執行了網絡請求。可以看到,使用volley執行網絡請求,大概分爲如下的幾個步驟:

  1. 通過Volley類獲取一個RequestQueue對象。
  2. 創建Listener來處理網絡操作的返回值。Response.Listener和Response.ErrorListener分別用於處理正常的和異常的返回值。
  3. 傳入Url,HTTP Method,Listener等參數創建Request。
  4. 將前面創建的Request添加到RequestQueue。

通過volley執行基本的網絡請求就是這麼簡單。要執行更復雜的網絡請求的話,可以自行探索。

Volley項目的結構

這裏我們以2016.5.27 clone下來的代碼爲基礎進行volley整個設計與實現的分析。我們先來看一下Volley的代碼結構:

com/android/volley/AuthFailureError.java
com/android/volley/Cache.java
com/android/volley/CacheDispatcher.java
com/android/volley/ClientError.java
com/android/volley/DefaultRetryPolicy.java
com/android/volley/ExecutorDelivery.java
com/android/volley/Network.java
com/android/volley/NetworkDispatcher.java
com/android/volley/NetworkError.java
com/android/volley/NetworkResponse.java
com/android/volley/NoConnectionError.java
com/android/volley/ParseError.java
com/android/volley/Request.java
com/android/volley/RequestQueue.java
com/android/volley/Response.java
com/android/volley/ResponseDelivery.java
com/android/volley/RetryPolicy.java
com/android/volley/ServerError.java
com/android/volley/TimeoutError.java
com/android/volley/VolleyError.java
com/android/volley/VolleyLog.java
com/android/volley/toolbox/AndroidAuthenticator.java
com/android/volley/toolbox/Authenticator.java
com/android/volley/toolbox/BasicNetwork.java
com/android/volley/toolbox/ByteArrayPool.java
com/android/volley/toolbox/ClearCacheRequest.java
com/android/volley/toolbox/DiskBasedCache.java
com/android/volley/toolbox/HttpClientStack.java
com/android/volley/toolbox/HttpHeaderParser.java
com/android/volley/toolbox/HttpStack.java
com/android/volley/toolbox/HurlStack.java
com/android/volley/toolbox/ImageLoader.java
com/android/volley/toolbox/ImageRequest.java
com/android/volley/toolbox/JsonArrayRequest.java
com/android/volley/toolbox/JsonObjectRequest.java
com/android/volley/toolbox/JsonRequest.java
com/android/volley/toolbox/NetworkImageView.java
com/android/volley/toolbox/NoCache.java
com/android/volley/toolbox/PoolingByteArrayOutputStream.java
com/android/volley/toolbox/RequestFuture.java
com/android/volley/toolbox/StringRequest.java
com/android/volley/toolbox/Volley.java

可以看到volley的所有代碼都在兩個package中,一個是com.android.volley,另一個是com.android.volley.toolbox,前者可以認爲是定義了volley的框架架構及接口,而後者則是相關接口的實現,提供實際的諸如HTTP網絡訪問、緩存等功能。

Volley設計

這裏先分析com.android.volley包,來看一下volley整體的框架架構。com.android.volley包中,類名以Error結尾的所有類都是Exception,用來指示某種異常。所有這些類的層次結構如下圖: 輸入圖片說明

對於這些Exception類,沒有需要過多說明的地方。接下來,我們從網絡請求的執行及執行結果的發佈的角度來看一下com.android.volley包中各個類之間的關係,如下圖: 輸入圖片說明

如我們在上面 Volley的使用 一節中看到的,應用程序在創建了Request之後,會將這個Request丟給RequestQueue,RequestQueue負責這個Request的處理及結果的Post。

RequestQueue在拿到Request之後,會根據這個Request是否應該緩存而將這個Request丟進NetworkQueue或CacheQueue,若Request應該緩存它會被放進CacheQueue中,若不需要則會被直接放進NetworkQueue中。NetworkQueue和CacheQueue都是類型爲PriorityBlockingQueue<Request<?>>的容器。

NetworkDispatcher和CacheDispatcher都是Thread。NetworkDispatcher主要的職責是通過Network執行HTTP請求並拋出執行結果。NetworkDispatcher線程在被啓動之後,會不斷地從NetworkQueue中取出Request來執行,執行之後得到NetworkReponse,NetworkReponse會得到解析並被重新構造爲Response,構造後的Response會被丟給ResponseDelivery,並由後者發佈給volley的調用者,同時在Request應該被緩存時,獲得的Response數據還會被放進Cache中。在Volley中,會創建NetworkDispatcher線程的線程池,其中包含固定的4個線程。

CacheDispatcher的主要職責則是訪問緩存,找到之前緩存的下載的數據,並通過ResponseDelivery發佈給volley的調用者,在沒找到時,則將Request丟進NetworkQueue中,以便於從網絡中獲取。在Volley中,只有一個CacheDispatcher線程。

Cache主要定義了緩存接口。RetryPolicy定義了緩存策略的接口,每個Request都會有自己的RetryPolicy,用於幫助Network確定重試的策略。ResponseDelivery定義了Request的發佈者的行爲,ExecutorDelivery是ResponseDelivery的一個實現,它主要是將結果post到一個Executor中。

Volley的實現

接下來通過代碼來看一下Volley的實現。

RequestQueue對象的創建

在Volley中,主要通過Volley類的newRequest來創建RequestQueue對象。Volley類就像膠水一樣,把Network的實現BasicNetwork/HurlStack/HttpClientStack和Cache的實現DiskBasedCache粘到一起,創建出可用的RequestQueue。其代碼如下:

public class Volley {

    /** Default on-disk cache directory. */
    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 An {@link HttpStack} to use for the network, or null for default.
     * @return A started {@link RequestQueue} instance.
     */
    public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);

        String userAgent = "volley/0";
        try {
            String packageName = context.getPackageName();
            PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
            userAgent = packageName + "/" + info.versionCode;
        } catch (NameNotFoundException e) {
        }

        if (stack == null) {
            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 = new BasicNetwork(stack);

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

不帶HttpStack參數的newRequestQueue()方法就是我們前面用到的那個,它會直接傳入null HttpStack調用帶HttpStack參數的newRequestQueue()方法。帶參數的newRequestQueue()方法的實現,感覺改爲下面這樣似乎更加清晰一點:

    public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {
                stack = new HurlStack();
            } else {
                String userAgent = "volley/0";
                try {
                    String packageName = context.getPackageName();
                    PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
                    userAgent = packageName + "/" + info.versionCode;
                } catch (NameNotFoundException e) {
                }

                // 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 = new BasicNetwork(stack);

        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();

        return queue;
    }

以上面的這段代碼爲基礎,來分析newRequestQueue()方法做的事情。可以看到它主要做了如下這樣幾件事情:

  1. 在HttpStack參數爲空時,創建HttpStack。HttpStack的職責主要是直接的執行網絡請求,並返回HttpResponse。BasicNetwork通過HttpStack執行網絡請求,對返回的HttpResponse做一些處理,然後構造NetworkResponse返回給調用者。這裏會根據系統當前的版本,來選擇是使用HttpClient接口還是HttpURLConnection接口,也就是使用HttpClientStack還是HurlStack,這兩個class都是實現了HttpStack接口。這裏可以來看一下HttpStack接口的定義:
public interface HttpStack {
    /**
     * Performs an HTTP request with the given parameters.
     *
     * A GET request is sent if request.getPostBody() == null. A POST request is sent otherwise,
     * and the Content-Type header is set to request.getPostBodyContentType().

     *
     * @param request the request to perform
     * @param additionalHeaders additional headers to be sent together with
     *         {@link Request#getHeaders()}
     * @return the HTTP response
     */
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
        throws IOException, AuthFailureError;

}

HurlStack的對象創建過程:

    /**
     * An interface for transforming URLs before use.
     */
    public interface UrlRewriter {
        /**
         * Returns a URL to use instead of the provided one, or null to indicate
         * this URL should not be used at all.
         */
        public String rewriteUrl(String originalUrl);
    }

    private final UrlRewriter mUrlRewriter;
    private final SSLSocketFactory mSslSocketFactory;

    public HurlStack() {
        this(null);
    }

    /**
     * @param urlRewriter Rewriter to use for request URLs
     */
    public HurlStack(UrlRewriter urlRewriter) {
        this(urlRewriter, null);
    }

    /**
     * @param urlRewriter Rewriter to use for request URLs
     * @param sslSocketFactory SSL factory to use for HTTPS connections
     */
    public HurlStack(UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) {
        mUrlRewriter = urlRewriter;
        mSslSocketFactory = sslSocketFactory;
    }

然後是HttpClientStack對象的創建過程:

    protected final HttpClient mClient;

    private final static String HEADER_CONTENT_TYPE = "Content-Type";

    public HttpClientStack(HttpClient client) {
        mClient = client;
    }
  1. 利用HttpStack創建BasicNetwork對象,其過程爲:
    protected final HttpStack mHttpStack;

    protected final ByteArrayPool mPool;

    /**
     * @param httpStack HTTP stack to be used
     */
    public BasicNetwork(HttpStack httpStack) {
        // If a pool isn't passed in, then build a small default pool that will give us a lot of
        // benefit and not use too much memory.
        this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
    }

    /**
     * @param httpStack HTTP stack to be used
     * @param pool a buffer pool that improves GC performance in copy operations
     */
    public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
        mHttpStack = httpStack;
        mPool = pool;
    }
  1. 創建DiskBasedCache對象。
    /** Default maximum disk usage in bytes. */
    private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;

    /** High water mark percentage for the cache */
    private static final float HYSTERESIS_FACTOR = 0.9f;

    /** Magic number for current version of cache file format. */
    private static final int CACHE_MAGIC = 0x20150306;

    /**
     * Constructs an instance of the DiskBasedCache at the specified directory.
     * @param rootDirectory The root directory of the cache.
     * @param maxCacheSizeInBytes The maximum size of the cache in bytes.
     */
    public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
        mRootDirectory = rootDirectory;
        mMaxCacheSizeInBytes = maxCacheSizeInBytes;
    }

    /**
     * Constructs an instance of the DiskBasedCache at the specified directory using
     * the default maximum cache size of 5MB.
     * @param rootDirectory The root directory of the cache.
     */
    public DiskBasedCache(File rootDirectory) {
        this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
    }

可以看到,volley創建了一個最大大小爲5MB的一個基於磁盤的緩存,緩存目錄的位置爲application的緩存目錄。 4. 傳遞BasicNetwork對象和DiskBasedCache對象,構造RequestQueue對象。 5. 執行RequestQueue的start()方法,啓動Request內部的線程。 整體地來看一下RequestQueue對象的構造,和start()初始化過程:

    /** Number of network request dispatcher threads to start. */
    private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;

    /** Cache interface for retrieving and storing responses. */
    private final Cache mCache;

    /** Network interface for performing requests. */
    private final Network mNetwork;

    /** Response delivery mechanism. */
    private final ResponseDelivery mDelivery;

    /** The network dispatchers. */
    private NetworkDispatcher[] mDispatchers;

    /** The cache dispatcher. */
    private CacheDispatcher mCacheDispatcher;

    private List<RequestFinishedListener> mFinishedListeners =
            new ArrayList<RequestFinishedListener>();

    /**
     * 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;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

    /**
     * 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
     */
    public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }

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

    /**
     * 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.
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

    /**
     * Stops the cache and network dispatchers.
     */
    public void stop() {
        if (mCacheDispatcher != null) {
            mCacheDispatcher.quit();
        }
        for (int i = 0; i < mDispatchers.length; i++) {
            if (mDispatchers[i] != null) {
                mDispatchers[i].quit();
            }
        }
    }

在RequestQueue對象的構造過程中,會創建ExecutorDelivery對象,該對象被用於發佈網絡請求的執行結果,向application的主UI線程中發佈,後面我們分析結果發佈時,會更詳細地來分析這個類。還會創建一個NetworkDispatcher的數組,其中包含了4個元素,也即是說,volley的網絡請求是通過後臺一個含有4個線程的固定線程池來執行的。 在RequestQueue的start()方法中,則主要是清理掉老的CacheDispatcher和NetworkDispatcher線程,創建新的並啓動他們。

Request對象的添加

這裏通過RequestQueue.add()的代碼,來具體看一下,向RequestQueue中添加一個Request的執行過程:

    /**
     * Adds a Request to the dispatch queue.
     * @param request The request to service
     * @return The passed-in 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);
        }

        // 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.
                Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList<Request<?>>();
                }
                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.put(cacheKey, null);
                mCacheQueue.add(request);
            }
            return request;
        }
    }

可以看到RequestQueue.add()爲Request設置了RquestQueue。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章