Volley源碼分析

volley-request.png

Volley的工作原理

  1. 線程管理
  2. 緩存的管理
  3. 發(fā)送網(wǎng)絡(luò)請(qǐng)求過程
    • 在主線程把請(qǐng)求加入請(qǐng)求隊(duì)列
    • 緩存線程查詢請(qǐng)求是否有緩存榴徐,如果有緩存,則從緩存中獲取數(shù)據(jù)解析返回給主線程攻柠,如果沒有緩存怀吻,把請(qǐng)求分發(fā)給網(wǎng)絡(luò)線程
    • 網(wǎng)絡(luò)線程發(fā)送請(qǐng)求,從服務(wù)器獲取數(shù)據(jù)惠遏,解析后返回給主線程

Volley源碼分析

? 首先,從創(chuàng)建請(qǐng)求隊(duì)列開始看

Volley.newRequestQueue(context);

? 點(diǎn)擊進(jìn)去跳到Volley類,然后就會(huì)跳到這個(gè)方法里面.

public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
    File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);  //這里創(chuàng)建了一個(gè)緩存目錄,具體路徑在data/data/包名/cache/DEFAULT_CACHE_DIR所指向的文件名里面

    String userAgent = "volley/0";
    try {
        String packageName = context.getPackageName();
        PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
        userAgent = packageName + "/" + info.versionCode;
    } catch (NameNotFoundException e) {
    }

    if (stack == null) {
        if (Build.VERSION.SDK_INT >= 9) { //如果當(dāng)前的構(gòu)建版本大于SDK9 就用HurlStack為HttpStack賦值,HttpStack內(nèi)部用HttpUrlConnection來創(chuàng)建網(wǎng)絡(luò)連接
            stack = new HurlStack();
        } else { //否則就用HttpClientStack來為HttpStack賦值,HttpClientStack里面使用HttpClient來創(chuàng)建網(wǎng)絡(luò)連接
            // 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;
} 

? 我們進(jìn)去看一下HttpStack類

public interface HttpStack {
    /**
     * Performs an HTTP request with the given parameters.
     *
     * <p>A GET request is sent if request.getPostBody() == null. A POST request is sent otherwise,
     * and the Content-Type header is set to request.getPostBodyContentType().</p>
     *
     * @param request the request to perform
     * @param additionalHeaders additional headers to be sent together with
     *         {@link Request#getHeaders()}
     * @return the HTTP response
     */
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
        throws IOException, AuthFailureError;

}

? 發(fā)現(xiàn)這是一個(gè)接口,那就看一下他的實(shí)現(xiàn)類HurlStack,查看該類的實(shí)現(xiàn)方法

public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError {
        String url = request.getUrl();
        HashMap<String, String> map = new HashMap<String, String>();
        map.putAll(request.getHeaders());
        map.putAll(additionalHeaders);
        if (mUrlRewriter != null) {
            String rewritten = mUrlRewriter.rewriteUrl(url);
            if (rewritten == null) {
                throw new IOException("URL blocked by rewriter: " + url);
            }
            url = rewritten;
        }
        URL parsedUrl = new URL(url);
        HttpURLConnection connection = openConnection(parsedUrl, request); //使用HttpURLConnection來打開網(wǎng)絡(luò)連接
        for (String headerName : map.keySet()) {
            connection.addRequestProperty(headerName, map.get(headerName));
        }
        setConnectionParametersForRequest(connection, request);
        // Initialize HttpResponse with data from the HttpURLConnection.
        ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
        int responseCode = connection.getResponseCode();
        if (responseCode == -1) {
            // -1 is returned by getResponseCode() if the response code could not be retrieved.
            // Signal to the caller that something was wrong with the connection.
            throw new IOException("Could not retrieve response code from HttpUrlConnection.");
        }
        StatusLine responseStatus = new BasicStatusLine(protocolVersion,
                connection.getResponseCode(), connection.getResponseMessage());
        BasicHttpResponse response = new BasicHttpResponse(responseStatus);
        if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) {
            response.setEntity(entityFromConnection(connection));
        }
        for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
            if (header.getKey() != null) {
                Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
                response.addHeader(h);
            }
        }
        return response;
    }

? 我們發(fā)現(xiàn)這個(gè)方法主要是在構(gòu)建網(wǎng)絡(luò)連接,里面是使用HttpURLConnection來網(wǎng)絡(luò)連接,然后返回HttpResponse響應(yīng).

