手撕 Volley (三)

WeBareBears.jpg

手撕 Volley (一)
手撕 Volley (二)
节后工作第一天,不知道大家有没有能够很好地融入到工作、学习当中呢。过几天 NBA 可就要开始了,还有毕设、实习答辩、说好的 dream offer,这个十月注定是忙碌的,啊·······
不扯了,继续 read the fucking source code,本篇内容包括:

网络访问

上回说到,最终的网络执行还是在 HttpStack 中,而我们又知道,HttpStack 是一个接口,在 Volley 中他有两种实现:

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

之所以会用到两种实现是由于 HttpURLConnection 在
Android SDK 9 以前有 bug, 可以说这是历史原因吧。而我们看 Volley 的配置文件:

manifest.png

可以看到最低支持的 SDK 为8,在最新的 Android 6.0 中已经取消了对 HttpClient 的依赖,如果你想用 Volley 的话可以修改 Volley 的源码,或者手动添加依赖到 gradle。这里我们只看 HurlStack。

HurlStack

先看类图


HurlStack.png
  • 定义了一个内部接口 UrlWriter,作用是实现对Url的拦截,对url进行一些处理
  • 成员变量 SSlSocketFactory,作用是用 SSL 构建安全的 Socket 进行 HTTPS 通信。

Class SSLSocketFactory

HurlStack 有三个构造方法:

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

还记得我们在手撕 Volley (一)
最开始分析的入口函数 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 默认调用的是最上面那个构造器,但其实最上面的构造函数会调用有一个参数的也就是第二个构造函数,并将参数值设为 null,同样第二个会调用最后一个有两个参数的构造函数,并将参数设为 null。也就是将 urlRewriter 和 sslSocketFactory 都初始化为 null。
当然我们如果有拦截 URL 需求或者安全需求需要用到 HTTPS 的话,可以自己写 HttpStack 实现需求,然后传给 Volley 的 newRequestQueue 方法。
再次感受到 Volley 基于接口编程的强大,膜拜 ing。
最后再来看核心方法 performRequest,其余的方法都是为它服务的。

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

来看流程图,顺序执行比较简单,偷个懒,判断的地方一笔带过。需要注意的是关于 SSLServerSocketFactory 的调用是在 openConnection 方法中处理的。

HurlStack.png

内容分发
上面说到 HttpStack 返回了一个 response,那么这个 response 是怎么被传递给 user 的呢,我们看一下这个过程,这个字体对中文显示比较差,索性全都用我蹩脚的英文来展示了。
deliver_response.png

HttpStack 返回的是 HttpResponse,NetWork 和 NetWorkDispacher 分别对其做了封装,具体代码我们前面的分析手撕 Volley (二)里面都有,忘记的朋友可以回去再看看。为什么要这么做呢,我们来看 response 的类图:

response.png

HttpResponse 是一个接口,这里他的具体实现类为 BasicHttpResponse,实现比较简单,按照 HTTP 报文的格式封了装对象,这里我们可以这样理解,Response 主要是为了给用户使用,它封装了对网络请求失败和成功的回调。NetWorkResponse 算是一个过度,可能不准确。大家有更好的理解可以私我。

Interface HttpResponse
Class BasicHttpResponse

ok,我们看到到了 Response 到了 NetworkDispacher 以后就是 Delivery 大发神威了,又是 pase 又是 deliver,来看一下它的实现,首先还是类图


delivery.png

ResponseDelivery 故名思意,就是传递响应,他在这里的实现类是 ExecutorDelivery,而 RequstQueue 的构造器也就是用的 ExcutorDelivery

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

ExcutorDelivery 对应的构造函数

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

传入了一个主线程的 Looper 创建的 Handler,然后有一个 Executor 的成员变量,用来向主线程传递 response。

Interface Executor
Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系

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 是一个内部类,实现了 Runnable 接口,postResponse 会调用他的 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

那到这里响应分发重担就传递到了 request 的身上

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

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

deliverResponse 是一个抽象类,StringRequest 里面是这样实现的。

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

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

可以看到都是调用的接口的方法,那谁来实现接口的方法呢。当然是使用 Volley 的我们。请看

StringRequest stringRequest = new StringRequest("//www.greatytc.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 是在我们使用的时候才创建传入到 request 中的,接口中的回调函数自然也是开发者来实现。
也就是说我们添加到 requestqueue 中的请求经过一系列的处理得到最终的 response 或者 error,交给开发者来处理。
到这里结果分发就分析结束了。大家应该对手撕 Volley (一)最开始给出的官方流程图有更深刻的认识了吧。
请求完成
上面的 run 方法中可以看到如果请求取消或者数据不需要更新就会调用 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;
    }

  • 调用 ReqestQueue 的 finish
  • 调用自己的 onFinish
  • 将调用 finish 传入的参数 tag 加入日志,然后 finish 掉日志,可以看到,如果当前线程不是主线程的话,会把处理移交给主线程处理。谁知道为什么。。

这里说一下 Volley 的日志处理,Wolley 的日志处理由 VolleyLog 帮助完成,上类图:


VolleyLog.png

三层嵌套的内部类,用来管理日志,需要注意的是 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;
            }
        }

这段代码的意思是,如果结束时 request 存在的时间不在一个常量值的范围之内,就将日志取出逐行计算时间差调用 d ( DEBUG ) 输出日志信息。
接下来继续看重头戏 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

请求取消

最后简单说一下,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;
            }
        });
    }

自己定义一个 RequestFilter, 所有符合这个 fiflter 的都将被标志为 mCanceled = true;
自己给 request 设置一个 tag 所有 request.getTag() == tag 的 Request 都会被标志为 mCanceled = true;
到这里 Volley 的主要流程就走了一遍了,感谢阅读。
总结
三遍文章算是把大概的流程捋了一遍,通过捋代码,我发现 Volley 基于接口编程拓展性强,逻辑清晰,层次分明。要学到大牛们设计开源项目的精髓还需要更加勤奋啊。。。。废话连篇。。大家一起加油。
手撕 Volley (一)
手撕 Volley (二)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,376评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,126评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,966评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,432评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,519评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,792评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,933评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,701评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,143评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,488评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,626评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,292评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,896评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,494评论 2 348

推荐阅读更多精彩内容