Volley全方位解析,帶你從源碼的角度徹底理解


Volley 是 Google 推出的輕量級 Android 異步網絡請求框架和圖片加載框架。在 Google I/O 2013 大會上發布。其適用場景是數據量小,通信頻繁的網絡操作。

主要特點:
(1). 擴展性強。Volley 中大多是基於接口的設計,可配置性強。
(2). 一定程度符合 Http 規范,包括返回 ResponseCode(2xx、3xx、4xx、5xx)的處理,請求頭的處理,緩存機制的支持等。並支持重試及優先級定義。
(3). 默認 Android2.3 及以上基於 HttpURLConnection,2.3 以下基於 HttpClient 實現。
    HttpURLConnection 和 AndroidHttpClient(HttpClient 的封裝)如何選擇及原因:
在 Froyo(2.2) 之前,HttpURLConnection 有個重大 Bug,調用 close() 函數會影響連接池,導致連接復用失效,所以在 Froyo 之前使用 HttpURLConnection 需要關閉 keepAlive。另外在 Gingerbread(2.3) HttpURLConnection 默認開啟了 gzip 壓縮,提高了 HTTPS 的性能,Ice Cream Sandwich(4.0) HttpURLConnection 支持了請求結果緩存。再加上 HttpURLConnection 本身 API 相對簡單,所以對 Android 來說,在 2.3 之后建議使用 HttpURLConnection,之前建議使用 AndroidHttpClient。
(4). 提供簡便的圖片加載工具。

 

 
(一).基本使用
     (a)各種Request的使用
volley使用及其簡單,我們只需要創建一個RequestQueue請求隊列,然后往隊列里面扔http請求即可,volley會
不斷從隊列里面取出請求然后交給一堆工作線程處理。這里的http請求是通過Request類來封裝的,我們只用創建Request對象,然后提供諸如url之類的參數即可。網絡操作全部是在子線程中處理的,我們不必擔心阻塞UI線程。
網絡請求的結果會異步返回給我們,我們只需要處理Request的回調即可。
Request本身是一個抽象類,不能直接創建實例,volley為我們實現了一些Request,比如StringRequest是普通http請求,JsonRequest可以封裝json數據,並將服務端的返回數據封裝成JsonObject,ImageRequest可以請求一張網絡圖片,並將服務端的返回數據封裝成Bitmap。
Request的使用分為三步: 1.創建RequestQueue隊列;2.創建Request對象,並加入隊列中;3.處理回調事件。

創建RequestQueue很簡單,調用Volley類的靜態方法newRequestQueue,並指定Context即可:

 

private RequestQueue mQueue = null;
// create request queue...
mQueue = Volley.newRequestQueue(this);//this代表當前的上下文

 

   1.StringRequest       
StringRequest的默認請求方式為GET,使用其他請求方式可以用其另一種重載形式。
String url = "http://192.168.56.1:8080";
StringRequest request = new StringRequest(url,new Response.Listener<String>()
		{
			@Override
			public void onResponse(String response)//success callbacks
			{
                //handle it
			}
			
		}, new Response.ErrorListener()//error callbacks
		{
			@Override
			public void onErrorResponse(VolleyError error)
			{
				error.printStackTrace();
			}
		});
		//add request to queue...
		mQueue.add(request);
Response.Listener處理請求成功時的回調。Response.ErrorListener處理失敗時的回調。代碼很簡單,不過多介紹。
         2.JsonRequest
