手撕 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("http://www.jianshu.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 (二)

推荐阅读更多精彩内容