1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Volley 源碼解讀

        共 4592字,需瀏覽 10分鐘

         ·

        2016-12-08 05:37

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

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

        Alt text

        目錄

        • [Volley特點(diǎn)]

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

        • [Volley初始化]

        • [創(chuàng)建RequestQueue]

        • [DiskBasedCache]
        • [CacheDispatcher & NetworkDispatcher]

        • [Request]

        • [加入RequestQueue]

        • [Request#finish自己]

        • [取消請求]

        • [緩存處理]

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

        • [PoolingByteArrayOutputStream & ByteArrayPool]

        • [Volley加載圖片 ]

        • [Handler]

        • [volley gson改造 ]

        • [volley okhttp改造]

        Volley特點(diǎn)

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

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

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

        4. 支持指定請求的優(yōu)先級;

          • 根據(jù)優(yōu)先級去請求數(shù)據(jù)
        5. 網(wǎng)絡(luò)請求cancel機(jī)制。我們可以取消單個(gè)請求,或者指定取消請求隊(duì)列中的一個(gè)區(qū)域;
          • 例如Activity finish后結(jié)束請求
        6. 框架容易被定制,例如,定制重試或者網(wǎng)絡(luò)請求庫;
          • 例如基于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è)接口,主要用于請求網(wǎng)絡(luò)數(shù)據(jù),并返回結(jié)果。默認(rèn)情況下stack是null,
        當(dāng)android版本>=9時(shí)使用HurlStack,否則使用HttpClientStack
        若用戶想要使用其他的網(wǎng)絡(luò)請求類庫,比如okhttp等就可以實(shí)現(xiàn)HttpStack接口,并在

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

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

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

        DiskBasedCache

        newRequestQueue 中還創(chuàng)建了一個(gè) RequestQueue,RequestQueue 中持有一個(gè) DiskBasedCache 對象,
        DiskBasedCache 是把服務(wù)端返回的數(shù)據(jù)保持到本地文件中的類,默認(rèn)大小5M。
        緩存文件是一個(gè)二進(jìn)制文件,非文本文件,
        緩存文件的開頭有個(gè)特殊的整型魔數(shù)(CACHE_MAGIC),用于判斷是不是緩存文件。DiskBasedCache 初始化時(shí)會
        讀取特定文件夾下的所有文件的部分?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ò)請求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只是簡單的刪除的部分文件,刪除代碼如下

        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ò)請求對象的 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()方法就會阻塞,直到有Request到來,PriorityBlockingQueue是線程安全的

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

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

        Request

        使用 Volley 進(jìn)行網(wǎng)絡(luò)請求時(shí)我們要把請求封裝成一個(gè) Request 對象,包括url,請求參數(shù),請求成功失敗 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所在的請求隊(duì)列,為了在用戶取消請求時(shí)能夠把request從requestQueue中移除掉。
        接下來設(shè)置了一下request的序列號,序列號按添加順序依次從0開始編號,同一個(gè)隊(duì)列中任何兩個(gè)request的序列號都不相同。序列號可以影響request的優(yōu)先級。
        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ò)請求
        如需要緩存則先判斷一下mWaitingRequests中有沒有正在請求的相同的request(根據(jù)request的url判斷),沒有的話就把該request丟到mCacheQueue中,
        這樣 CacheDispatcher 執(zhí)行完之前的請求后就可以執(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;
            }

        接下來會從緩存中取緩存,沒有或者緩存已經(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中的請求成功或失敗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ì)列,然后把請求丟到了mCacheQueue中,丟到mCacheQueue后就會導(dǎo)致 CacheDispatcher 去執(zhí)行網(wǎng)絡(luò)請求,
        這時(shí)由于上次的請求已經(jīng)緩存了,所以可以直接使用上傳的數(shù)據(jù)了,到此為止request如何finish自己就介紹完了。

        取消請求

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

        我們來看一下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來取消特定的請求,比如我們可以在apply中判斷request的url是不是http://api.mogujie.org/gw/mwp.timelinemwp.homeListActionlet/1/?data=xxx,若是則返回true,否則返回false,這樣就可以結(jié)束特定url的請求。

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

         /**
             * 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中只是簡單的標(biāo)記了一下該request需要結(jié)束,代碼如下:

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

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

        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請求到數(shù)據(jù)后會判斷requset是否需要緩存,需要的話會調(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請求失敗重試機(jī)制

        Volley的請求重試機(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中請求失?。⊿ocketTimeoutException,ConnectTimeoutException等)時(shí)會再次請求一次(默認(rèn)重試一次)
        若還是失敗就會拋出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 對象,request重試次數(shù)超過規(guī)定的次數(shù)時(shí)
        attemptRetryOnException 就會拋出 VolleyError,從而導(dǎo)致 performRequest() 方法中 while
        循環(huán)終止,同時(shí)繼續(xù)向上拋異常。

        PoolingByteArrayOutputStream & ByteArrayPool

        為了避免讀取服務(wù)端數(shù)據(jù)時(shí)反復(fù)的內(nèi)存申請,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,所有在子類中可以對其進(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由于沒有被任何對象持有,最終會被回收掉。PoolingByteArrayOutputStream就是在重寫的expand對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ò)請求的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ù)是圖片請求成功的回調(diào),這里我們把返回的Bitmap參數(shù)設(shè)置到ImageView中。第三第四個(gè)參數(shù)分別用于指定允許圖片最大的寬度和高度,如果指定的網(wǎng)絡(luò)圖片的寬度或高度大于這里的最大值,則會對圖片進(jìn)行壓縮,指定成0的話就表示不管圖片有多大,都不會進(jìn)行壓縮。第五個(gè)參數(shù)用于指定圖片的顏色屬性,Bitmap.Config下的幾個(gè)常量都可以在這里使用,其中ARGB_8888可以展示最好的顏色屬性,每個(gè)圖片像素占據(jù)4個(gè)字節(jié)的大小,而RGB_565則表示每個(gè)圖片像素占據(jù)2個(gè)字節(jié)大小。第六個(gè)參數(shù)是圖片請求失敗的回調(diào),這里我們當(dā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中對圖片進(jìn)行了縮放處理,可以看到當(dāng)mMaxWidth == 0 && mMaxHeight == 0時(shí),沒有做過多的處理,這種情況適合于不打的圖片,否則容易導(dǎo)致內(nèi)存溢出。else中對圖片進(jìn)行了縮放處理,首先創(chuàng)建一個(gè)Options 對象,并設(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的冪最終也會按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ò)請求是在非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()會出錯(cuò),我們可以通過Looper.getMainLooper()得到ui線程的Looper,這樣任何線程都可以使用ui線程的Looper了,而且可以在任何線程中創(chuàng)建Handler,并且使用handler發(fā)送消息時(shí)就會跑到ui線程執(zhí)行了。

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

        new Handler(Looper.getMainLooper());

        --> handler之java工程版

        volley gson改造

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

        我們可以自定義一個(gè)XJsonRequest并繼承自Request,同時(shí)為XJsonRequest增加一個(gè)注冊gson類型轉(zhuǎn)換的方法,并把泛型參數(shù)中圖文混排字段設(shè)置為Spanned,然后在Response parseNetworkResponse(NetworkResponse response)中把圖文混拍json轉(zhuǎn)換成Spanned即可,由于Response parseNetworkResponse(NetworkResponse response)是在非ui線程中執(zhí)行的,所已不會導(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)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产成人视频在线 | 美女国产精品 | 91九色麻豆| 久久精品一区二区三区视频 | 和女老板做爰3 | 男女操逼动态视频 | 无遮挡1000部免费视频软件 | 国产吧在线 | 欧美国产三区 | 岛国Av在线成人 俺去也激情 |