這里的JsonObject是android內置的org.json庫,而不是自家的Gson,這點需要注意。
Map<String,String> params = new HashMap<String,String>();
		params.put("name","zhangsan");
		params.put("age","17");
		JSONObject jsonRequest = new JSONObject(params);
		Log.i(TAG,jsonRequest.toString());
		//如果json數據為空則是get請求,否則是post請求
		//如果jsonrequest不為null,volley會將jsonObject對象轉化為json字符串原封不動的發給服務器,並不會轉成k-v對,因為volley不知道應該如何轉化
       String url = "http://192.168.56.1:8080/volley_test/servlet/JsonServlet";
		JsonObjectRequest request = new JsonObjectRequest(url, jsonRequest, new Response.Listener<JSONObject>()
		{
			@Override
			public void onResponse(JSONObject response)
			{
		      //handle it
			}
			
		},new Response.ErrorListener()
		{
			@Override
			public void onErrorResponse(VolleyError error)
			{
				error.printStackTrace();
			}
		});
		mQueue.add(request);
3.ImageRequest
        ImageRequest可以控制圖片的寬高、照片品質。如果寬高比原始寬高小的話,將會進行壓縮。
ImageRequest request = new ImageRequest("http://192.168.56.1:8080/volley_test/image.jpg",new Response.Listener<Bitmap>()
		{
			@Override
			public void onResponse(Bitmap response)
			{
				mImageView.setImageBitmap(response);
			}
		},0,0, Bitmap.Config.ARGB_8888,new Response.ErrorListener()
		{//參數0 0 代表不壓縮
			@Override
			public void onErrorResponse(VolleyError error)
			{
				show(error.getMessage());
				//可以去顯示默認圖片
			}
		});
		
		mQueue.add(request);
當然,圖片加載還有另外兩種方式,我們放到下一部分再介紹。
         4.添加請求頭
有時候我們需要為Request添加請求頭,這時候可以去重寫Request的getHeaders方法。
String url = "http://192.168.56.1:8080/volley_test/servlet/JsonServlet";
JsonObjectRequest request = new JsonObjectRequest(url, null,resplistener,errlistener)
		{
			//添加自定義請求頭
			@Override
			public Map<String, String> getHeaders() throws AuthFailureError
			{
				Map<String,String> map = new HashMap<String,String>();
				map.put("header1","header1_val");
				map.put("header2","header2_val");
				return map;
			}
		};
  5.添加post請求參數
 添加Post請求參數可以重寫Request的GetParams方法,另需 修改請求參數為POST。
String url = "http://192.168.56.1:8080/volley_test/servlet/PostServlet";
StringRequest request = new StringRequest(Method.POST,url,listener, errorListener)
		{
			//post請求需要復寫getParams方法
			@Override
			protected Map<String, String> getParams() throws AuthFailureError
			{
				Map<String,String> map = new HashMap<String,String>();
				map.put("KEY1","value1");
				map.put("KEY2", "value2");
				return map;
			}			
		};
Request里面還有一些getXXX方法,大家參考代碼自己琢磨吧。
         6.取消請求
當Activity銷毀時,我們可能需要去取消一些網絡請求,這時候可以通過如下方式:
Request req = ...;
request.setTag("MAIN_ACTIVITY");
onDestroy()
{
    ...
    mQueue.cancelAll("MAIN_ACTIVITY");
}
為屬於該Activity的請求全部加上Tag,然后需要銷毀的時候調用cancelAll傳入tag即可。
RequestQueue#cancelAll還有另一種重載形式,可以傳入RequestFilter,自己指定一個過濾策略。
如果我們需要干掉所有請求,並且后續不再有網絡請求,可以干掉RequestQueue,調用其stop方法即可。
         7.全局共享RequestQueue
RequestQueue沒有必要每個Activity里面都創建,全局保有一個即可。這時候自然想到使用Application了。我們可以在Application里面創建RequestQueue,並向外暴露get方法。代碼很簡單,相信大家都會寫。   
        
     (b)圖片加載框架的使用
上面介紹了ImageRequest加載網絡圖片,但是這還不夠精簡,volley另提供了ImageLoader和NetworkImageView。當然,它們內部都是使用ImageRequest。
          1.ImageLoader
RequestQueue mQueue = ...;
ImageCache mCache = ...;
loader = new ImageLoader(mQueue,mImageCache);
ImageListener listener = ImageLoader.getImageListener(mImageView/*關聯的iamgeView*/,R.drawable.ic_launcher/*圖片加載時顯示*/, R.drawable.task_icon/*圖片加載失敗時顯示*/);
loader.get("http://192.168.56.1:8080/volley_test/image.jpg", listener, 0, 0);
首先創建RequestQueue不用多說,然后創建ImageLoader實例,同時需要傳入請求隊列和ImageCache。這個ImageCache是一個接口,我們需要自己實現,它是一個圖片緩存,通常我們都是結合LRUCache作為內存緩存使用,當然,如果你不想使用內存緩存,那就給它一個空實現即可。緊接着,需要為ImageLoader綁定ImageView以及圖片加載時、圖片加載失敗時的圖片資源。最后調用get方法傳入url請求網絡。
關於ImageCache緩存:
ImageCache接口:
 public interface ImageCache {
        public Bitmap getBitmap(String url);
        public void putBitmap(String url, Bitmap bitmap);
    }
可見,如果我們需要圖片緩存,那么實現getBitmap/putBitmap即可。每次調用ImageLoader#get方法時,都會首先從ImageCache中尋找(getBitmap),如果沒找到才將請求添加到隊列中。
應該怎樣實現ImageCache:
通常結合LRUCache。值得注意的是 ImageCache需要做成單例,全局共享。下面是ImageCache結合LRUCache的代碼:
/**
	 * @author Rowandjj
	 *圖片緩存需要做成單例,全局共享
	 */
	private static class LruImageCache implements ImageCache
	{
		private LruImageCache(){}
		
		private static LruImageCache instance = new LruImageCache();
		
		public static final LruImageCache getInstance()
		{
			return instance;
		}
		private static final String TAG = "LruImageCache";
		private final int maxSize = (int) (Runtime.getRuntime().maxMemory()/8);
		private LruCache<String,Bitmap> mCacheMap = new LruCache<String,Bitmap>(maxSize)
		{
			protected int sizeOf(String key, Bitmap value)
			{
				return value.getRowBytes()*value.getHeight();
			}
		};
		
		@Override
		public Bitmap getBitmap(String url)
		{
			Bitmap bitmap = mCacheMap.get(url);
			Log.i(TAG, "url = "+url+",cache:"+bitmap);
			return bitmap;
		}
		@Override
		public void putBitmap(String url, Bitmap bitmap)
		{
			Log.i(TAG, "put url = "+url);
			mCacheMap.put(url, bitmap);
		}
		
	}
   2.NetworkImageView
這是一個自定義控件,使用上跟ImaegView類似。
<com.android.volley.toolbox.NetworkImageView
            android:id="@+id/niv"
             android:layout_width="0dp"
             android:layout_height="match_parent"
             android:layout_weight="1" 
             >
ImageLoader loader = ...;
mNetImageView = findViewById(R.id.niv);
mNetImageView.setDefaultImageResId(R.drawable.ic_launcher);
mNetImageView.setErrorImageResId(R.drawable.task_icon);
mNetImageView.setImageUrl("http://192.168.56.1:8080/volley_test/image.jpg", loader);
代碼很簡單,不過多介紹。
    (c)定制request
