咱們平時在開發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接口。php
另外,HttpURLConnection和HttpClient的用法仍是有些複雜的,提供的功能也比較基礎,若是不進行適當封裝的話,很容易寫出大量重複代碼。因而乎,一些Android網絡通訊框架也就應運而生,好比說AsyncHttpClient等,它把HTTP全部的通訊細節所有封裝在內部,同時提供更爲強大的API,咱們只需簡單調用幾行代碼就能夠完成通訊操做。html
Volley是Google提供的一個HTTP網絡庫,其功能大致是提供對通訊細節的封裝,以方便網絡操做的調用,volley在內部實現中,會根據運行的android的版本,來決定是使用HttpURLConnection和Apache HttpClient接口;提供緩存機制,以加速網絡訪問;提供HTTP請求異步執行的能力。這裏咱們就來看一下Volley的設計和實現。java
咱們先來了解一些怎麼下載到volley的代碼。咱們能夠經過以下的命令,下載的volley的代碼:android
git clone https://android.googlesource.com/platform/frameworks/volley
下載了volley以後,將代碼導入到Android Studio中,根據volley工程的配置對於工具版本的要求,下載必要的工具,好比Android SDK platform,SDK Build tools,Gradle插件,或者根據本地工具鏈的版本,適當修改volley工程的設置,隨後就能夠對volley進行編譯,產生aar包了。git
這裏咱們經過一個簡單的例子來看一下volley的使用。好比,咱們利用淘寶的接口抓取某一個IP地址的相關信息:shell
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執行網絡請求,大概分爲以下的幾個步驟:數組
經過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網絡訪問、緩存等功能。架構
這裏先分析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中,主要經過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()方法作的事情。能夠看到它主要作了以下這樣幾件事情:
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; }
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; }
/** 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線程,建立新的並啓動他們。
這裏經過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。