手撕 Volley (三)

WeBareBears.jpg

手撕 Volley (一)
手撕 Volley (二)
節(jié)后工作第一天啦租,不知道大家有沒有能夠很好地融入到工作、學(xué)習(xí)當(dāng)中呢。過幾天 NBA 可就要開始了辖试,還有畢設(shè)、實(shí)習(xí)答辯莺债、說好的 dream offer滤蝠,這個(gè)十月注定是忙碌的,啊·······
不扯了缓苛,繼續(xù) read the fucking source code,本篇內(nèi)容包括:

網(wǎng)絡(luò)訪問

上回說到邓深,最終的網(wǎng)絡(luò)執(zhí)行還是在 HttpStack 中未桥,而我們又知道,HttpStack 是一個(gè)接口芥备,在 Volley 中他有兩種實(shí)現(xiàn):

  • 基于 HttpClient 的 HttpClientStack
  • 基于 HttpURLConnection 的 HurlStack

之所以會(huì)用到兩種實(shí)現(xiàn)是由于 HttpURLConnection 在
Android SDK 9 以前有 bug冬耿, 可以說這是歷史原因吧。而我們看 Volley 的配置文件:

manifest.png

可以看到最低支持的 SDK 為8萌壳,在最新的 Android 6.0 中已經(jīng)取消了對(duì) HttpClient 的依賴亦镶,如果你想用 Volley 的話可以修改 Volley 的源碼,或者手動(dòng)添加依賴到 gradle袱瓮。這里我們只看 HurlStack缤骨。

HurlStack

先看類圖


HurlStack.png
  • 定義了一個(gè)內(nèi)部接口 UrlWriter,作用是實(shí)現(xiàn)對(duì)Url的攔截尺借,對(duì)url進(jìn)行一些處理
  • 成員變量 SSlSocketFactory绊起,作用是用 SSL 構(gòu)建安全的 Socket 進(jìn)行 HTTPS 通信。

Class SSLSocketFactory

HurlStack 有三個(gè)構(gòu)造方法:

public HurlStack() {
    this(null);
  }

  /**
   * @param urlRewriter Rewriter to use for request URLs
   */
  public HurlStack(UrlRewriter urlRewriter) {
    this(urlRewriter, null);
  }

  /**
   * @param urlRewriter Rewriter to use for request URLs
   * @param sslSocketFactory SSL factory to use for HTTPS connections
   */
  public HurlStack(UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) {
    mUrlRewriter = urlRewriter;
    mSslSocketFactory = sslSocketFactory;
  }

還記得我們?cè)?a href="http://www.reibang.com/p/33be82da8f25" target="_blank">手撕 Volley (一)
最開始分析的入口函數(shù) Volley 的 newRequestQueue 方法嗎

  public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
    // 省略
        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));
            }
        }
   //省略
    }
 

可以看到燎斩,這里 Volley 默認(rèn)調(diào)用的是最上面那個(gè)構(gòu)造器虱歪,但其實(shí)最上面的構(gòu)造函數(shù)會(huì)調(diào)用有一個(gè)參數(shù)的也就是第二個(gè)構(gòu)造函數(shù),并將參數(shù)值設(shè)為 null栅表,同樣第二個(gè)會(huì)調(diào)用最后一個(gè)有兩個(gè)參數(shù)的構(gòu)造函數(shù)笋鄙,并將參數(shù)設(shè)為 null。也就是將 urlRewriter 和 sslSocketFactory 都初始化為 null怪瓶。
當(dāng)然我們?nèi)绻袛r截 URL 需求或者安全需求需要用到 HTTPS 的話萧落,可以自己寫 HttpStack 實(shí)現(xiàn)需求,然后傳給 Volley 的 newRequestQueue 方法洗贰。
再次感受到 Volley 基于接口編程的強(qiáng)大找岖,膜拜 ing。
最后再來看核心方法 performRequest哆姻,其余的方法都是為它服務(wù)的宣增。

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

來看流程圖,順序執(zhí)行比較簡單矛缨,偷個(gè)懶爹脾,判斷的地方一筆帶過帖旨。需要注意的是關(guān)于 SSLServerSocketFactory 的調(diào)用是在 openConnection 方法中處理的。

HurlStack.png

內(nèi)容分發(fā)
上面說到 HttpStack 返回了一個(gè) response灵妨,那么這個(gè) response 是怎么被傳遞給 user 的呢解阅,我們看一下這個(gè)過程,這個(gè)字體對(duì)中文顯示比較差泌霍,索性全都用我蹩腳的英文來展示了货抄。
deliver_response.png

HttpStack 返回的是 HttpResponse,NetWork 和 NetWorkDispacher 分別對(duì)其做了封裝朱转,具體代碼我們前面的分析手撕 Volley (二)里面都有蟹地,忘記的朋友可以回去再看看。為什么要這么做呢藤为,我們來看 response 的類圖:

response.png