繼續(xù)回到newRequestQueue方法里面

    Network network = new BasicNetwork(stack);

    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
    queue.start();

? 代碼執(zhí)行到這里了,我們看一下這行代碼,這里傳入了兩個(gè)參數(shù),一個(gè)是基于磁盤的緩存,一個(gè)是網(wǎng)絡(luò)連接.進(jìn)去DiskBasedCache類看一下

 RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
  public DiskBasedCache(File rootDirectory) {
        this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
    }

? 可以看出這里設(shè)置了緩存的目錄以及默認(rèn)使用緩存的大小

 private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;

? 默認(rèn)緩存的大小為5M.

我們最后進(jìn)入RequestQueue里面看一下構(gòu)造方法做了一些什么事情

public RequestQueue(Cache cache, Network network) {
        this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
    }

繼續(xù)點(diǎn)擊進(jìn)去,

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

再次點(diǎn)擊進(jìn)去,最終是調(diào)用這個(gè)構(gòu)造方法

 public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache; //根據(jù)上面封裝的構(gòu)造方法,這里傳進(jìn)來的是new DiskBasedCache(cacheDir)
        mNetwork = network;//這里傳進(jìn)來的是new BasicNetwork(stack)
        mDispatchers = new NetworkDispatcher[threadPoolSize];//threadPoolSize傳進(jìn)來的是 DEFAULT_NETWORK_THREAD_POOL_SIZE,根據(jù)查看源碼得知這個(gè)值是為4
        //private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
        mDelivery = delivery; 
   //這里傳進(jìn)來的是
   //new ExecutorDelivery(new Handler(Looper.getMainLooper())));
   //這里做的是,將子線程切換到主線程
    }

我們最終是初始化了以上這些類,我們點(diǎn)擊NetworkDispatcher進(jìn)去看一下,里面做了一些什么事情

發(fā)現(xiàn)NetworkDispatcher是繼承了Thread類的,就說明,上面初始化的時(shí)候,初始化了4個(gè)線程.

 RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();

可以看出RequestQueue初始化做了一下事情:

  • 初始化磁盤緩存
  • 初始化了網(wǎng)絡(luò)連接
  • 創(chuàng)建了4個(gè)網(wǎng)絡(luò)線程
  • 初始化了一個(gè)將執(zhí)行結(jié)果發(fā)送到主線程的傳遞器

繼續(xù)點(diǎn)擊進(jìn)去看start方法做了什么事情

public void start() {
        stop();  // Make sure any currently running dispatchers are stopped.
        // Create the cache dispatcher and start it.
  
  //首先創(chuàng)建了緩存分發(fā)器
        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();
        }
    }

通過這個(gè)方法我們可以知道,首先創(chuàng)建了緩存分發(fā)器.首先檢查緩存中是否有數(shù)據(jù),我們進(jìn)去CacheDispatcher類查看

