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執行網絡請求,大概分爲如下的幾個步驟:
- 通過Volley類獲取一個RequestQueue對象。
- 創建Listener來處理網絡操作的返回值。Response.Listener和Response.ErrorListener分別用於處理正常的和異常的返回值。
- 傳入Url,HTTP Method,Listener等參數創建Request。
- 將前面創建的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()方法做的事情。可以看到它主要做了如下這樣幾件事情:
- 在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;
}
- 利用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;
}
- 創建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。