HttpResponse 是一個(gè)接口怪与,這里他的具體實(shí)現(xiàn)類為 BasicHttpResponse,實(shí)現(xiàn)比較簡單缅疟,按照 HTTP 報(bào)文的格式封了裝對(duì)象分别,這里我們可以這樣理解,Response 主要是為了給用戶使用存淫,它封裝了對(duì)網(wǎng)絡(luò)請(qǐng)求失敗和成功的回調(diào)耘斩。NetWorkResponse 算是一個(gè)過度,可能不準(zhǔn)確桅咆。大家有更好的理解可以私我括授。

Interface HttpResponse
Class BasicHttpResponse

ok,我們看到到了 Response 到了 NetworkDispacher 以后就是 Delivery 大發(fā)神威了轧邪,又是 pase 又是 deliver刽脖,來看一下它的實(shí)現(xiàn),首先還是類圖


delivery.png

ResponseDelivery 故名思意忌愚,就是傳遞響應(yīng),他在這里的實(shí)現(xiàn)類是 ExecutorDelivery却邓,而 RequstQueue 的構(gòu)造器也就是用的 ExcutorDelivery

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

ExcutorDelivery 對(duì)應(yīng)的構(gòu)造函數(shù)

 /**
     * Creates a new response delivery interface.
     * @param handler {@link Handler} to post responses on
     */
    public ExecutorDelivery(final Handler handler) {
        // Make an Executor that just wraps the handler.
        mResponsePoster = new Executor() {
            @Override
            public void execute(Runnable command) {
                handler.post(command);
            }
        };
    }

傳入了一個(gè)主線程的 Looper 創(chuàng)建的 Handler硕糊,然后有一個(gè) Executor 的成員變量,用來向主線程傳遞 response腊徙。

Interface Executor
Android 異步消息處理機(jī)制 讓你深入理解 Looper简十、Handler、Message三者關(guān)系

ExcutorDelivery 最重要的方法就是 PostResponse 了

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

ResponseDeliveryRunnable 是一個(gè)內(nèi)部類撬腾,實(shí)現(xiàn)了 Runnable 接口螟蝙,postResponse 會(huì)調(diào)用他的 run 方法,如下

     @Override
        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()) {
                mRequest.deliverResponse(mResponse.result);
            } else {
                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();
            }
       }

邏輯比較簡單


ResponseDeliveryRunnable .png

那到這里響應(yīng)分發(fā)重?fù)?dān)就傳遞到了 request 的身上

 /** Listener interface for errors. */
 private Response.ErrorListener mErrorListener;

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

deliverResponse 是一個(gè)抽象類民傻,StringRequest 里面是這樣實(shí)現(xiàn)的胰默。

    import com.android.volley.Response.Listener;
   //省略
    private Listener<String> mListener;

 @Override
    protected void deliverResponse(String response) {
        if (mListener != null) {
            mListener.onResponse(response);
        }
    }

可以看到都是調(diào)用的接口的方法场斑,那誰來實(shí)現(xiàn)接口的方法呢。當(dāng)然是使用 Volley 的我們牵署。請(qǐng)看

StringRequest stringRequest = new StringRequest("http://www.reibang.com",  
                        new Response.Listener<String>() {  
                            @Override  
                            public void onResponse(String response) {  
                                Log.d("TAG", response);  
                            }  
                        }, new Response.ErrorListener() {  
                            @Override  
                            public void onErrorResponse(VolleyError error) {  
                                Log.e("TAG", error.getMessage(), error);  
                            }  
                        });  

Listener 是在我們使用的時(shí)候才創(chuàng)建傳入到 request 中的漏隐,接口中的回調(diào)函數(shù)自然也是開發(fā)者來實(shí)現(xiàn)。
也就是說我們添加到 requestqueue 中的請(qǐng)求經(jīng)過一系列的處理得到最終的 response 或者 error奴迅,交給開發(fā)者來處理青责。
到這里結(jié)果分發(fā)就分析結(jié)束了。大家應(yīng)該對(duì)手撕 Volley (一)最開始給出的官方流程圖有更深刻的認(rèn)識(shí)了吧取具。
請(qǐng)求完成
上面的 run 方法中可以看到如果請(qǐng)求取消或者數(shù)據(jù)不需要更新就會(huì)調(diào)用 request 的 finish 方法脖隶,

 void finish(final String tag) {
        if (mRequestQueue != null) {
            mRequestQueue.finish(this);
            onFinish();
        }
        if (MarkerLog.ENABLED) {
            final long threadId = Thread.currentThread().getId();
            if (Looper.myLooper() != Looper.getMainLooper()) {
                // If we finish marking off of the main thread, we need to
                // actually do it on the main thread to ensure correct ordering.
                Handler mainThread = new Handler(Looper.getMainLooper());
                mainThread.post(new Runnable() {
                    @Override
                    public void run() {
                        mEventLog.add(tag, threadId);
                        mEventLog.finish(this.toString());
                    }
                });
                return;
            }

            mEventLog.add(tag, threadId);
            mEventLog.finish(this.toString());
        }
    }

    /**
     * clear listeners when finished
     */
    protected void onFinish() {
        mErrorListener = null;
    }

  • 調(diào)用 ReqestQueue 的 finish
  • 調(diào)用自己的 onFinish
  • 將調(diào)用 finish 傳入的參數(shù) tag 加入日志,然后 finish 掉日志,可以看到,如果當(dāng)前線程不是主線程的話世澜,會(huì)把處理移交給主線程處理啄清。誰知道為什么。枷遂。