volley是一個高度可擴展性的框架,我們可以在其基礎上,增加很多自己的東西,比如定制Request。
上面介紹了JsonRequest可以解析傳遞json數據,那么下面我們定制一個XMLRequest去解析XML數據。
定制Request需重寫Request#parseNetworkResponse解析網絡響應數據,Request#deliverResponse回調Listener.onResponse。
public class XMLRequest extends Request<XmlPullParser>
{
	private Listener<XmlPullParser> mListener;
	public XMLRequest(int method, String url, Listener<XmlPullParser> listener,
			ErrorListener errorListener)
	{
		super(method, url, errorListener);
		mListener = listener;
	}
	public XMLRequest(String url, Listener<XmlPullParser> listener,
			ErrorListener errorListener)
	{
		this(Method.GET, url, listener, errorListener);
	}
	@Override
	protected Response<XmlPullParser> parseNetworkResponse(NetworkResponse response)
	{
		try
		{
			String xmlString = new String(response.data,HttpHeaderParser.parseCharset(response.headers));
			XmlPullParser parser = Xml.newPullParser();
			parser.setInput(new StringReader(xmlString));//將返回數據設置給解析器
			return Response.success(parser,HttpHeaderParser.parseCacheHeaders(response));
		} catch (UnsupportedEncodingException e)
		{
			return Response.error(new VolleyError(e));
		} catch (XmlPullParserException e)
		{
			return Response.error(new VolleyError(e));
		}
	}
	@Override
	protected void deliverResponse(XmlPullParser response)
	{
		mListener.onResponse(response);
	}
}

使用方式:

/**
	 * xmlRequest 使用示例
	 */
	void test()
	{
		RequestQueue queue = Volley.newRequestQueue(context);
		String url = "";
		XMLRequest request = new XMLRequest(url,new Response.Listener<XmlPullParser>()
		{
			@Override
			public void onResponse(XmlPullParser response)
			{
				int type = response.getEventType();
				while(type != XmlPullParser.END_DOCUMENT)
				{
					switch (type)
					{
					case XmlPullParser.START_TAG:
						break;
					case XmlPullParser.END_TAG:
						break;
					default:
						break;
					}
					response.next();
				}
			}
		},new Response.ErrorListener()
		{
			@Override
			public void onErrorResponse(VolleyError error)
			{
			}
		});
	}

其實除了定制自己的Request,我們還可以定制好多東西,比如RequestQueue,參看RequestQueue的構造器:

public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery)
我們發現構造器需要指定緩存策略(默認硬盤緩存),線程池大小,結果分發策略等等。所有這些都是可配置的。volley這個框架寫的太牛逼了!
 
(二).源碼分析
僅知道如何使用那是遠遠不夠的,我們應該分析源碼看其內部實現原理,這樣才能夠進步。     
    1.主線
首先我們從整體上把握volley的工作流程,抓住其主線。
(1)請求隊列(RequestQueue)的創建
創建請求隊列的工作是從Volley#newRequestQueue開始的,這個方法內部會調用RequestQueue的構造器,同時指定一些基本配置,如緩存策略為硬盤緩存(DiskBasedCache),http請求方式為HttpURLConnection(level>9)和HttpClient(level<9),默認線程池大小為4。最后,調用RequestQueue#start啟動請求隊列。
看詳細代碼:
//Volley.java
 public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, null);
    }

調用另一個工廠方法:

//volley.java
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
          ... ...
        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;
    }
這里指定了硬盤緩存的位置為data/data/package_name/cache/volley/...,Network類(具體實現類是BasicNetwork)封裝了請求方式,並且根據當前API版本來選用不同的http工具。最后啟動了請求隊列。
下面看RequestQueue的構造器:
//RequestQueue.java
public RequestQueue(Cache cache, Network network) {
        this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
    }

指定默認線程池大小為4。

//RequestQueue.java  
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;
    }
這里的ResponseDelivery是請求結果的分發器(具體實現是ExecutorDelivery),內部將結果返回給主線程( 根據代碼中使用了Handler和UI線程的Looper大家就應該能猜到了),並處理回調事件。
下面看請求隊列的開始后會發生什么,查看RequestQueue#start方法:
 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();
        }
    }

邏輯很簡單,創建了CacheDispatcher和4個NetworkDispatcher個對象,然后分別啟動之。這個CacheDispatcher和NetworkDispatcher都是Thread的子類,其中CacheDispatcher處理走緩存的請求,而4個NetworkDispatcher處理走網絡的請求。CacheDispatcher通過構造器注入了緩存請求隊列(mCacheQueue),網絡請求隊列(mNetworkQueue),硬盤緩存對象(DiskBasedCache),結果分發器(mDelivery)。之所以也注入網絡請求隊列是因為一部分緩存請求可能已經過期了,這時候需要重新從網絡獲取。NetworkDispatcher除了緩存請求隊列沒有注入,其他跟CacheDispatcher一樣。到這里RequestQueue的任務就完成了,以后有請求都會交由這些dispatcher線程處理。

 

(2)請求的添加
請求的添加是通過RequestQueue#add完成的,add方法的邏輯是這樣的:
1.將請求加入mCurrentRequests集合
2.為請求添加序列號
3.判斷是否應該緩存請求,如果不需要,加入網絡請求隊列
4.如果有相同請求正在被處理,加入到相同請求等待隊列中,否則加入緩存請求隊列。
public Request add(Request 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;
        }
    }

 

通過這一方法,請求就被分發到兩個隊列中分別供CacheDispatcher和NetworkDispatcher處理。

(3)請求的處理
請求的處理是由CacheDispatcher和NetworkDispatcher來完成的,它們的run方法通過一個死循環不斷去從各自的隊列中取出請求,進行處理,並將結果交由ResponseDelivery。兩者處理思想一致但是具體邏輯還是有點區別,我們分別看看。
     1.走緩存的請求
CacheDispatcher.java#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 {
                // Get a request from the cache triage queue, blocking until
                // at least one is available.
                final Request request = mCacheQueue.take();
                request.addMarker("cache-queue-take");
                // If the request has been canceled, don't bother dispatching it.
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }
                // Attempt to retrieve this item from cache.
                Cache.Entry entry = mCache.get(request.getCacheKey());
                if (entry == null) {
                    request.addMarker("cache-miss");
                    // Cache miss; send off to the network dispatcher.
                    mNetworkQueue.put(request);
                    continue;
                }
                // If it is completely expired, just send it to the network.
                if (entry.isExpired()) {
                    request.addMarker("cache-hit-expired");
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                }
                // We have a cache hit; parse its data for delivery back to the reques
                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;
                    // 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) {
                                // 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;
            }
        }
    }


大體邏輯是這樣的,首先從隊列中取出請求,看其是否已被取消,若是則返回,否則繼續向下走。接着從硬盤緩存中通過緩存的鍵找到值(Cache.Entry),如果找不到,那么將此請求加入網絡請求隊列。否則對緩存結果進行過期判斷(這個需要請求的頁面指定了Cache-Control或者Last-Modified/Expires等字段,並且Cache-Control的優先級比Expires更高。否則請求一定是過期的),如果過期了,則加入網絡請求隊列。如果沒有過期,那么通過request.parseNetworkResponse方法將硬盤緩存中的數據封裝成Response對象(Request的parseNetworkResponse是抽象的,需要復寫)。最后進行新鮮度判斷,如果不需要刷新,那么調用ResponseDelivery結果分發器的postResponse分發結果。否則先將結果返回,再將請求交給網絡請求隊列進行刷新。【這段代碼讀起來很爽,google工程師寫的太贊了!】關於ResponseDelivery的具體過程我們留到下節講。

 2.走網絡的請求

@Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        Request request;
        while (true) {
            try {
                // Take a request from the queue.
                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");
                // If the request was cancelled already, do not perform the
                // network request.
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }
                // Tag the request (if API >= 14)
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                    TrafficStats.setThreadStatsTag(request.getTrafficStatsTag());
                }
                // Perform the network 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.
                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                    request.finish("not-modified");
                    continue;
                }
                // 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);
            } catch (VolleyError volleyError) {
                parseAndDeliverNetworkError(request, volleyError);
            } catch (Exception e) {
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
                mDelivery.postError(request, new VolleyError(e));
            }
        }
    }