這是一個(gè)繼承了Thread的類,

 public void run() {
        if (DEBUG) VolleyLog.v("start new dispatcher");
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

        // Make a blocking call to initialize the cache.
        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();  //這里不斷地往緩存隊(duì)列中拿數(shù)據(jù)
                request.addMarker("cache-queue-take");     //并且為請(qǐng)求添加標(biāo)記

                // If the request has been canceled, don't bother dispatching it.
                if (request.isCanceled()) {  //判斷請(qǐng)求是否已經(jīng)取消
                    request.finish("cache-discard-canceled");
                    continue;
                }

                // Attempt to retrieve this item from cache.
                Cache.Entry entry = mCache.get(request.getCacheKey());
              //內(nèi)存中找不到請(qǐng)求,就從磁盤中查找緩存
                if (entry == null) { //如果找不到緩存
                    request.addMarker("cache-miss"); //則為這個(gè)請(qǐng)求打上"緩存丟失的標(biāo)記"
                    // Cache miss; send off to the network dispatcher.
                    mNetworkQueue.put(request);  //找不到緩存,就會(huì)往網(wǎng)絡(luò)隊(duì)列中尋找緩存
                    continue;
                }

                // If it is completely expired, just send it to the network.
                if (entry.isExpired()) { //判斷緩存是否過期
                    request.addMarker("cache-hit-expired"); //過期了就,為緩存打上"緩存過期" 的標(biāo)記
                    request.setCacheEntry(entry); //并重新設(shè)置緩存
                    mNetworkQueue.put(request); //向網(wǎng)絡(luò)請(qǐng)求隊(duì)列中加入緩存
                    continue;
                }

                // We have a cache hit; parse its data for delivery back to the request.
                request.addMarker("cache-hit");  //如果有緩存,就打上"找到緩存"的標(biāo)記
                Response<?> response = request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));
              //讀取緩存并解析緩存
                request.addMarker("cache-hit-parsed"); //為請(qǐng)求打上"緩存被解析"的標(biāo)記

                if (!entry.refreshNeeded()) {//判斷數(shù)據(jù)是否需要刷新
                    // Completely unexpired cache hit. Just deliver the response.
                    mDelivery.postResponse(request, response); //傳遞器將響應(yīng)從子線程傳遞到主線程
                } else {
                  //如果數(shù)據(jù)需要重新刷新,就需要重新加入網(wǎng)絡(luò)請(qǐng)求隊(duì)列中,發(fā)送網(wǎng)絡(luò)請(qǐng)求
                    // Soft-expired cache hit. We can deliver the cached response,
                    // but we need to also send the request to the network for
                    // refreshing.
                    request.addMarker("cache-hit-refresh-needed");
                    request.setCacheEntry(entry);

                    // Mark the response as intermediate.
                    response.intermediate = true;

                    // Post the intermediate response back to the user and have
                    // the delivery then forward the request along to the network.
                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mNetworkQueue.put(request);
                            } catch (InterruptedException e) {
                                // Not much we can do about this.
                            }
                        }
                    });
                }

            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
        }
    }

看完這里,可以知道,首先在內(nèi)存中尋找緩存,如果沒有就到磁盤中尋找緩存,如果沒有,再發(fā)送網(wǎng)絡(luò)請(qǐng)求獲取數(shù)據(jù).

volley-request.png

和上面這個(gè)圖的工作原理一樣.數(shù)據(jù)加入網(wǎng)絡(luò)請(qǐng)求隊(duì)列之后,繼續(xù)往下看

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

這里添加了四個(gè)線程給網(wǎng)絡(luò)分發(fā)器,對(duì)應(yīng)上圖.

我們進(jìn)去NetworkDispatcher里面看

 public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (true) {
            long startTimeMs = SystemClock.elapsedRealtime();
            Request<?> request;
            try {
                // Take a request from the queue.
                request = mQueue.take();
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }

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

                // If the request was cancelled already, do not perform the
                // network request.
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }

                addTrafficStatsTag(request);

                // Perform the network request.
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                request.addMarker("network-http-complete");

                // If the server returned 304 AND we delivered a response already,
                // we're done -- don't deliver a second identical response.
                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                    request.finish("not-modified");
                    continue;
                }

                // Parse the response here on the worker thread.
                Response<?> response = request.parseNetworkResponse(networkResponse);
                request.addMarker("network-parse-complete");

                // Write to cache if applicable.
                // 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");
                }

                // Post the response back.
                request.markDelivered();
                mDelivery.postResponse(request, response); //發(fā)送響應(yīng)到主線程
            } catch (VolleyError volleyError) {
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                parseAndDeliverNetworkError(request, volleyError);
            } catch (Exception e) {
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
                VolleyError volleyError = new VolleyError(e);
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                mDelivery.postError(request, volleyError);
            }
        }
    }

工作原理和上面的緩存隊(duì)列工作原理相似.

  mDelivery.postResponse(request, response); //發(fā)送響應(yīng)到主線程

繼續(xù)往下查看

@Override
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
        request.markDelivered();
        request.addMarker("post-response");
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
    }

這里執(zhí)行了ResponseDeliveryRunnable,繼續(xù)查看

 public void run() {
            // If this request has canceled, finish it and don't deliver.
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }

            // Deliver a normal response or error, depending.
            if (mResponse.isSuccess()) { //響應(yīng)成功
                mRequest.deliverResponse(mResponse.result);
            } else { //響應(yīng)失敗
                mRequest.deliverError(mResponse.error);
            }

            // If this is an intermediate response, add a marker, otherwise we're done
            // and the request can be finished.
            if (mResponse.intermediate) {
                mRequest.addMarker("intermediate-response");
            } else {
                mRequest.finish("done");
            }

            // If we have been provided a post-delivery runnable, run it.
            if (mRunnable != null) {
                mRunnable.run();
            }
       }

響應(yīng)成功或者失敗都會(huì)回調(diào)監(jiān)聽

響應(yīng)成功:

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

響應(yīng)失敗:

public void deliverError(VolleyError error) {
        if (mErrorListener != null) {
            mErrorListener.onErrorResponse(error);
        }
    }

因?yàn)楸救诉@里是使用JsonRequest,所以這里會(huì)響應(yīng)JsonRequest的請(qǐng)求,監(jiān)聽器就有數(shù)據(jù)過來了

private NetWorkListener<NewsListBean> mListener=new NetWorkListener<NewsListBean>()
    {
        @Override
        public void onResponse(NewsListBean response) {
            //保存新聞列表數(shù)據(jù)
            mNews = response.getData().getNews();
            //保存輪播圖數(shù)據(jù)
            mTopNews = response.getData().getTopnews();
            //刷新新聞列表
            mAdapter.notifyDataSetChanged();
            //保存more字段,加載更多
            mMore = response.getData().getMore();
            List<String> urls = new ArrayList<String>();
            mTopNews = response.getData().getTopnews();
            //遍歷輪播圖的數(shù)據(jù),構(gòu)建圖片url集合
            for (int i = 0; i <mTopNews.size() ; i++) {
                urls.add(mTopNews.get(i).getTopimage());
            }
            //刷新輪播圖
            mFunBanner.setImageUrls(urls);//內(nèi)部使用glide加載圖片
        }
    };

NetWorkListener是我自己寫的,實(shí)現(xiàn)了Response.Listener,Response.ErrorListener接口

public class NetWorkListener<T> implements Response.Listener<T>,Response.ErrorListener{
    @Override
    public void onErrorResponse(VolleyError error) {

    }

    @Override
    public void onResponse(T response) {

    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末骏啰,一起剝皮案震驚了整個(gè)濱河市节吮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌器一,老刑警劉巖课锌,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異祈秕,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)雏胃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門请毛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瞭亮,你說我怎么就攤上這事方仿。” “怎么了统翩?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵仙蚜,是天一觀的道長。 經(jīng)常有香客問我厂汗,道長委粉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任娶桦,我火速辦了婚禮贾节,結(jié)果婚禮上汁汗,老公的妹妹穿的比我還像新娘。我一直安慰自己栗涂,他們只是感情好知牌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著斤程,像睡著了一般角寸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上忿墅,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天扁藕,我揣著相機(jī)與錄音,去河邊找鬼球匕。 笑死纹磺,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的亮曹。 我是一名探鬼主播橄杨,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼照卦!你這毒婦竟也來了式矫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤役耕,失蹤者是張志新(化名)和其女友劉穎采转,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瞬痘,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡故慈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了框全。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片察绷。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖津辩,靈堂內(nèi)的尸體忽然破棺而出拆撼,到底是詐尸還是另有隱情,我是刑警寧澤喘沿,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布闸度,位于F島的核電站,受9級(jí)特大地震影響蚜印,放射性物質(zhì)發(fā)生泄漏莺禁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一晒哄、第九天 我趴在偏房一處隱蔽的房頂上張望睁宰。 院中可真熱鬧肪获,春花似錦、人聲如沸柒傻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽红符。三九已至青柄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間预侯,已是汗流浹背致开。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留萎馅,地道東北人双戳。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像糜芳,于是被迫代替她去往敵國和親飒货。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 我的博客: Volley 源碼分析 Volley 的使用流程分析 官網(wǎng)示例 創(chuàng)建一個(gè)請(qǐng)求隊(duì)列 RequestQue...
    realxz閱讀 2,040評(píng)論 1 11
  • 前言 本文是一篇日常學(xué)習(xí)總結(jié)性的文章峭竣,筆者通過分析經(jīng)典網(wǎng)絡(luò)框架Volley的源碼塘辅,望以鞏固Android網(wǎng)絡(luò)框架中...
    Armstrong_Q閱讀 574評(píng)論 0 7
  • Volley源碼分析之流程和緩存 前言 Android一開始提供了HttpURLConnection和HttpCl...
    大寫ls閱讀 618評(píng)論 0 6
  • 以StringRequest為例子,分析Volley是怎樣執(zhí)行一個(gè)網(wǎng)絡(luò)請(qǐng)求的皆撩。 先看實(shí)現(xiàn)Request抽象類的St...
    stefanJi閱讀 219評(píng)論 0 0
  • kidII閱讀 101評(píng)論 0 0