Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android之Volley框架源碼分析

Android之Volley框架源碼分析

編輯:關於Android編程

臨近畢業,各種事情各種忙。我也沒有認真專注寫過博客,最近仔細看了Volley框架的使用及其源碼,思前想後,想挑戰一下自己,還是寫一篇博客來分享,如有錯誤,歡迎吐槽。

Volley簡介

網絡請求是一個App很重要的一部分,android系統只是提供了一個平台,而android應用則是基於這個平台上進行展示數據,起到與用戶進行交互的作用,數據來源於服務端,而二者之間必須通過互聯網進行傳輸數據,在Android系統發布初期,很多開發者都是在Apache協會的Http協議的基礎上進行網絡請求方法的封裝,當然有小白和大神,難免會出現一些意想不到的問題。谷歌也是考慮到了這一點,在2013年I/O大會上發布了一個基於HttpURLConnection的網絡請求框架–Volley。
Volley把AsyncHttpClient和Universal-Image-Loader的優點集於了一身,既可以像AsyncHttpClient一樣非常簡單地進行HTTP通信,也可以像Universal-Image-Loader一樣輕松加載網絡上的圖片。
Volley適用於請求一些數據量不大且頻繁的數據,不適合請求一些大數據。
 

Volley源碼分析

我們知道Volley所有的請求都是基於Request這個抽象類,我們來看看這個類中的主要方法:

/**
     * Creates a new request with the given method (one of the values from {@link Method}),
     * URL, and error listener.  Note that the normal response listener is not provided here as
     * delivery of responses is provided by subclasses, who have a better idea of how to deliver
     * an already-parsed response.
     */
    public Request(int method, String url, Response.ErrorListener listener) {
        mMethod = method;
        mUrl = url;
        mErrorListener = listener;
        setRetryPolicy(new DefaultRetryPolicy());

        mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
    }

這裡Request的構造方法,沒什麼特別,就是把傳入的參數傳遞給對應的對象,以及設置了一個請求的優先級,這裡為默認。

abstract protected Response parseNetworkResponse(NetworkResponse response);

這是Request中最主要的兩個方法之一,每個繼承的類都必須具體實現這兩個方法。parseNetworkResponse方法中response是最後服務端返回的字節碼數據之後在子線程中被調用,進行解析數據,我們可以將這個字節碼數據轉換成我們想要的類型,比如StringRequest中,parseNetworkResponse方法的實現如下:

@Override
    protected Response parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException e) {
            parsed = new String(response.data);
        }
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
    }

  很明顯,這個將字節碼以指定的編碼格式轉換成了String字符串。

  abstract protected void deliverResponse(T response);

看著方法名就可以知道,這個是將最後解析過的請求數據交付給主線程,讓主線程進行處理,還記得我們在構造函數中傳入一個Listener的對象嗎,對,就是它。我們可以通過接口回調的方式將最終數據返回給主線程使用。還是一樣,我們看看StringRequest中的實現:

 @Override
    protected void deliverResponse(String response) {
        mListener.onResponse(response);
    }

  Request抽象類中還有一些其他的屬性,比如:

public Request setTag(Object tag) {
        mTag = tag;
        return this;
    }

  給每個Request附加一個標簽,便於管理。

 public Request setRetryPolicy(RetryPolicy retryPolicy) {
        mRetryPolicy = retryPolicy;
        return this;
    }

  設置一個自定義的在請求失敗的情況下重新請求的規則。

public void addMarker(String tag) {
        if (MarkerLog.ENABLED) {
            mEventLog.add(tag, Thread.currentThread().getId());
        } else if (mRequestBirthTime == 0) {
            mRequestBirthTime = SystemClock.elapsedRealtime();
        }
    }

  這個方法主要是打印日志的方式展示請求的過程進度。

/**
     * Returns the URL of this request.
     */
    public String getUrl() {
        return mUrl;
    }

    /**
     * Returns the cache key for this request.  By default, this is the URL.
     */
    public String getCacheKey() {
        return getUrl();
    }

這裡要注意的是把url作為一個緩存的Key。


我們知道我們需要把Request添加到RequestQueue中才能完成網絡請求,我們一般通過靜態方法
RequestQueue mQueue = Volley.newRequestQueue(MainActivity.this);
創建一個RequestQueue對象,
那麼我們看看RequestQueue的代碼:

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) {
            //sdk版本高於9時,創建一個HrlStack對象,裡面是網絡請求的具體邏輯
                stack = new HurlStack();
            } else {
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }
        //創建一個Network對象,裡面進行了網絡請求數據方法的二次封裝
        Network network = new BasicNetwork(stack);
        //傳入一個緩存對象以及netWork對象
        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        //啟動隊列
        queue.start();

        return queue;
    }

可以看出,在這個方法中,最後在創建RequestQueue的時候,傳入了一個緩存對象以及封裝好的網絡請求方法的對象,最後調用一個start方法,現在我們還沒有看它的具體實現,但是我們知道現在是萬事俱備,只欠東風了。就像一台搾汁機接通了電源並且開啟了開關,我們只需要扔進水果就行了。當然,我們這裡不能扔進水果…我們需要做的就是:

requestQueue.add(request);

把Request對象加入進去,讓它自己去跑,我們已經在Request中兩個抽象方法中寫好了解析以及最終數據的交付邏輯。我們來看看RequestQueue.add()中的代碼:
  

public  Request add(Request request) {
        // 在此request設置一個標記,指明此request屬於的RequestQueue
        request.setRequestQueue(this);
        synchronized (mCurrentRequests) {
        //將請求添加到隊列中
            mCurrentRequests.add(request);
        }

        // 處理請求按順序添加
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

        // 如果request不需要緩存,直接加入mNetworkQueue隊列,等待處理
        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)) {
                // 等待緩存的隊列中已經有了此請求
                Queue> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList>();
                }
                stagedRequests.add(request);
                //將新增加的request的集合對象覆蓋進去
                mWaitingRequests.put(cacheKey, stagedRequests);
                if (VolleyLog.DEBUG) {
                    VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
                }
            } else {
                //如果等待緩存隊列中只有一個請求,那麼value的值就為null
                mWaitingRequests.put(cacheKey, null);
                //將request添加到緩存工作隊列中
                mCacheQueue.add(request);
            }
            return request;
        }
    }

可能一部分讀者看到這裡就有點暈了,怎麼這麼多的隊列。我們一個個解釋。
  
mCurrentRequests,它的對象聲明是這樣的:
private final Set> mCurrentRequests = new HashSet>();
Set集合中存放的是無序且不重復的子對象。mCurrentRequests 在這裡包含了所有我們通過requestQueue.add(request); 方法添加的request,這個集合主要起到管理request的作用,在request請求在NetworkDispatcher線程中處理結束之後,無論請求成功或者失敗,都會調用Request的方法:
  

void finish(final String tag) {
        if (mRequestQueue != null) {
            mRequestQueue.finish(this);
        }

很明顯,Request中調用了mRequestQueue的finish方法,讓我們看下代碼:

 void finish(Request request) {
        // Remove from the set of requests currently being processed.
        synchronized (mCurrentRequests) {
            mCurrentRequests.remove(request);
        }

這裡我貼出了部分代碼。我們看到,當請求結束之後,就從mCurrentRequests移除掉了這個request。


  
  mNetworkQueue和mCacheQueue,它們的聲明是這樣的:
  private final PriorityBlockingQueue> mNetworkQueue =new PriorityBlockingQueue>();

  private final PriorityBlockingQueue> mCacheQueue =new PriorityBlockingQueue>();
  先來說下PriorityBlockingQueue這個類吧,這個類實現了implements BlockingQueue 這個接口,BlockingQueue是jdk中Concurrent包中新增的用於解決了多線程中,如何高效安全“傳輸”數據的問題,並且它能保證線程安全。所以將mNetworkQueue和mCacheQueue傳入子線程中,讓子線程從中獲取request進行處理。

mWaitingRequests:
private final Map>> mWaitingRequests =
new HashMap>>();

  mWaitingRequests是一個Map集合,裡面保存著鍵值對,以request的url為key。


接著往下我們看`queue.start();`這個方法中的代碼:
 /**
     * Starts the dispatchers in this queue.
     */
    public void start() {
        stop();  // 如果隊列在開啟狀態,關閉
        //創建一個緩存分發線程,並且啟動它
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        // 創建多個網絡請求的線程,並且啟動它們
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

NetworkDispatcher和CacheDispatcher都是繼承於Thread類。從上面的源碼可知,我們將默認啟動是4個子線程,這4個子線程一直在跑,執行RequestQueue中的Request。而mCacheDispatcher只開啟一個線程,用於緩存響應數據。


接著我們來看在NetworkDispatcher線程中是如何處理請求的:
  

public class NetworkDispatcher extends Thread {
    /** 需要處理的請求隊列 */
    private final BlockingQueue> mQueue;
    /** 封裝好了的用來處理網絡請求的接口 */
    private final Network mNetwork;
    /** 用於寫入緩存的對象 */
    private final Cache mCache;
    /** 用來請求之後的接口回調 */
    private final ResponseDelivery mDelivery;
    /** 用於退出此線程 */
    private volatile boolean mQuit = false;

    public NetworkDispatcher(BlockingQueue> queue,
            Network network, Cache cache,
            ResponseDelivery delivery) {
        mQueue = queue;
        mNetwork = network;
        mCache = cache;
        mDelivery = delivery;
    }

    /**
     * 結束此線程
     */
    public void quit() {
        mQuit = true;
        interrupt();
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    private void addTrafficStatsTag(Request request) {
        //Api>=14以上調用此方法(if API >= 14)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {                TrafficStats.setThreadStatsTag(
        request.getTrafficStatsTag());
        }
    }

    @Override
    public void run() {
    //設置線程為後台線程
 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        //全真循環,不斷獲取mQueue中的request進行處理
        while (true) {
            long startTimeMs = SystemClock.elapsedRealtime();
            Request request;
            try {
                // 獲取請求
                request = mQueue.take();
            } catch (InterruptedException e) {
                // 請求超時的時候,結束線程
                if (mQuit) {
                    return;
                }
                continue;
            }

            try {
                request.addMarker("network-queue-take");

                // 請求被取消
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }

                addTrafficStatsTag(request);

                // 執行請求
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                request.addMarker("network-http-complete");

                // 如果我們已經遞交過同樣的響應數據,那麼不會再次遞交
                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                    request.finish("not-modified");
                    continue;
                }

                // 在子線程中解析字節碼為我們想要的數據類型
                Response response = request.parseNetworkResponse(networkResponse);
                request.addMarker("network-parse-complete");

                // request需要緩存,並且response的緩存入口不為空
                if (request.shouldCache() && response.cacheEntry != null) {
                //以request的url為key,以response的緩存入口為value進行緩存
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                    request.addMarker("network-cache-written");
                }

                 //標志已經遞交,請求結束
                request.markDelivered();
                //接口回調
                mDelivery.postResponse(request, response);
            } catch (VolleyError volleyError) {
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                parseAndDeliverNetworkError(request, volleyError);
            } 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);
            }
        }
    }
/**
*解析錯誤的時候,接口回調對應的方法
*/
    private void parseAndDeliverNetworkError(Request request, VolleyError error) {
        error = request.parseNetworkError(error);
        mDelivery.postError(request, error);
    }
}

  Volley默認是開啟四個NetworkDispatcher這個線程,用於不斷從mQueue中獲取request進行處理請求。以mNetwork為處理器,並且在獲取成功之後回調在request中parseNetworkResponse方法進行解析成我們想要的數據。這個方法是不是很熟悉,沒錯,它就是我們在自定義Request中所要重新的抽象方法之一。在解析失敗時也會有相應的接口回調。
  在請求需要進行緩存並且響應數據緩存入口不為空的情況下,我們將以request的url為key,以response的緩存入口為value進行緩存。
  最後將解析好的數據通過 mDelivery.postResponse(request, response); 方法拋給主線程處理。
  


CacheDispatcher是一個緩存線程,讓我們看看它的代碼實現:

public class CacheDispatcher extends Thread {

    private static final boolean DEBUG = VolleyLog.DEBUG;

    /** 緩存隊列*/
    private final BlockingQueue> mCacheQueue;

    /** 網絡請求隊列*/
    private final BlockingQueue> mNetworkQueue;

    /** 緩存對象 */
    private final Cache mCache;

    /** 用於將最終數據傳遞的接口*/
    private final ResponseDelivery mDelivery;

    /** 線程是否存活 */
    private volatile boolean mQuit = false;

    public CacheDispatcher(
            BlockingQueue> cacheQueue, BlockingQueue> networkQueue,
            Cache cache, ResponseDelivery delivery) {
        mCacheQueue = cacheQueue;
        mNetworkQueue = networkQueue;
        mCache = cache;
        mDelivery = delivery;
    }

    /**
     * 結束線程
     */
    public void quit() {
        mQuit = true;
        interrupt();
    }

    @Override
    public void run() {
        if (DEBUG) VolleyLog.v("start new dispatcher");                  Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        // 初始化線程對象
        mCache.initialize();
        //全真循環,不斷獲取mCacheQueue中的request進行處理
        while (true) {
            try {
                //從mCacheQueue中獲取request對象,如果mCacheQueue為空,那麼會堵塞暫停,直至mCacheQueue添加了對象
                final Request request = mCacheQueue.take();
                request.addMarker("cache-queue-take");
                // 取消了緩存要求
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }

                // 獲取緩存的條目
            Cache.Entry entry =mCache.get(request.getCacheKey());
                if (entry == null) {
                    request.addMarker("cache-miss");
                        // 如果緩存中找不到緩存條目,那麼把請求扔mNetworkQueue,讓它去通過網絡請求數據
                    mNetworkQueue.put(request);
                    continue;
                }

                //如果緩存條目過期的話,那麼把request加入mNetworkQueue讓它去通過網絡重新請求數據
                if (entry.isExpired()) {
                    request.addMarker("cache-hit-expired");
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                }

                //有了有效的緩存對象,將緩存對象通過Request中定義的解析方法解析成我們想要的數據類型
                request.addMarker("cache-hit");
                Response response = request.parseNetworkResponse(
  new NetworkResponse(entry.data, entry.responseHeaders));
                request.addMarker("cache-hit-parsed");

                if (!entry.refreshNeeded()) {
                    // 不需要刷新的數據,可以直接遞交給主線程使用了
                    mDelivery.postResponse(request, response);
                } else {
                   //需要刷新的數據,將它它去通過網絡重新請求數據
                    request.addMarker("cache-hit-refresh-needed");
                    request.setCacheEntry(entry);

                    // 因為只是響應數據需要刷新,所以可以把response作為媒介,標記這個為true
                    response.intermediate = true;

                    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) {
                if (mQuit) {
                    return;
                }
                continue;
            }
        }
    }
}

Volley官網的框架結構流程圖:
這裡寫圖片描述

總結一下:Volley框架中封裝了網絡請求的具體方法在HurlStack類中,它就相當於一個加載器,BasicNetwork類對它進行了二次封裝。CacheDispatcher的目的就是從mCacheQueue中獲取request,然後再從緩存中判斷是否有對應的緩存,如果沒有或者過期等原因,會將此request拋給mNetworkQueue隊列,讓mNetworkQueue在NetworkDispatcher中,而在NetworkDispatcher中通過獲取mNetworkQueue的每個request,使用默認的HurlStack加載器(當然也可以自定義)去網絡上請求數據,最後講請求成功的響應數據再緩存起來。
  
至此。Volley框架的源碼算是解析完畢。那麼如何在實際開發項目中正確使用它呢,我會在下一篇中敘述。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved