Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> [Android]Volley源碼分析(二)Cache

[Android]Volley源碼分析(二)Cache

編輯:關於Android編程

Cache作為Volley最為核心的一部分,Volley花了重彩來實現它。本章我們順著Volley的源碼思路往下,來看下Volley對Cache的處理邏輯。

我們回想一下昨天的簡單代碼,我們的入口是從構造一個Request隊列開始的,而我們並不直接調用new來構造,而是將控制權反轉給Volley這個靜態工廠來構造。

com.android.volley.toolbox.Volley:

 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;
    }

參數HttpStack用於制定你的HttpStack實現機制,比如是采用apache的http-client還是HttpUrlConnection.當然如果你不指定,Volley也會根據你的sdk版本給出不同的策略。而這種HttpStack對象被Network對象包裝起來。上一節我們說過,為了構造平台統一的網絡調用,Volley通過橋接的方式來實現網絡調用,而橋接的接口就是這個Network.

Volley的核心在於Cache和Network。既然兩個對象已經構造完了,我們就可以生成request隊列RequestQueue.但是,為什麼要開啟queue.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();
        }
    }

上一節體系結構我們已經說了,Volley采用生產者和消費者的模式來產生反應堆,而這中反應必須要通過線程的方式來實現。調用了RequestQueue的start之後,將開啟一個Cache線程和一定數量的Network線程池。我們看到networkDispatcher的線程池數量由數組mDispatchers指定。而mDispatchers的賦值在RequestQueue的中:

public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

怎樣?是不是覺得Volley的代碼寫的非常的淺顯合理。好了,到RequestQueue.start開始,我們已經為我們的request構建好了它的上下文環境,我們接著只需要將它add到這個隊列中來就可以了;

 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();
            System.out.println("request.cacheKey = "+(cacheKey));
            if (mWaitingRequests.containsKey(cacheKey)) {
                // There is already a request in flight. Queue up.
                Queue> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList>();
                }
                stagedRequests.add(request);
                mWaitingRequests.put(cacheKey, stagedRequests);
            } else {
                // Insert 'null' queue for this cacheKey, indicating there is now a request in
                // flight.
                mWaitingRequests.put(cacheKey, null);
                mCacheQueue.add(request);
            }
            return request;
        }
    }

這段代碼綠色的部分是點睛之筆,有了暫存的概念,避免了重復的請求。我們add一個Request的時候,需要設置上這個RequestQueue。目的是為了結束的時候將自己從Queue中回收。我們這裡還可以看到一個簡單的狀態機:

request.addMarker("add-to-queue");

這個方法將在request不同的上下文中調用。方便以後查錯。之後Request會檢查是否需要進行Cache

        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }
我們的觀念裡面,似乎文本數據是不需要Cache的,你可以通過這個方法來實現是否要cache住你的東西,當然不限制你的數據類型。之後,如果你的請求不被暫存的話,那就被投入Cache反應堆。我們來看下mCacheQueue這個對象:

private final PriorityBlockingQueue> mCacheQueue =
        new PriorityBlockingQueue>();

我們看到mCacheQueue本質是一個PriorityBlockingQueue的線程安全隊列,而且在這個隊列裡面是可以進行優先級比較。ImageRequest對Request的優先級進行了指定:

com.android.volley.toolbox.ImageRequest:

    @Override
    public Priority getPriority() {
        return Priority.LOW;
    }

你可以自己指定你的Request的優先級別.我們回到CacheDispatcher消費者,CacheDispatcher繼承Thread。生成之後直接對Cache初始化mCache.initialize();初始化的目的在於獲得Cache中已經存在的數據。Cache的實現類是DiskBasedCache.java我們來看下它如何實現的初始化:

 @Override
    public synchronized void initialize() {
        if (!mRootDirectory.exists()) {
            if (!mRootDirectory.mkdirs()) {
                VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
            }
            return;
        }

        File[] files = mRootDirectory.listFiles();
        if (files == null) {
            return;
        }
        for (File file : files) {
            FileInputStream fis = null;
            try {
                fis = new FileInputStream(file);
                CacheHeader entry = CacheHeader.readHeader(fis);
                entry.size = file.length();
                putEntry(entry.key, entry);
            } catch (IOException e) {
                if (file != null) {
                   file.delete();
                }
            } finally {
                try {
                    if (fis != null) {
                        fis.close();
                    }
                } catch (IOException ignored) { }
            }
        }
    }

我們可以看出,Volley區別於其他Cache的另一個特點,就是存儲元數據,或者說自定義了數據格式。在文件的頭部增加了Volley的文件頭。這種做法不僅能從某方面保證了數據的安全性,也能很好的存儲數據元。
public static CacheHeader readHeader(InputStream is) throws IOException {
            CacheHeader entry = new CacheHeader();
            int magic = readInt(is);
            if (magic != CACHE_MAGIC) {
                // don't bother deleting, it'll get pruned eventually
                throw new IOException();
            }
            entry.key = readString(is);
            entry.etag = readString(is);
            if (entry.etag.equals("")) {
                entry.etag = null;
            }
            entry.serverDate = readLong(is);
            entry.ttl = readLong(is);
            entry.softTtl = readLong(is);
            entry.responseHeaders = readStringStringMap(is);
            return entry;
        }

我們從這段代碼裡面看到不少信息,Volley自己定義了自己的數據魔數,也按照Volley自己的規范來讀取元數據。

好的,我們初始化了Cache接下來就是CacheDispatcher的核心了。

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 request.
                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;
            }
        }

線程通過while true的方式進行輪詢,當然由於queue是阻塞的,因此不會造成費電問題。

Cache.Entry entry = mCache.get(request.getCacheKey());獲得數據的時候如果數據存在,則會將真實數據讀取出來。這就是Volley的LazyLoad。

if (entry.isExpired()) {//判斷是否失效
                    request.addMarker("cache-hit-expired");
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                }

這段代碼從時效性來判斷是否進行淘汰。我們回顧下剛才所看到的代碼,request在不同的上下文中總被標記為不同的狀態,這對後期維護有及其重要的意義。同時,為了保證接口的統一性,CacheDispatcher將自己的結果偽裝成為NetResponse。這樣對外部接口來說,不論你采用的是那種方式獲得數據,對我來說都當作網絡來獲取,這本身也是DAO模式存在的意義之一。

                request.addMarker("cache-hit");
                Response response = request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));
                request.addMarker("cache-hit-parsed");

request.parseNetworkResponse的目的是為了讓你的request轉成自己的數據對象。好了,到現在,對於Cache來說就差分發了,數據已經完全准備就緒了。我們上一講說道Request最終會拋給Delivery對象用來異步分發,這樣能有效避免分發造成的線程阻塞。我剛才說了,Cache會偽裝成為Netresponse來post數據,那也就是說對於Network的處理,這些部分也是一模一樣的。因此,後一篇關於NetworkDispatcher的管理我將省略掉這些。Volley中Delivery的實現類是:

com.android.volley.ExecutorDelivery.java

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);
            }
        };
    }
我們看到在它的中傳入了一個Handler,這個Handler如果是UI線程的Handler,那麼你的線程就是在UI線程中運行,避免了你自己post UI線程消息的問題。post出來數據將被封裝成為ResponseDeliveryRunnable 命令。這種命令跑在Handler所在的線程中.到此CacheDispatcher的基本流程就結束了,ResponseDeliveryRunnable中除了分發以外也會進行一些收尾的工作,看官們可以自己閱讀。





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