這里說一下 Volley 的日志處理,Wolley 的日志處理由 VolleyLog 幫助完成,上類圖:


VolleyLog.png

三層嵌套的內(nèi)部類怎燥,用來管理日志,需要注意的是 MarkerLog 的 finish 方法

 public synchronized void finish(String header) {
            mFinished = true;

            long duration = getTotalDuration();
            if (duration <= MIN_DURATION_FOR_LOGGING_MS) {
                return;
            }

            long prevTime = mMarkers.get(0).time;
            d("(%-4d ms) %s", duration, header);
            for (Marker marker : mMarkers) {
                long thisTime = marker.time;
                d("(+%-4d) [%2d] %s", (thisTime - prevTime), marker.thread, marker.name);
                prevTime = thisTime;
            }
        }

這段代碼的意思是蜜暑,如果結(jié)束時(shí) request 存在的時(shí)間不在一個(gè)常量值的范圍之內(nèi)铐姚,就將日志取出逐行計(jì)算時(shí)間差調(diào)用 d ( DEBUG ) 輸出日志信息。
接下來繼續(xù)看重頭戲 requestqueue 的 finish 方法

 <T> void finish(Request<T> request) {
        // Remove from the set of requests currently being processed.
        synchronized (mCurrentRequests) {
            mCurrentRequests.remove(request);
        }
        synchronized (mFinishedListeners) {
          for (RequestFinishedListener<T> listener : mFinishedListeners) {
            listener.onRequestFinished(request);
          }
        }

        if (request.shouldCache()) {
            synchronized (mWaitingRequests) {
                String cacheKey = request.getCacheKey();
                Queue<Request<?>> 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.png

請(qǐng)求取消

最后簡單說一下肛捍,RequestQueue 的 cancelAll 方法

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

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

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

自己定義一個(gè) RequestFilter隐绵, 所有符合這個(gè) fiflter 的都將被標(biāo)志為 mCanceled = true;
自己給 request 設(shè)置一個(gè) tag 所有 request.getTag() == tag 的 Request 都會(huì)被標(biāo)志為 mCanceled = true;
到這里 Volley 的主要流程就走了一遍了,感謝閱讀拙毫。
總結(jié)
三遍文章算是把大概的流程捋了一遍依许,通過捋代碼,我發(fā)現(xiàn) Volley 基于接口編程拓展性強(qiáng)缀蹄,邏輯清晰峭跳,層次分明。要學(xué)到大牛們?cè)O(shè)計(jì)開源項(xiàng)目的精髓還需要更加勤奮啊缺前。蛀醉。。衅码。廢話連篇拯刁。。大家一起加油逝段。
手撕 Volley (一)
手撕 Volley (二)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末垛玻,一起剝皮案震驚了整個(gè)濱河市割捅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌夭谤,老刑警劉巖棺牧,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異朗儒,居然都是意外死亡颊乘,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門醉锄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乏悄,“玉大人,你說我怎么就攤上這事恳不¢菪。” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵烟勋,是天一觀的道長规求。 經(jīng)常有香客問我,道長卵惦,這世上最難降的妖魔是什么阻肿? 我笑而不...
    開封第一講書人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮沮尿,結(jié)果婚禮上丛塌,老公的妹妹穿的比我還像新娘。我一直安慰自己畜疾,他們只是感情好赴邻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著啡捶,像睡著了一般姥敛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上届慈,一...
    開封第一講書人閱讀 51,208評(píng)論 1 299
  • 那天徒溪,我揣著相機(jī)與錄音,去河邊找鬼金顿。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鲤桥,可吹牛的內(nèi)容都是我干的揍拆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼茶凳,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼嫂拴!你這毒婦竟也來了播揪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤筒狠,失蹤者是張志新(化名)和其女友劉穎猪狈,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辩恼,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡雇庙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了灶伊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疆前。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖聘萨,靈堂內(nèi)的尸體忽然破棺而出竹椒,到底是詐尸還是另有隱情,我是刑警寧澤米辐,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布胸完,位于F島的核電站,受9級(jí)特大地震影響翘贮,放射性物質(zhì)發(fā)生泄漏赊窥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一择膝、第九天 我趴在偏房一處隱蔽的房頂上張望誓琼。 院中可真熱鬧,春花似錦肴捉、人聲如沸腹侣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽傲隶。三九已至,卻和暖如春窃页,著一層夾襖步出監(jiān)牢的瞬間跺株,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來泰國打工脖卖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乒省,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓畦木,卻偏偏與公主長得像袖扛,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

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