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

Volley源碼分析

編輯:關於Android編程

流程圖

盜張網上的流程圖
這裡寫圖片描述

源碼分析

構建RequestQueue

Volley 的調用比較簡單,通過 newRequestQueue(…) 函數新建並啟動一個請求隊列RequestQueue後,只需要往這個RequestQueue不斷 add Request 即可。我們來看看newRequestQueue(…)的代碼:

 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) {
//            當不顯示指定HttpStack時,若SDK版本9以上使用HttpUrlConnection,9以下使用HttpClient
            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時,若SDK版本9以上使用HttpUrlConnection,9以下使用HttpClient,當然我們可以在此處傳入OkHttpClient。(HttpClient的Api太多不好維護,現在已被廢棄,建議使用HttpUrlConnection)

Request這個類 代表一個網絡請求的抽象類。

public abstract class Request implements Comparable> `

我們通過構建一個Request類的非抽象子類(StringRequest、JsonRequest、ImageRequest 或自定義)對象,並將其加入到RequestQueue中來完成一次網絡請求操作。
Volley 支持 8 種 Http 請求方式 GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE, PATCH
Request 類中包含了請求 url,請求請求方式,請求 Header,請求 Body,請求的優先級等信息(實現了Comparable接口,這裡的優先級,僅僅是保證一個請求比另外一個請求先處理,而並不能保證一個高優先級請求一定會比低優先級的請求先回來
)。

因為是抽象類,子類必須重寫的兩個方法。

protected abstract Response parseNetworkResponse(NetworkResponse var1);

子類重寫此方法,將網絡返回的原生字節內容,轉換成合適的類型。此方法會在工作線程中被調用。

protected abstract void deliverResponse(T var1);

子類重寫此方法,將解析成合適類型的內容傳遞給它們的監聽回調。

RequestQueue中有如下元素:
一個Request被提交之後有幾個去處:
1。mCurrentRequests對應所有請求隊列。所有調用add的Request必然都會添加到這裡面來。
2.mNetworkQueue 對應網絡隊列。如果一個Request不需要緩存,那麼add之後會被直接添加到網絡隊列中。
3.mCacheQueue對應緩存請求。如果一個Request需要緩存,並且當前的RequestQueue中並沒有一個Request的getCacheKey和當前Request相同(可以認為一個請求),那麼加入緩存隊列,讓緩存工作線程來處理。
4.mWaitingRequests對應等待隊列。如果RequestQueue中已經有一個相同請求在處理,這裡只需要將這個Request放到等待隊列中,等之前的Request結果回來之後,進行處理即可(我們同時發出了三個一模一樣的Request,此時底層其實不必真正走三個網絡請求,而只需要走一個請求即可。所以Request1被add之後會被調度執行,而Request2 和Request3被加進來時,如果Request1還未執行完畢,那麼Request2和 Request3只需要等著Request1的結果即可。)

此外還有默認的一個緩存線程和四個網絡線程

  private final PriorityBlockingQueue mCacheQueue;
    private final PriorityBlockingQueue mNetworkQueue;
    private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
    private NetworkDispatcher[] mDispatchers;
    private CacheDispatcher mCacheDispatcher;
    private final Map> mWaitingRequests;
    private final Set mCurrentRequests;
啟動隊列:
接下來啟動隊列:
public void start() {
        this.stop();
        this.mCacheDispatcher = new CacheDispatcher(this.mCacheQueue, this.mNetworkQueue, this.mCache, this.mDelivery);
        this.mCacheDispatcher.start();

        for(int i = 0; i < this.mDispatchers.length; ++i) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(this.mNetworkQueue, this.mNetwork, this.mCache, this.mDelivery);
            this.mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }

    }
public void stop() {
        if(this.mCacheDispatcher != null) {
            this.mCacheDispatcher.quit();
        }

        for(int i = 0; i < this.mDispatchers.length; ++i) {
            if(this.mDispatchers[i] != null) {
                this.mDispatchers[i].quit();
            }
        }

    }

首先停止當前正在運行的線程,開啟一個緩存調度線程CacheDispatcher和 n 個網絡調度線程NetworkDispatcher,這裡 n 默認為 4,存在優化的余地,比如可以根據 CPU 核數以及網絡類型計算更合適的並發數。緩存調度線程不斷的從緩存請求隊列中取出 Request 去處理,網絡調度線程不斷的從網絡請求隊列中取出 Request 去處理。

緩存線程的run():

@Override
 public void run() {
 //初始化Cache
 mCache.initialize();
 Request request;
 while (true) {
   //阻塞 獲取一個Cache任務
   request = mCacheQueue.take();
  try {
   //已經被取消
    if (request.isCanceled()) {
    request.finish("cache-discard-canceled");
    continue;
   }
   //如果拿cache未果,放入網絡請求隊列
   Cache.Entry entry = mCache.get(request.getCacheKey());
   if (entry == null) {
    request.addMarker("cache-miss");
    mNetworkQueue.put(request);
    continue;
   }
   //緩存超時,硬過期,放入網絡請求隊列 
   if (entry.isExpired()) {
    request.addMarker("cache-hit-expired");
    request.setCacheEntry(entry);
    mNetworkQueue.put(request);
    continue;
   }
   //根據Cache構造Response
   Response response = request.parseNetworkResponse(
     new NetworkResponse(entry.data, entry.responseHeaders));
   //是否超過軟過期
   if (!entry.refreshNeeded()) {
    // 直接返回Cache
    mDelivery.postResponse(request, response);
   } else {
    request.setCacheEntry(entry);
    //設置中間結果
    response.intermediate = true;
    //發送中間結果
    final Request finalRequest = request;
    mDelivery.postResponse(request, response, new Runnable() {
     @Override
     public void run() {
      try {
       //返回中間結果後,將請求放入網絡隊列
       mNetworkQueue.put(finalRequest);
      } catch (InterruptedException e) {
       // Not much we can do about this.
      }
     }
    });
   }
  } catch (Exception e) {

  }
 }
}

這裡的Cache分為硬過期和軟過期:

public interface Cache {
    /**
     * Retrieves an entry from the cache.
     * @param key Cache key
     * @return An {@link Entry} or null in the event of a cache miss
     */
    public Entry get(String key);

    /**
     * Adds or replaces an entry to the cache.
     * @param key Cache key
     * @param entry Data to store and metadata for cache coherency, TTL, etc.
     */
    public void put(String key, Entry entry);

    /**
     * Performs any potentially long-running actions needed to initialize the cache;
     * will be called from a worker thread.
     */
    public void initialize();

    /**
     * Invalidates an entry in the cache.
     * @param key Cache key
     * @param fullExpire True to fully expire the entry, false to soft expire
     */
    public void invalidate(String key, boolean fullExpire);

    /**
     * Removes an entry from the cache.
     * @param key Cache key
     */
    public void remove(String key);

    /**
     * Empties the cache.
     */
    public void clear();

    /**
     * Data and metadata for an entry returned by the cache.
     */
    public static class Entry {
        /** The data returned from cache. */
        public byte[] data;

        /** ETag for cache coherency. */
        public String etag;

        /** Date of this response as reported by the server. */
        public long serverDate;

        /** TTL for this record. */
        public long ttl;

        /** Soft TTL for this record. */
        public long softTtl;

        /** Immutable response headers as received from server; must be non-null. */
        public Map responseHeaders = Collections.emptyMap();

        /** True if the entry is expired. */
        public boolean isExpired() {
            return this.ttl < System.currentTimeMillis();
        }

        /** True if a refresh is needed from the original data source. */
        public boolean refreshNeeded() {
            return this.softTtl < System.currentTimeMillis();
        }
    }

}

softTtl字段對應軟過期,ttl字段對應硬過期。如果ttl過期,那麼這個緩存永遠不會被使用了;如果softTtl沒有過期,這個數據直接返回;如果softTtl過期,那麼這次請求將有兩次返回,第一次返回這個Cahce,第二次返回網絡請求的結果:先進入頁面展示緩存,然後再刷新頁面

接下來看網絡線程的run():
執行網絡請求的工作線程,默認有4個線程,它不停地從網絡隊列中取任務執行。

public void run() {
 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
 Request request;
 while (true) {
  long startTimeMs = SystemClock.elapsedRealtime();
  // release previous request object to avoid leaking request object when mQueue is drained.
  request = null;
  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;
   }
   //通過Http棧實現客戶端發送網絡請求
   NetworkResponse networkResponse = mNetwork.performRequest(request);
   request.addMarker("network-http-complete");

   // 如果緩存軟過期,那麼會重新走網絡;如果server返回304,表示上次之後請求結果數據本地並沒有過期,所以可以直接用本地的,因為之前Volley已經發過一次Response了,所以這裡就不需要再發送Response結果了。
   if (networkResponse.notModified && request.hasHadResponseDelivered()) {
    request.finish("not-modified");
    continue;
   }

   Response response = request.parseNetworkResponse(networkResponse);
   request.addMarker("network-parse-complete");
   //更新緩存
   if (request.shouldCache() && response.cacheEntry != null) {
    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);
  }
 }
}
請求完成:
/**
     * Called from {@link Request#finish(String)}, indicating that processing of the given request
     * has finished.
     *
     * 
Releases waiting requests for request.getCacheKey() if * request.shouldCache().

*/ void finish(Request request) { // Remove from the set of requests currently being processed. synchronized (mCurrentRequests) { mCurrentRequests.remove(request); } if (request.shouldCache()) { synchronized (mWaitingRequests) { String cacheKey = request.getCacheKey(); Queue waitingRequests = mWaitingRequests.remove(cacheKey); if (waitingRequests != null) { if (VolleyLog.DEBUG) { VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.", waitingRequests.size(), cacheKey); } // Process all queued up requests. They won't be considered as in flight, but // that's not a problem as the cache has been primed by 'request'. mCacheQueue.addAll(waitingRequests); } } } }

(1). 首先從正在進行中請求集合mCurrentRequests中移除該請求。
(2). 然後查找請求等待集合mWaitingRequests中是否存在等待的請求,如果存在,則將等待隊列移除,並將等待隊列所有的請求添加到緩存請求隊列中,讓緩存請求處理線程CacheDispatcher自動處理。

請求取消:
public void cancelAll(RequestFilter filter)
public void cancelAll(final Object tag)

取消當前請求集合中所有符合條件的請求。
filter 參數表示可以按照自定義的過濾器過濾需要取消的請求。
tag 表示按照Request.setTag設置好的 tag 取消請求,比如同屬於某個 Activity 的。

NetworkImageView自動管理請求

    @Override
    protected void onDetachedFromWindow() {
        if (mImageContainer != null) {
            // If the view was bound to an image request, cancel it and clear
            // out the image from the view.
            mImageContainer.cancelRequest();
            setImageBitmap(null);
            // also clear out the container so we can reload the image if necessary.
            mImageContainer = null;
        }
        super.onDetachedFromWindow();
    }

此時自動觸發事件取消之前的請求。

private void loadImageIfNecessary(final boolean isInLayoutPass) {
  ***
          if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
            if (mImageContainer.getRequestUrl().equals(mUrl)) {
                // if the request is from the same URL, return.
                return;
            } else {
                // if there is a pre-existing request, cancel it if it's fetching a different URL.
                mImageContainer.cancelRequest();
                setDefaultImageOrNull();
            }
        }
***
}

相同的請求直接返回,不同的url則取消之前的請求

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