這里的邏輯跟CacheDispatcher類似,也是構造Response對象,然后交由ResponseDelivery處理,但是這里的Response對象是通過NetworkResponse轉化的,而這個NetworkResponse是從網絡獲取的,這里最核心的一行代碼就是

 NetworkResponse networkResponse = mNetwork.performRequest(request);

這個mNetwork是BasicNetwork對象,我們看其performRequest的實現:

 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 {
                // Gather headers.
                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());
                // Handle cache validation.
                if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,
                            request.getCacheEntry().data, responseHeaders, true);
                }
                // Some responses such as 204s do not have content.  We must check.
                if (httpResponse.getEntity() != null) {
                  responseContents = entityToBytes(httpResponse.getEntity());
                } 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, statusLine);
                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);
                }
            }
        }
    }

這里最核心的是這一句:

httpResponse = mHttpStack.performRequest(request, headers);

它調用了HttpStack的performRequest,這個方法內部肯定會調用HttpURLConnection或者是HttpClient去請求網絡。這里我們就不必繼續向下跟源碼了。

 

(4)請求結果的分發與處理
請求結果的分發處理是由ResponseDelivery實現類ExecutorDelivery完成的,ExecutorDelivery是在RequestQueue的構造器中被創建的,並且綁定了UI線程的Looper:
 public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }

ExecutorDelivery內部有個自定義Executor,它僅僅是封裝了Handler,所有待分發的結果最終會通過handler.post方法交給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);
            }
        };
    }

下面看我們最關心的postResponse方法:

@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 postResponse(Request<?> request, Response<?> response) {
        postResponse(request, response, null);
    }

最終執行的是ResponseDeliveryRunnable這個Runnable:

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() {
            // 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()) {
                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");
            }
            // If we have been provided a post-delivery runnable, run it.
            if (mRunnable != null) {
                mRunnable.run();
            }
       }
}

這里我們看到了request.deliverResponse被調用了,這個方法通常會回調Listener.onResponse。哈哈,到這里,整個volley框架的主線就看完了!讀到這里,我真是由衷覺得google工程師牛逼啊!

 

 

2.一些支線細節
    (1)請求的取消
調用Request#cancel可以取消一個請求。cancel方法很簡單,僅將mCanceled變量置為true。而CacheDispatcher/NetworkDispatcher的run方法中在取到一個Request后會判斷是否請求取消了:
 if (request.isCanceled()) {
    request.finish("network-discard-cancelled");
      continue;
       }

如果請求取消就調用Request#finish,finish方法內部將調用與之綁定的請求隊列的finish方法,該方法內部會將請求對象在隊列中移除。

 

 

(2)請求隊列的終止
調用RequestQueue#stop可以終止整個請求隊列,並終止緩存請求線程與網絡請求線程:
public void stop() {
        if (mCacheDispatcher != null) {
            mCacheDispatcher.quit();
        }
        for (int i = 0; i < mDispatchers.length; i++) {
            if (mDispatchers[i] != null) {
                mDispatchers[i].quit();
            }
        }
    }

XXXDispatcher的quit方法會修改mQuit變量並調用interrupt使線程拋Interrupt異常,而Dispatcher捕獲到異常后會判斷mQuit變量最終while循環結束,線程退出。

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

 

 

  (3)ImageLoader
ImageLoader是對ImageRequest的封裝,這里重點關注下get方法:
public ImageContainer get(String requestUrl, ImageListener imageListener,
            int maxWidth, int maxHeight) {
      ...
        final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);
        // Try to look up the request in the cache of remote images.
        Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
        if (cachedBitmap != null) {
            // Return the cached bitmap.
            ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
            imageListener.onResponse(container, true);
            return container;
        }
 ...
        Request<?> newRequest =
            new ImageRequest(requestUrl, new Listener<Bitmap>() {
                @Override
                public void onResponse(Bitmap response) {
                    onGetImageSuccess(cacheKey, response);
                }
            }, maxWidth, maxHeight,
            Config.RGB_565, new ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    onGetImageError(cacheKey, error);
                }
            });
        mRequestQueue.add(newRequest);
        mInFlightRequests.put(cacheKey,
                new BatchedImageRequest(newRequest, imageContainer));
        return imageContainer;
    }
首先會從緩存中獲取如果沒有則構造ImageRequest並添加到請求隊列。
(4)關於緩存
Volley的CacheDispatcher工作時需要指定緩存策略,這個緩存策略即Cache接口,這個接口有兩個實現類,DiskBasedCache和NoCache,默認使用DiskedBasedCache。它會將請求結果存入文件中,以備復用。Volley是一個高度靈活的框架,緩存是可以配置的。甚至你可以使用自己的緩存策略。
可惜這個DiskBasedCache很多時候並不能被使用,因為CacheDispatcher即使從緩存文件中拿到了緩存的數據,還需要看該數據是否過期,如果過期,將不使用緩存數據。這就要求服務端的頁面可以被緩存,這個是由Cache-Control和Expires等字段決定的,服務端需要設定此字段才能使數據可以被緩存。否則緩存始終是過期的,最終總是走的網絡請求。
服務端假如是servlet寫的可以這樣做:
Servlet#doPost/doGet()
/*設置緩存*/
resp.setDateHeader("Last-Modified",System.currentTimeMillis());
resp.setDateHeader("Expires", System.currentTimeMillis()+10*1000*60);
resp.setHeader("Cache-Control","max-age=10000");
resp.setHeader("Pragma","Pragma");

Cache-Control字段的優先級高於Expires。這個可以從HttpHeaderParser#parseCacheHeaders方法中看到。

public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
        long now = System.currentTimeMillis();
        Map<String, String> headers = response.headers;
        long serverDate = 0;
        long serverExpires = 0;
        long softExpire = 0;
        long maxAge = 0;
        boolean hasCacheControl = false;
        String serverEtag = null;
        String headerValue;
        headerValue = headers.get("Date");
        if (headerValue != null) {
            serverDate = parseDateAsEpoch(headerValue);
        }
        headerValue = headers.get("Cache-Control");
        if (headerValue != null) {
            hasCacheControl = true;
            String[] tokens = headerValue.split(",");
            for (int i = 0; i < tokens.length; i++) {
                String token = tokens[i].trim();
                if (token.equals("no-cache") || token.equals("no-store")) {
                    return null;
                } else if (token.startsWith("max-age=")) {
                    try {
                        maxAge = Long.parseLong(token.substring(8));
                    } catch (Exception e) {
                    }
                } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
                    maxAge = 0;
                }
            }
        }
        headerValue = headers.get("Expires");
        if (headerValue != null) {
            serverExpires = parseDateAsEpoch(headerValue);
        }
        serverEtag = headers.get("ETag");
        // Cache-Control takes precedence over an Expires header, even if both exist and Expires
        // is more restrictive.
        if (hasCacheControl) {
            softExpire = now + maxAge * 1000;
        } else if (serverDate > 0 && serverExpires >= serverDate) {
            // Default semantic for Expire header in HTTP specification is softExpire.
            softExpire = now + (serverExpires - serverDate);
        }
        Cache.Entry entry = new Cache.Entry();
        entry.data = response.data;
        entry.etag = serverEtag;
        entry.softTtl = softExpire;
        entry.ttl = entry.softTtl;
        entry.serverDate = serverDate;
        entry.responseHeaders = headers;
        return entry;
    }

這個方法是由Request子類的parseNetworkResponse方法調用的:

Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response))
另外,在使用圖片加載框架時還有個ImageCache,它是作為圖片加載的一級緩存,跟上面的DiskedBasedCache沒有任何關系,大家不要混淆。ImageCache需要我們自己實現,通常結合LRUCache,具體第一部分已經介紹過了。
到這里我們把整個Volley框架全部分析完了。最后貼上Volley的整體架構圖:

 

下面這幅圖也很好:







免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM