<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Volley 源碼解讀

          共 4592字,需瀏覽 10分鐘

           ·

          2016-12-08 05:37

          Volley 的中文翻譯為“齊射、并發(fā)”,是在2013年的Google大會(huì)上發(fā)布的一款A(yù)ndroid平臺(tái)網(wǎng)絡(luò)通信庫,具有網(wǎng)絡(luò)請(qǐng)求的處理、小圖片的異步加載和緩存等功能,能夠幫助 Android APP 更方便地執(zhí)行網(wǎng)絡(luò)操作,而且更快速高效。Volley可是說是把AsyncHttpClient和Universal-Image-Loader的優(yōu)點(diǎn)集于了一身,既可以像AsyncHttpClient一樣非常簡(jiǎn)單地進(jìn)行HTTP通信,也可以像Universal-Image-Loader一樣輕松加載網(wǎng)絡(luò)上的圖片。除了簡(jiǎn)單易用之外,Volley在性能方面也進(jìn)行了大幅度的調(diào)整,它的設(shè)計(jì)目標(biāo)就是非常適合去進(jìn)行數(shù)據(jù)量不大,但通信頻繁的網(wǎng)絡(luò)操作,而對(duì)于大數(shù)據(jù)量的網(wǎng)絡(luò)操作,比如說下載文件等,Volley的表現(xiàn)就會(huì)非常糟糕。

          在Google IO的演講上,其配圖是一幅發(fā)射火弓箭的圖,有點(diǎn)類似流星。這表示,Volley特別適合數(shù)據(jù)量不大但是通信頻繁的場(chǎng)景。見下圖:

          Alt text

          目錄

          • [Volley特點(diǎn)]

          • [Volley執(zhí)行流程]

          • [Volley初始化]

          • [創(chuàng)建RequestQueue]

          • [DiskBasedCache]
          • [CacheDispatcher & NetworkDispatcher]

          • [Request]

          • [加入RequestQueue]

          • [Request#finish自己]

          • [取消請(qǐng)求]

          • [緩存處理]

          • [Request請(qǐng)求失敗重試機(jī)制]

          • [PoolingByteArrayOutputStream & ByteArrayPool]

          • [Volley加載圖片 ]

          • [Handler]

          • [volley gson改造 ]

          • [volley okhttp改造]

          Volley特點(diǎn)

          1. 自動(dòng)調(diào)度網(wǎng)絡(luò)請(qǐng)求;

            • Volley直接new 5個(gè)線程(默認(rèn)5個(gè)),讓多個(gè)線程去搶奪網(wǎng)絡(luò)請(qǐng)求對(duì)象(Request),搶到就執(zhí)行,搶不到就等待,直到有網(wǎng)絡(luò)請(qǐng)求到來。
          2. 可以加載圖片;

          3. 通過標(biāo)準(zhǔn)的 HTTP cache coherence(高速緩存一致性)緩存磁盤和內(nèi)存透明的響應(yīng);

          4. 支持指定請(qǐng)求的優(yōu)先級(jí);

            • 根據(jù)優(yōu)先級(jí)去請(qǐng)求數(shù)據(jù)
          5. 網(wǎng)絡(luò)請(qǐng)求cancel機(jī)制。我們可以取消單個(gè)請(qǐng)求,或者指定取消請(qǐng)求隊(duì)列中的一個(gè)區(qū)域;
            • 例如Activity finish后結(jié)束請(qǐng)求
          6. 框架容易被定制,例如,定制重試或者網(wǎng)絡(luò)請(qǐng)求庫;
            • 例如基于Okhttp的Volley

          Volley執(zhí)行流程

          Alt text

          Volley初始化

          創(chuàng)建RequestQueue

          使用Volley時(shí)我們需要先創(chuàng)建一個(gè)RequestQueue,如下

          RequestQueue queue = Volley.newRequestQueue(context);

          Volley.newRequestQueue(context)最終調(diào)用了如下方法

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

          HttpStack 是一個(gè)接口,主要用于請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù),并返回結(jié)果。默認(rèn)情況下stack是null,
          當(dāng)android版本>=9時(shí)使用HurlStack,否則使用HttpClientStack
          若用戶想要使用其他的網(wǎng)絡(luò)請(qǐng)求類庫,比如okhttp等就可以實(shí)現(xiàn)HttpStack接口,并在

          HttpResponse performRequest(Request request, Map additionalHeaders)
              throws IOException, AuthFailureError

          中調(diào)用okhttp進(jìn)行網(wǎng)絡(luò)請(qǐng)求,并把請(qǐng)求的結(jié)果封裝成一個(gè)
          HttpResponse返回即可,HttpResponse中包含了狀態(tài)碼,響應(yīng)頭,body信息。

          newRequestQueue中創(chuàng)建了一個(gè)BasicNetwork對(duì)象,BasicNetwork使用HttpStack執(zhí)行網(wǎng)絡(luò)請(qǐng)求,成功后返回一個(gè)NetworkResponse,NetworkResponse只是一個(gè)簡(jiǎn)單的記錄狀態(tài)碼,body,響應(yīng)頭,服務(wù)端是否返回304并且緩存過,執(zhí)行網(wǎng)絡(luò)請(qǐng)求時(shí)間的類。

          DiskBasedCache

          newRequestQueue 中還創(chuàng)建了一個(gè) RequestQueueRequestQueue 中持有一個(gè) DiskBasedCache 對(duì)象,
          DiskBasedCache 是把服務(wù)端返回的數(shù)據(jù)保持到本地文件中的類,默認(rèn)大小5M。
          緩存文件是一個(gè)二進(jìn)制文件,非文本文件,
          緩存文件的開頭有個(gè)特殊的整型魔數(shù)(CACHE_MAGIC),用于判斷是不是緩存文件。DiskBasedCache 初始化時(shí)會(huì)
          讀取特定文件夾下的所有文件的部分?jǐn)?shù)據(jù),包括響應(yīng)頭等極少數(shù)數(shù)據(jù),不包含body,當(dāng)調(diào)用DiskBasedCache的get(String key)方法時(shí)才讀取body部分,若文件開頭魔數(shù)不是 CACHE_MAGIC 則刪除。是的話就把讀取的數(shù)據(jù)保存到內(nèi)存中。代碼如下

          public synchronized void initialize() {
                  if (!mRootDirectory.exists()) {
          
                      return;
                  }
          
                  File[] files = mRootDirectory.listFiles();
                  if (files == null) {
                      return;
                  }
                  for (File file : files) {
                      BufferedInputStream fis = null;
                      try {
                          fis = new BufferedInputStream(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) { }
                      }
                  }
              }

          緩存文件除了可以保存 int,long 型數(shù)據(jù),還可以保存 String 字符串,Map,保存字符串時(shí)先保存字符串的長度( long 型),然后再保存 byte[]數(shù)組。
          保存 Map時(shí),先保存 Map 大小( int 型),然后再保存key和value。代碼如下

              static String readString(InputStream is) throws IOException {
                  int n = (int) readLong(is);
                  byte[] b = streamToBytes(is, n);
                  return new String(b, "UTF-8");
              }
              static Map readStringStringMap(InputStream is) throws IOException {
                  int size = readInt(is);
                  Map result = (size == 0)
                  ? Collections.emptyMap()
                  : new HashMap(size);
                  for (int i = 0; i < size; i++) {
                      String key = readString(is).intern();
                      String value = readString(is).intern();
                      result.put(key, value);
                  }
                  return result;
              }
              static long readLong(InputStream is) throws IOException {
                  long n = 0;
                  n |= ((read(is) & 0xFFL) << 0);
                  n |= ((read(is) & 0xFFL) << 8);
                  n |= ((read(is) & 0xFFL) << 16);
                  n |= ((read(is) & 0xFFL) << 24);
                  n |= ((read(is) & 0xFFL) << 32);
                  n |= ((read(is) & 0xFFL) << 40);
                  n |= ((read(is) & 0xFFL) << 48);
                  n |= ((read(is) & 0xFFL) << 56);
                  return n;
              }
          

          緩存文件名由cache key字符串的前半段字符串的hashCode拼接上cache key(網(wǎng)絡(luò)請(qǐng)求url)后
          半段字符串的hashCode值組成。代碼如下

          private String getFilenameForKey(String key) {
                  int firstHalfLength = key.length() / 2;
                  String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode());
                  localFilename += String.valueOf(key.substring(firstHalfLength).hashCode());
                  return localFilename;
              }

          當(dāng)緩存文件占用空間超過指定值時(shí),Volley只是簡(jiǎn)單的刪除的部分文件,刪除代碼如下

          private void pruneIfNeeded(int neededSpace) {
                  if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {
                      return;
                  }
          
                  Iterator> iterator = mEntries.entrySet().iterator();
                  while (iterator.hasNext()) {
                      Map.Entry entry = iterator.next();
                      CacheHeader e = entry.getValue();
                      boolean deleted = getFileForKey(e.key).delete();
                      if (deleted) {
                          mTotalSize -= e.size;
                      } 
                      iterator.remove();
          
                      if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * 0.9f) {
                          break;
                      }
                  }
              }

          可以看到刪除時(shí)只是遍歷mEntries,如果刪除成功并且剩余文件所占大小+新的緩存所需空間 則停止刪除,否則繼續(xù)刪除。

          CacheDispatcher & NetworkDispatcher

          RequestQueue 只是一個(gè)普通的類,沒有繼承任何類,RequestQueue 的 start 方法中創(chuàng)建了一個(gè) CacheDispatcher,和4個(gè)(默認(rèn)4個(gè))NetworkDispatcher,
          CacheDispatcher 和 NetworkDispatcher 都是 Thread 的直接子類,這5個(gè) Thread 就是前面提到的搶奪網(wǎng)絡(luò)請(qǐng)求對(duì)象的 Thread。
          調(diào)用start()時(shí)先調(diào)用了一下stop(),stop()中把5個(gè)線程的mQuit設(shè)為 true,然后調(diào)用interrupt()讓 Thread 的run不再執(zhí)行。
          代碼如下

          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();
                  }
          
                  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();
          
                          ...
          
                      }
                  }
              }
          
           /**
               * 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();
                      }
                  }
              }

          CacheDispatcher啟動(dòng)后先調(diào)用了一下DiskBasedCache的initialize()方法,這個(gè)方法要讀取文件,比較耗時(shí),Volley把他放到了Cache線程中。

          CacheDispatcher和NetworkDispatcher的run方法內(nèi)部很像,都是在 while (true)循環(huán)中從PriorityBlockingQueue中讀取Request,CacheDispatcher 獨(dú)享一個(gè)PriorityBlockingQueue,其余4各 NetworkDispatcher 共享一個(gè)PriorityBlockingQueue 。PriorityBlockingQueue是一個(gè)阻塞隊(duì)列,當(dāng)隊(duì)列里沒有Request時(shí)take()方法就會(huì)阻塞,直到有Request到來,PriorityBlockingQueue是線程安全的

          同一個(gè) Request 只能被1個(gè)線程獲取到。PriorityBlockingQueue 可以根據(jù)線程優(yōu)先級(jí)對(duì)隊(duì)列里的reqest進(jìn)行排序。

          Volley 的初始化到這就完成了,下面開始執(zhí)行網(wǎng)絡(luò)請(qǐng)求

          Request

          使用 Volley 進(jìn)行網(wǎng)絡(luò)請(qǐng)求時(shí)我們要把請(qǐng)求封裝成一個(gè) Request 對(duì)象,包括url,請(qǐng)求參數(shù),請(qǐng)求成功失敗 Listener,
          Volley 默認(rèn)給我們提供了 ImageRequest(獲取圖片),
          JsonObjectRequest、JsonArrayRequest(獲取json),StringRequest(獲取 String)。
          例如:

          Requestrequest=new StringRequest(Method.GET, "http://mogujie.com", new  Listener(){
                  @Override
                  public void onResponse(String response) {
                  }
          
              }, new Response.ErrorListener() {
                  @Override
                  public void onErrorResponse(VolleyError error) {
                  }
              });
              Volley.newRequestQueue(context).add(request);

          只需要把Request丟進(jìn)requestQueue中就可以。

          加入RequestQueue

          我們來看一下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();
                      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);
                          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;
                  }
              }
          

          add方法中首先設(shè)置了request所在的請(qǐng)求隊(duì)列,為了在用戶取消請(qǐng)求時(shí)能夠把request從requestQueue中移除掉。
          接下來設(shè)置了一下request的序列號(hào),序列號(hào)按添加順序依次從0開始編號(hào),同一個(gè)隊(duì)列中任何兩個(gè)request的序列號(hào)都不相同。序列號(hào)可以影響request的優(yōu)先級(jí)。
          request.addMarker("")用于調(diào)試(打印日志等)。
          通過查看源碼我們可以看到request默認(rèn)是需要緩存的。

          /** Whether or not responses to this request should be cached. */
              private boolean mShouldCache = true;

          若request不需要緩存則直接把request丟到mNetworkQueue,然后4個(gè) NetworkDispatcher 就可以"搶奪"request了,誰"搶"到誰就執(zhí)行網(wǎng)絡(luò)請(qǐng)求
          如需要緩存則先判斷一下mWaitingRequests中有沒有正在請(qǐng)求的相同的request(根據(jù)request的url判斷),沒有的話就把該request丟到mCacheQueue中,
          這樣 CacheDispatcher 執(zhí)行完之前的請(qǐng)求后就可以執(zhí)行該request了,若已經(jīng)有相同的request正在執(zhí)行,則只需保存一下該request,
          等相同的request執(zhí)行完后直接使用其結(jié)果就可。

          CacheDispatcher中獲取到request后先判斷一下request后有沒有取消,有的話就finish掉自己,然后等待下一個(gè)request的到來

           if (request.isCanceled()) {
                  request.finish("cache-discard-canceled");
                  continue;
              }

          接下來會(huì)從緩存中取緩存,沒有或者緩存已經(jīng)過期,就把request丟掉mNetworkQueue中,讓NetworkDisptcher去“搶奪”request

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

          若取到緩存且沒過期,則解析緩存

            Response response = request.parseNetworkResponse(
                  new NetworkResponse(entry.data, entry.responseHeaders));
          

          若不需要刷新則把request和response丟到ui線程中,回調(diào)request中的請(qǐng)求成功或失敗listener,同時(shí)finish自己
          若還需要刷新的話還需要把request丟到mNetworkQueue中,讓NetworkDispatcher去獲取數(shù)據(jù)。
          NetworkDispatcher在獲取到數(shù)據(jù)后執(zhí)行了如下代碼:

          // 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");
              }

          CacheDispatcher 才是處理緩存相關(guān)的,為什么 NetworkDispatcher 中還需要進(jìn)行以上的判斷呢?

          Request#finish自己

          前面我們提到過 CacheDispatcher 把相同的request放到了隊(duì)列中,當(dāng)獲取到數(shù)據(jù)后調(diào)用了request的finish方法,該方法又調(diào)用了
          mRequestQueue的finish方法。

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

          request的finish方法如下:

           void finish(Request 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);
                          }
                      }
                  }
              }
          }
          

          finish中取出了相同的request所在的隊(duì)列,然后把請(qǐng)求丟到了mCacheQueue中,丟到mCacheQueue后就會(huì)導(dǎo)致 CacheDispatcher 去執(zhí)行網(wǎng)絡(luò)請(qǐng)求,
          這時(shí)由于上次的請(qǐng)求已經(jīng)緩存了,所以可以直接使用上傳的數(shù)據(jù)了,到此為止request如何finish自己就介紹完了。

          取消請(qǐng)求

          我們可以調(diào)用request.cancel取消某個(gè)請(qǐng)求,也可以調(diào)用requestQueue的 cancelAll(RequestFilter filter) 或cancelAll(final Object tag) 取消多個(gè)請(qǐng)求。

          我們來看一下cancelAll(RequestFilter filter) 方法

           /**
               * Cancels all requests in this queue for which the given filter applies.
               * @param filter The filtering function to use
               */
              public void cancelAll(RequestFilter filter) {
                  synchronized (mCurrentRequests) {
                      for (Request request : mCurrentRequests) {
                          if (filter.apply(request)) {
                              request.cancel();
                          }
                      }
                  }
              }
          

          cancelAll需要一個(gè)RequestFilter,RequestFilter是一個(gè)接口,代碼如下

           /**
               * A simple predicate or filter interface for Requests, for use by
               * {@link RequestQueue#cancelAll(RequestFilter)}.
               */
              public interface RequestFilter {
                  public boolean apply(Request request);
              }
          

          我們可以通過實(shí)現(xiàn)自己的RequestFilter來取消特定的請(qǐng)求,比如我們可以在apply中判斷request的url是不是http://api.mogujie.org/gw/mwp.timelinemwp.homeListActionlet/1/?data=xxx,若是則返回true,否則返回false,這樣就可以結(jié)束特定url的請(qǐng)求。

          cancelAll(final Object tag)中自己實(shí)現(xiàn)了一個(gè)RequestFilter,根據(jù)tag來結(jié)束特定請(qǐng)求,代碼如下:

           /**
               * Cancels all requests in this queue with the given tag. Tag must be non-null
               * and equality is by identity.
               */
              public void cancelAll(final Object tag) {
                  if (tag == null) {
                      throw new IllegalArgumentException("Cannot cancelAll with a null tag");
                  }
                  cancelAll(new RequestFilter() {
                      @Override
                      public boolean apply(Request request) {
                          return request.getTag() == tag;
                      }
                  });
              }

          根據(jù)以上代碼可以看出,最終都是調(diào)用了request的cancel方法,cancel中只是簡(jiǎn)單的標(biāo)記了一下該request需要結(jié)束,代碼如下:

            /**
               * Mark this request as canceled.  No callback will be delivered.
               */
              public void cancel() {
                  mCanceled = true;
              }

          Volley會(huì)在執(zhí)行網(wǎng)絡(luò)請(qǐng)求前和回調(diào)監(jiān)聽前判斷一下標(biāo)記位是否已取消,若取消就結(jié)束自己,不再執(zhí)行網(wǎng)絡(luò)請(qǐng)求,也不回調(diào),從而達(dá)到取消請(qǐng)求的效果。代碼如下:

          NetworkDispatcher 和 CacheDispatcher

          
              @Override
              public void run() {
                 ...
                  while (true) {
                      try {
                         ...
                          final Request request = mCacheQueue.take();
                         ...
                          if (request.isCanceled()) {
                              request.finish("cache-discard-canceled");
                              continue;
                          }
                     }
          
                 }
             }

          ExecutorDelivery中

              public void run() {
          
                      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);
                      }

          緩存處理

          HttpHeaderParser.parseCacheHeaders
          處理字符串(分割等)得到響應(yīng)頭,并把響應(yīng)頭,body包裝到Cache.Entry中返回。
          當(dāng)NetworkDispatcher請(qǐng)求到數(shù)據(jù)后會(huì)判斷requset是否需要緩存,需要的話會(huì)調(diào)用DiskBasedCache的put(String key, Entry entry)方法,key是url,put中先調(diào)用了pruneIfNeeded,如果緩存新的數(shù)據(jù)后超過規(guī)定大小就先刪除一部分。

            /**
               * Prunes the cache to fit the amount of bytes specified.
               * @param neededSpace The amount of bytes we are trying to fit into the cache.
               */
              private void pruneIfNeeded(int neededSpace) {
                  if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {
                      return;
                  }
                  long before = mTotalSize;
          
                  Iterator> iterator = mEntries.entrySet().iterator();
                  while (iterator.hasNext()) {
                      Map.Entry entry = iterator.next();
                      CacheHeader e = entry.getValue();
                      boolean deleted = getFileForKey(e.key).delete();
                      if (deleted) {
                          mTotalSize -= e.size;
                      } 
                      iterator.remove();
                      if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
                          break;
                      }
                  }
              }
          

          put中可以看到數(shù)據(jù)被保存到了文件中。

           /**
               * Puts the entry with the specified key into the cache.
               */
              @Override
              public synchronized void put(String key, Entry entry) {
                  pruneIfNeeded(entry.data.length);
                  File file = getFileForKey(key);
                  try {
                      BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(file));
                      CacheHeader e = new CacheHeader(key, entry);
                      boolean success = e.writeHeader(fos);
                      if (!success) {
                          fos.close();
                          VolleyLog.d("Failed to write header for %s", file.getAbsolutePath());
                          throw new IOException();
                      }
                      fos.write(entry.data);
                      fos.close();
                      putEntry(key, e);
                      return;
                  } catch (IOException e) {
                  }
                  boolean deleted = file.delete();
                  if (!deleted) {
                      VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
                  }
              }

          Request請(qǐng)求失敗重試機(jī)制

          Volley的請(qǐng)求重試機(jī)制是在Request中設(shè)置的,這樣的好處是每一個(gè)Request都可以有自己的重試機(jī)制,代碼如下

            /**
               * 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#setRetryPolicy中只是記錄了一下RetryPolicy

          /**
               * Sets the retry policy for this request.
               *
               * @return This Request object to allow for chaining.
               */
              public Request setRetryPolicy(RetryPolicy retryPolicy) {
                  mRetryPolicy = retryPolicy;
                  return this;
              }

          DefaultRetryPolicy實(shí)現(xiàn)了RetryPolicy接口,根據(jù)接口我們可以看到DefaultRetryPolicy可以提供當(dāng)前超時(shí)時(shí)間,當(dāng)前重試次數(shù)等

          
          /**
           * Retry policy for a request.
           */
          public interface RetryPolicy {
          
              /**
               * Returns the current timeout (used for logging).
               */
              public int getCurrentTimeout();
          
              /**
               * Returns the current retry count (used for logging).
               */
              public int getCurrentRetryCount();
          
              /**
               * Prepares for the next retry by applying a backoff to the timeout.
               * @param error The error code of the last attempt.
               * @throws VolleyError In the event that the retry could not be performed (for example if we
               * ran out of attempts), the passed in error is thrown.
               */
              public void retry(VolleyError error) throws VolleyError;
          }

          BasicNetwork中performRequest中請(qǐng)求失敗(SocketTimeoutException,ConnectTimeoutException等)時(shí)會(huì)再次請(qǐng)求一次(默認(rèn)重試一次)
          若還是失敗就會(huì)拋出VolleyError異常
          具體代碼如下:

          public NetworkResponse performRequest(Request request) throws VolleyError {
                  ...
                  while (true) {
                      ...
                      try {
                          ...
                          httpResponse = mHttpStack.performRequest(request, headers);
                          ...
                          return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
                              SystemClock.elapsedRealtime() - requestStart);
                      } 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) {
                          ...
                      }
                  }
              }
          

          attemptRetryOnException代碼如下:

           private static void attemptRetryOnException(String logPrefix, Request request,
                      VolleyError exception) throws VolleyError {
                  RetryPolicy retryPolicy = request.getRetryPolicy();
                  int oldTimeout = request.getTimeoutMs();
          
                  try {
                      retryPolicy.retry(exception);
                  } catch (VolleyError e) {
                      request.addMarker(
                              String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));
                      throw e;
                  }
                  request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
              }

          request.getRetryPolicy()得到的是DefaultRetryPolicy類,DefaultRetryPolicy中retry方法

              public void retry(VolleyError error) throws VolleyError {
                  mCurrentRetryCount++;
                  mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
                  if (!hasAttemptRemaining()) {
                      throw error;
                  }
              }
          
              //Returns true if this policy has attempts remaining, false otherwise.
              protected boolean hasAttemptRemaining() {
                  return mCurrentRetryCount <= mMaxNumRetries;
              }

          request.getRetryPolicy() 得到的是 DefaultRetryPolicy 對(duì)象,request重試次數(shù)超過規(guī)定的次數(shù)時(shí)
          attemptRetryOnException 就會(huì)拋出 VolleyError,從而導(dǎo)致 performRequest() 方法中 while
          循環(huán)終止,同時(shí)繼續(xù)向上拋異常。

          PoolingByteArrayOutputStream & ByteArrayPool

          為了避免讀取服務(wù)端數(shù)據(jù)時(shí)反復(fù)的內(nèi)存申請(qǐng),Volley提供了PoolingByteArrayOutputStream和ByteArrayPool。

          我們先看一下PoolingByteArrayOutputStream的父類ByteArrayOutputStream

          /**
               * The byte array containing the bytes written.
               */
              protected byte[] buf;
          
              /**
               * The number of bytes written.
               */
              protected int count;

          ByteArrayOutputStream中提供了兩個(gè)protected 的byte[] buf 和 int count,buf用于write時(shí)保存數(shù)據(jù),count記錄buf已使用的大小,因?yàn)槎际莗rotected,所有在子類中可以對(duì)其進(jìn)行修改。

          我們來看一下ByteArrayOutputStream的write方法,可以看到write中調(diào)用了擴(kuò)展buf大小的expand方法,再來看一下expand的具體實(shí)現(xiàn)

            private void expand(int i) {
                  /* Can the buffer handle @i more bytes, if not expand it */
                  if (count + i <= buf.length) {
                      return;
                  }
          
                  byte[] newbuf = new byte[(count + i) * 2];
                  System.arraycopy(buf, 0, newbuf, 0, count);
                  buf = newbuf;
              }
          

          可以看到當(dāng)已使用的空間+要寫入的大小>buf大小時(shí),直接new 了一個(gè)兩倍大小的空間,并把原來的buf中的數(shù)據(jù)復(fù)制到了新的空間中,最后把新分配的空間賦值給了buf,原來的buf由于沒有被任何對(duì)象持有,最終會(huì)被回收掉。PoolingByteArrayOutputStream就是在重寫的expand對(duì)buf進(jìn)行了處理。

           @Override
              public synchronized void write(byte[] buffer, int offset, int len) {
                  Arrays.checkOffsetAndCount(buffer.length, offset, len);
                  if (len == 0) {
                      return;
                  }
                  expand(len);
                  System.arraycopy(buffer, offset, buf, this.count, len);
                  this.count += len;
              }
          
              /**
               * Writes the specified byte {@code oneByte} to the OutputStream. Only the
               * low order byte of {@code oneByte} is written.
               *
               * @param oneByte
               *            the byte to be written.
               */
              @Override
              public synchronized void write(int oneByte) {
                  if (count == buf.length) {
                      expand(1);
                  }
                  buf[count++] = (byte) oneByte;
              }

          接下來我們看一下PoolingByteArrayOutputStream是怎么復(fù)用內(nèi)存空間的。

          在執(zhí)行網(wǎng)絡(luò)請(qǐng)求的BasicNetwork我們看到new 了一個(gè)ByteArrayPool

           /**
               * @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));
              }

          我們看一下ByteArrayPool的構(gòu)造函數(shù)

           /** The buffer pool, arranged both by last use and by buffer size */
              private List mBuffersByLastUse = new LinkedList();
              private List mBuffersBySize = new ArrayList(64);
          
           /**
               * @param sizeLimit the maximum size of the pool, in bytes
               */
              public ByteArrayPool(int sizeLimit) {
                  mSizeLimit = sizeLimit;
              }
          

          可以看到ByteArrayPool只是記錄了一下最大的內(nèi)存池空間,默認(rèn)是4096 bytes,并創(chuàng)建了兩個(gè)保存byte[]數(shù)組的List。
          為什么要有兩個(gè)List , mBuffersBySize用于二分查找,(int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR);)。mBuffersByLastUse用于LRU(Least recently used,最近最少使用)置換算法。

          我們從BasicNetwork中看到讀取服務(wù)端數(shù)據(jù)時(shí)調(diào)用了entityToBytes方法

            /** Reads the contents of HttpEntity into a byte[]. */
              private byte[] entityToBytes(HttpEntity entity) throws IOException, ServerError {
                  PoolingByteArrayOutputStream bytes =
                          new PoolingByteArrayOutputStream(mPool, (int) entity.getContentLength());
                  byte[] buffer = null;
                  try {
                      InputStream in = entity.getContent();
                      if (in == null) {
                          throw new ServerError();
                      }
                      buffer = mPool.getBuf(1024);
                      int count;
                      while ((count = in.read(buffer)) != -1) {
                          bytes.write(buffer, 0, count);
                      }
                      return bytes.toByteArray();
                  } finally {
                      try {
                          // Close the InputStream and release the resources by "consuming the content".
                          entity.consumeContent();
                      } catch (IOException e) {
                          // This can happen if there was an exception above that left the entity in
                          // an invalid state.
                          VolleyLog.v("Error occured when calling consumingContent");
                      }
                      mPool.returnBuf(buffer);
                      bytes.close();
                  }
              }
          

          entityToBytes中又new 了一個(gè)PoolingByteArrayOutputStream,PoolingByteArrayOutputStream是繼承自java.io.ByteArrayOutputStream的。我們看一下PoolingByteArrayOutputStream構(gòu)造函數(shù)

           /**
               * Constructs a new {@code ByteArrayOutputStream} with a default size of {@code size} bytes. If
               * more than {@code size} bytes are written to this instance, the underlying byte array will
               * expand.
               *
               * @param size initial size for the underlying byte array. The value will be pinned to a default
               *        minimum size.
               */
              public PoolingByteArrayOutputStream(ByteArrayPool pool, int size) {
                  mPool = pool;
                  buf = mPool.getBuf(Math.max(size, DEFAULT_SIZE));
              }

          PoolingByteArrayOutputStream構(gòu)造函數(shù)中調(diào)用了mPool.getBuf并賦值給了父類的buf,所以以后調(diào)用write時(shí)都是把數(shù)據(jù)寫到了mPool.getBuf得到的byte[]數(shù)組中,也就是byte[]池中。getBuf代碼如下:

           /**
               * Returns a buffer from the pool if one is available in the requested size, or allocates a new
               * one if a pooled one is not available.
               *
               * @param len the minimum size, in bytes, of the requested buffer. The returned buffer may be
               *        larger.
               * @return a byte[] buffer is always returned.
               */
              public synchronized byte[] getBuf(int len) {
                  for (int i = 0; i < mBuffersBySize.size(); i++) {
                      byte[] buf = mBuffersBySize.get(i);
                      if (buf.length >= len) {
                          mCurrentSize -= buf.length;
                          mBuffersBySize.remove(i);
                          mBuffersByLastUse.remove(buf);
                          return buf;
                      }
                  }
                  return new byte[len];
              }

          由于內(nèi)存池是被多個(gè)NetworkDispatcher公用的,所以getBuf前加了synchronized,getBuf就是從byte[]池中找一個(gè)滿足大小的空間返回,并從List移除掉,若沒有足夠大的則new一個(gè)。再來看一下PoolingByteArrayOutputStream的write方法

           @Override
              public synchronized void write(byte[] buffer, int offset, int len) {
                  expand(len);
                  super.write(buffer, offset, len);
              }
          
              @Override
              public synchronized void write(int oneByte) {
                  expand(1);
                  super.write(oneByte);
              }

          可以看到write中調(diào)用了expand方法,這個(gè)方法不是ByteArrayOutputStream中的,而是PoolingByteArrayOutputStream重寫的,現(xiàn)在看一下expand方法

           /**
               * Ensures there is enough space in the buffer for the given number of additional bytes.
               */
              private void expand(int i) {
                  /* Can the buffer handle @i more bytes, if not expand it */
                  if (count + i <= buf.length) {
                      return;
                  }
                  byte[] newbuf = mPool.getBuf((count + i) * 2);
                  System.arraycopy(buf, 0, newbuf, 0, count);
                  mPool.returnBuf(buf);
                  buf = newbuf;
              }
          

          expand中沒有調(diào)用父類的expand方法,其與父類expand方法的卻別就是由每次的new byte[]變成了從byte[]池中獲取。

          在entityToBytes方法中我們看到用完之后又調(diào)用了mPool.returnBuf(buffer);把byte[]歸還給了byte[]池,代碼如下:

           /**
               * Returns a buffer to the pool, throwing away old buffers if the pool would exceed its allotted
               * size.
               *
               * @param buf the buffer to return to the pool.
               */
              public synchronized void returnBuf(byte[] buf) {
                  if (buf == null || buf.length > mSizeLimit) {
                      return;
                  }
                  mBuffersByLastUse.add(buf);
                  int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR);
                  if (pos < 0) {
                      pos = -pos - 1;
                  }
                  mBuffersBySize.add(pos, buf);
                  mCurrentSize += buf.length;
                  trim();
              }
          
              /**
               * Removes buffers from the pool until it is under its size limit.
               */
              private synchronized void trim() {
                  while (mCurrentSize > mSizeLimit) {
                      byte[] buf = mBuffersByLastUse.remove(0);
                      mBuffersBySize.remove(buf);
                      mCurrentSize -= buf.length;
                  }
              }

          Volley加載圖片

          Volley除了可以獲取json還可以加載圖片,用法如下:

          ImageRequest imageRequest = new ImageRequest(url,  
                  new Response.Listener() {  
                      @Override  
                      public void onResponse(Bitmap response) {  
                          imageView.setImageBitmap(response);  
                      }  
                  }, 0, 0, Config.RGB_565, new Response.ErrorListener() {  
                      @Override  
                      public void onErrorResponse(VolleyError error) {  
                          imageView.setImageResource(R.drawable.default_image);  
                      }  
                  }); 

          ImageRequest的構(gòu)造函數(shù)接收6個(gè)參數(shù),第一個(gè)參數(shù)就是圖片的URL地址。第二個(gè)參數(shù)是圖片請(qǐng)求成功的回調(diào),這里我們把返回的Bitmap參數(shù)設(shè)置到ImageView中。第三第四個(gè)參數(shù)分別用于指定允許圖片最大的寬度和高度,如果指定的網(wǎng)絡(luò)圖片的寬度或高度大于這里的最大值,則會(huì)對(duì)圖片進(jìn)行壓縮,指定成0的話就表示不管圖片有多大,都不會(huì)進(jìn)行壓縮。第五個(gè)參數(shù)用于指定圖片的顏色屬性,Bitmap.Config下的幾個(gè)常量都可以在這里使用,其中ARGB_8888可以展示最好的顏色屬性,每個(gè)圖片像素占據(jù)4個(gè)字節(jié)的大小,而RGB_565則表示每個(gè)圖片像素占據(jù)2個(gè)字節(jié)大小。第六個(gè)參數(shù)是圖片請(qǐng)求失敗的回調(diào),這里我們當(dāng)請(qǐng)求失敗時(shí)在ImageView中顯示一張默認(rèn)圖片。

          ImageRequest默認(rèn)采用GET方式獲取圖片,采用ScaleType.CENTER_INSIDE方式縮放圖片

           public ImageRequest(String url, Response.Listener listener, int maxWidth, int maxHeight,
                      ScaleType scaleType, Config decodeConfig, Response.ErrorListener errorListener) {
                  super(Method.GET, url, errorListener);
                  setRetryPolicy(new DefaultRetryPolicy(DEFAULT_IMAGE_TIMEOUT_MS, DEFAULT_IMAGE_MAX_RETRIES,
                          DEFAULT_IMAGE_BACKOFF_MULT));
                  mListener = listener;
                  mDecodeConfig = decodeConfig;
                  mMaxWidth = maxWidth;
                  mMaxHeight = maxHeight;
                  mScaleType = scaleType;
              }
          
              /**
               * For API compatibility with the pre-ScaleType variant of the constructor. Equivalent to
               * the normal constructor with {@code ScaleType.CENTER_INSIDE}.
               */
              @Deprecated
              public ImageRequest(String url, Response.Listener listener, int maxWidth, int maxHeight,
                      Config decodeConfig, Response.ErrorListener errorListener) {
                  this(url, listener, maxWidth, maxHeight,
                          ScaleType.CENTER_INSIDE, decodeConfig, errorListener);
              }

          我們來看一下ImageRequest具體執(zhí)行邏輯

            @Override
              protected Response parseNetworkResponse(NetworkResponse response) {
                  // Serialize all decode on a global lock to reduce concurrent heap usage.
                  synchronized (sDecodeLock) {
                      try {
                          return doParse(response);
                      } catch (OutOfMemoryError e) {
                          VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length, getUrl());
                          return Response.error(new ParseError(e));
                      }
                  }
              }
          
              /**
               * The real guts of parseNetworkResponse. Broken out for readability.
               */
              private Response doParse(NetworkResponse response) {
                  byte[] data = response.data;
                  BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
                  Bitmap bitmap = null;
                  if (mMaxWidth == 0 && mMaxHeight == 0) {
                      decodeOptions.inPreferredConfig = mDecodeConfig;
                      bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
                  } else {
                      // If we have to resize this image, first get the natural bounds.
                      decodeOptions.inJustDecodeBounds = true;
                      BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
                      int actualWidth = decodeOptions.outWidth;
                      int actualHeight = decodeOptions.outHeight;
          
                      // Then compute the dimensions we would ideally like to decode to.
                      int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
                              actualWidth, actualHeight, mScaleType);
                      int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
                              actualHeight, actualWidth, mScaleType);
          
                      // Decode to the nearest power of two scaling factor.
                      decodeOptions.inJustDecodeBounds = false;
                      // TODO(ficus): Do we need this or is it okay since API 8 doesn't support it?
                      // decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED;
                      decodeOptions.inSampleSize =
                          findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
                      Bitmap tempBitmap =
                          BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
          
                      // If necessary, scale down to the maximal acceptable size.
                      if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
                              tempBitmap.getHeight() > desiredHeight)) {
                          bitmap = Bitmap.createScaledBitmap(tempBitmap,
                                  desiredWidth, desiredHeight, true);
                          tempBitmap.recycle();
                      } else {
                          bitmap = tempBitmap;
                      }
                  }
          
                  if (bitmap == null) {
                      return Response.error(new ParseError(response));
                  } else {
                      return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
                  }
              }
          

          doParse中對(duì)圖片進(jìn)行了縮放處理,可以看到當(dāng)mMaxWidth == 0 && mMaxHeight == 0時(shí),沒有做過多的處理,這種情況適合于不打的圖片,否則容易導(dǎo)致內(nèi)存溢出。else中對(duì)圖片進(jìn)行了縮放處理,首先創(chuàng)建一個(gè)Options 對(duì)象,并設(shè)置decodeOptions.inJustDecodeBounds = true,然后使用BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);時(shí)就可以只獲取圖片的大小,而不需要把data數(shù)組轉(zhuǎn)換成bitmap,
          BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);可以直接從內(nèi)存中獲取圖片的寬高,之后就可以使用
          int actualWidth = decodeOptions.outWidth;
          int actualHeight = decodeOptions.outHeight;
          來獲取圖片寬高了。

          獲取到圖片寬高后需要通過findBestSampleSize找到一個(gè)合適的縮放比例并賦值給decodeOptions.inSampleSize,縮放比例一定是2的n次冪。即使不是2的冪最終也會(huì)按2的n次冪處理

           static int findBestSampleSize(
                      int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
                  double wr = (double) actualWidth / desiredWidth;
                  double hr = (double) actualHeight / desiredHeight;
                  double ratio = Math.min(wr, hr);
                  float n = 1.0f;
                  while ((n * 2) <= ratio) {
                      n *= 2;
                  }
          
                  return (int) n;
              }
            /**
                   * If set to a value > 1, requests the decoder to subsample the original
                   * image, returning a smaller image to save memory. The sample size is
                   * the number of pixels in either dimension that correspond to a single
                   * pixel in the decoded bitmap. For example, inSampleSize == 4 returns
                   * an image that is 1/4 the width/height of the original, and 1/16 the
                   * number of pixels. Any value <= 1 is treated the same as 1. Note: the
                   * decoder uses a final value based on powers of 2, any other value will
                   * be rounded down to the nearest power of 2.
                   */
                  public int inSampleSize;

          獲取到縮放比例后就可以通過一下代碼獲取到圖片了

          decodeOptions.inJustDecodeBounds = false;
          decodeOptions.inSampleSize =
                          findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
          Bitmap tempBitmap =BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
          

          Handler

          Volley進(jìn)行網(wǎng)絡(luò)請(qǐng)求是在非ui線程中進(jìn)行的,回調(diào)是怎么跑到ui線程中執(zhí)行的呢?
          Volley在創(chuàng)建RequestQueue時(shí)new 了一個(gè) ExecutorDelivery(new Handler(Looper.getMainLooper()))

           public RequestQueue(Cache cache, Network network, int threadPoolSize) {
                  this(cache, network, threadPoolSize,
                          new ExecutorDelivery(new Handler(Looper.getMainLooper())));
              }
          

          以前使用handler時(shí)我們都是直接new Handler(),沒有跟任何參數(shù),但不跟參數(shù)的Handler默認(rèn)使用的是該線程獨(dú)有的Looper,默認(rèn)情況下ui線程是有Looper的而其他線程是沒有Looper的,在非UI線程中直接new Handler()會(huì)出錯(cuò),我們可以通過Looper.getMainLooper()得到ui線程的Looper,這樣任何線程都可以使用ui線程的Looper了,而且可以在任何線程中創(chuàng)建Handler,并且使用handler發(fā)送消息時(shí)就會(huì)跑到ui線程執(zhí)行了。

          也就是說如果我們想在任何線程中都可以創(chuàng)建Hadler,并且handler發(fā)送的消息要在ui線程執(zhí)行的話,就可以采用這種方式創(chuàng)建Handler

          new Handler(Looper.getMainLooper());

          --> handler之java工程版

          volley gson改造

          在實(shí)際開發(fā)中我們會(huì)遇到對(duì)文字表情的混排處理,一種做法是服務(wù)端直接返回轉(zhuǎn)意后的字符串(比如 ? 用 \:wx 代替),然后客戶端每次都要在ui線程中解析字符串轉(zhuǎn)換成Spanned,若是在ListView中,滾動(dòng)就會(huì)非常卡頓。

          我們可以自定義一個(gè)XJsonRequest并繼承自Request,同時(shí)為XJsonRequest增加一個(gè)注冊(cè)gson類型轉(zhuǎn)換的方法,并把泛型參數(shù)中圖文混排字段設(shè)置為Spanned,然后在Response parseNetworkResponse(NetworkResponse response)中把圖文混拍json轉(zhuǎn)換成Spanned即可,由于Response parseNetworkResponse(NetworkResponse response)是在非ui線程中執(zhí)行的,所已不會(huì)導(dǎo)致ANR。

          demo如下:

          先看效果圖:

          Alt text

          代碼如下:

          String json = "{\"nickname\":\"xiaoming\",\"age\":10,\"emoj\":\"[:f001}[:f002}[:f003}hello[:f004}[:f005}\"}";  
                  jsonTv.setText(json);  
          
                  Gson gson = new GsonBuilder().registerTypeAdapter(Spanned.class, new String2Spanned()).create();  
          
                  Person person = gson.fromJson(json, Person.class);  
          
                  editText.append(person.getNickname());  
                  editText.append(" ");  
                  editText.append(person.getAge() + " ");  
                  editText.append(person.getEmoj());  
          
                  tv.append(Spanned2String.parse(editText.getText())); 
          public  class Person {  
              private Spanned emoj;  
              private String nickname;  
              private int age;  
          }
          
          /** 
           * 字符串轉(zhuǎn)表情Spanned 表情格式 [:f000} 
           *  
           * @author wanjian 
           * 
           */  
          public class String2Spanned extends TypeAdapter {  
          
              static Pattern pattern;  
          
              static Map map = new HashMap();  
          
              static ImageGetter imageGetter = new ImageGetter() {  
                  @Override  
                  public Drawable getDrawable(String source) {  
                      int id = Integer.parseInt(source);  
                      Drawable d = MyApplication.application.getResources().getDrawable(id);  
                      d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());  
                      return d;  
                  }  
              };  
          
              static {  
          
                  pattern = Pattern.compile("\\[:f\\d{3}\\}"); 
                  //省略部分代碼 
                  map.put("[:f000}", R.drawable.f000);  
                  map.put("[:f001}", R.drawable.f001);  
                  map.put("[:f002}", R.drawable.f002);  
                  map.put("[:f003}", R.drawable.f003);  
                  map.put("[:f004}", R.drawable.f004);  
                  map.put("[:f005}", R.drawable.f005);  
              }  
          
              @Override  
              public Spanned read(JsonReader in) throws IOException {  
                  if (in.peek() == JsonToken.NULL) {  
                      in.nextNull();  
                      return null;  
                  }  
          
                  String origStr = in.nextString();  
                  Matcher matcher = pattern.matcher(origStr);  
          
                  StringBuilder stringBuilder = new StringBuilder();  
          
                  int last = 0;  
                  while (matcher.find()) {  
          
                      int s = matcher.start();  
                      int e = matcher.end();  
                      stringBuilder.append(origStr.substring(last, s));  
          
                      String group = matcher.group();  
                      Integer emojId = map.get(group);  
                      if (emojId == null) {  
                          stringBuilder.append(group);  
                      } else {  
                          stringBuilder.append("");  
                      }  
          
                      last = e;  
          
                  }  
                  stringBuilder.append(origStr.substring(last, origStr.length()));  
                          // String ss = "";  
          
                  return Html.fromHtml(stringBuilder.toString(), imageGetter, null);  
              }  
          
              @Override  
              public void write(JsonWriter arg0, Spanned arg1) throws IOException {  
          
              }  
          
          }  
          
          import org.apache.commons.lang.StringEscapeUtils;  
          /** 
           * 表情轉(zhuǎn) 字符串 
           * @author wanjian 
           * 
           */  
          public class Spanned2String {  
          
              //  

          //

          hello

          static Pattern pattern; static Mapmap=new HashMap<>(); static{ pattern=Pattern.compile(""); //省略部分代碼 map.put(String.valueOf(R.drawable.f000) , "[:f000}"); map.put(String.valueOf(R.drawable.f001) , "[:f001}"); map.put(String.valueOf(R.drawable.f002) , "[:f002}"); map.put(String.valueOf(R.drawable.f003) , "[:f003}"); map.put(String.valueOf(R.drawable.f004) , "[:f004}"); } public static String decode(String str) { String[] tmp = str.split(";&#|&#|;"); StringBuilder sb = new StringBuilder(""); for (int i = 0; i < tmp.length; i++) { if (tmp[i].matches("\\d{5}")) { sb.append((char) Integer.parseInt(tmp[i])); } else { sb.append(tmp[i]); } } return sb.toString(); } public static String parse(Spanned spanned ) { try { String origHtml= Html.toHtml(spanned); //某些機(jī)型Html.toHtml得到的字符串略有區(qū)別,需要處理下 //模擬器:

          慢慢

          //小米2A

          慢慢

          String origStr=origHtml.substring(origHtml.indexOf(">")+1, origHtml.length()-5); origStr=StringEscapeUtils.unescapeHtml(origStr);//html 漢字實(shí)體編碼轉(zhuǎn)換 Matcher matcher=pattern.matcher(origStr); // hello StringBuilder stringBuilder=new StringBuilder(); int last=0; while (matcher.find()) { int s= matcher.start(); int e=matcher.end(); stringBuilder.append(origStr.substring( last,s)); String group=matcher.group(); String id=group.substring(10, group.length()-2); String emojStr=map.get(id); if (emojStr==null) { stringBuilder.append(group); }else{ stringBuilder.append(emojStr); } last=e; } stringBuilder.append(origStr.substring(last, origStr.length())); return stringBuilder.toString().replace("
          ", "\n"); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); return ""; } } }

          -->完整代碼

          volley okhttp改造

          
          public class OkHttpStack implements HttpStack {
              private final OkHttpClient mClient;
          
              public OkHttpStack(OkHttpClient client) {
                  this.mClient = client;
              }
          
              @Override
              public HttpResponse performRequest(com.android.volley.Request request, Map additionalHeaders)
                      throws IOException, AuthFailureError {
                  OkHttpClient client = mClient.clone();
                  int timeoutMs = request.getTimeoutMs();
                  client.setConnectTimeout(timeoutMs, TimeUnit.MILLISECONDS);
                  client.setReadTimeout(timeoutMs, TimeUnit.MILLISECONDS);
                  client.setWriteTimeout(timeoutMs, TimeUnit.MILLISECONDS);
          
                  com.squareup.okhttp.Request.Builder okHttpRequestBuilder = new com.squareup.okhttp.Request.Builder();
                  okHttpRequestBuilder.url(request.getUrl());
          
                  Map headers = request.getHeaders();
          
                  for (final String name : headers.keySet()) {
                      if (name!=null&& headers.get(name)!=null) {
                          okHttpRequestBuilder.addHeader(name, headers.get(name));
                      }
          
                  }
          
                  for (final String name : additionalHeaders.keySet()) {
                      if (name!=null&& headers.get(name)!=null) 
                          okHttpRequestBuilder.addHeader(name, additionalHeaders.get(name));
                  }
          
                  setConnectionParametersForRequest(okHttpRequestBuilder, request);
          
                  com.squareup.okhttp.Request okHttpRequest = okHttpRequestBuilder.build();
                  Response okHttpResponse = client.newCall(okHttpRequest).execute();
          
                  StatusLine responseStatus = new BasicStatusLine(parseProtocol(okHttpResponse.protocol()), okHttpResponse.code(),
                          okHttpResponse.message());
          
                  BasicHttpResponse response = new BasicHttpResponse(responseStatus);
                  response.setEntity(entityFromOkHttpResponse(okHttpResponse));
          
                  Headers responseHeaders = okHttpResponse.headers();
          
                  for (int i = 0, len = responseHeaders.size(); i < len; i++) {
                      final String name = responseHeaders.name(i), value = responseHeaders.value(i);
          
                      if (name != null) {
                          response.addHeader(new BasicHeader(name, value));
                      }
                  }
          
                  return response;
              }
          
              private static HttpEntity entityFromOkHttpResponse(Response r) throws IOException {
                  BasicHttpEntity entity = new BasicHttpEntity();
                  ResponseBody body = r.body();
          
                  entity.setContent(body.byteStream());
                  entity.setContentLength(body.contentLength());
                  entity.setContentEncoding(r.header("Content-Encoding"));
          
                  if (body.contentType() != null) {
                      entity.setContentType(body.contentType().type());
                  }
                  return entity;
              }
          
              private static void setConnectionParametersForRequest(com.squareup.okhttp.Request.Builder builder,
                      com.android.volley.Request request) throws IOException, AuthFailureError {
                  switch (request.getMethod()) {
                  case com.android.volley.Request.Method.DEPRECATED_GET_OR_POST:
                      // Ensure backwards compatibility.
                      // Volley assumes a request with a null body is a GET.
                      byte[] postBody = request.getPostBody();
          
                      if (postBody != null) {
                          builder.post(RequestBody.create(MediaType.parse(request.getPostBodyContentType()), postBody));
                      }
                      break;
          
                  case com.android.volley.Request.Method.GET:
                      builder.get();
                      break;
          
                  case com.android.volley.Request.Method.DELETE:
                      builder.delete();
                      break;
          
                  case com.android.volley.Request.Method.POST:
                      builder.post(createRequestBody(request));
                      break;
          
                  case com.android.volley.Request.Method.PUT:
                      builder.put(createRequestBody(request));
                      break;
          
                  // case com.android.volley.Request.Method.HEAD:
                  // builder.head();
                  // break;
                  //
                  // case com.android.volley.Request.Method.OPTIONS:
                  // builder.method("OPTIONS", null);
                  // break;
                  //
                  // case com.android.volley.Request.Method.TRACE:
                  // builder.method("TRACE", null);
                  // break;
                  //
                  // case com.android.volley.Request.Method.PATCH:
                  // builder.patch(createRequestBody(request));
                  // break;
          
                  default:
                      throw new IllegalStateException("Unknown method type.");
                  }
              }
          
              private static ProtocolVersion parseProtocol(final Protocol p) {
                  switch (p) {
                  case HTTP_1_0:
                      return new ProtocolVersion("HTTP", 1, 0);
                  case HTTP_1_1:
                      return new ProtocolVersion("HTTP", 1, 1);
                  case SPDY_3:
                      return new ProtocolVersion("SPDY", 3, 1);
                  case HTTP_2:
                      return new ProtocolVersion("HTTP", 2, 0);
                  }
          
                  throw new IllegalAccessError("Unkwown protocol");
              }
          
              private static RequestBody createRequestBody(com.android.volley.Request r) throws AuthFailureError {
                  final byte[] body = r.getBody();
                  if (body == null)
                      return null;
          
                  return RequestBody.create(MediaType.parse(r.getBodyContentType()), body);
              }
          }
          瀏覽 29
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  极品少妇国产 | A V性天堂网 | 日韩mv欧美mv国产网址 | 中文av中文字幕 中文一级久久黄色 | 亚洲.www |