Volley源码分析

官方文档: https://developer.android.google.cn/training/volley/index.html
项目GitHub地址: https://github.com/google/volley
Volley 是一个可让 Android 应用更轻松、(最重要的是)更快捷地联网的 HTTP 库。
Volley 具有以下优势:

  • 自动网络请求调度。
  • 多个并发网络连接。
  • 透明磁盘和具有标准 HTTP 缓存一致性的内存响应缓存。
  • 支持请求优先级。
  • 取消请求 API。您可以取消单个请求,也可以设置要取消的请求的时间段或范围。
  • 可轻松自定义,例如自定义重试和退避时间。
  • 强大的排序功能,让您可以轻松使用从网络异步提取的数据正确填充界面。
  • 调试和跟踪工具。

整体流程图

volley流程_pub.png

详细流程信息上图已表示的很清楚,建议通过大图详看,下面就几个大块做进一步了解

常见类说明

  • RequestManager:通过单例的方式创建RequestQueue,并对外暴露RequestQueue,和提供RequestQueue.add(request)方法。
  • Volley:对外暴露newRequestQueue(…) 方法,通过Build.VERSION.SDK_INT >= 9 条件控制创建BasicNetwork对象时传入参数为HurlStack还是HttpClientStack,前者基于HttpURLConnection后者基于HttpClient、这里指定了缓存目录、新建并启动一个请求队列RequestQueue。
  • RequestQueue:表示请求队列,里面包含一个CacheDispatcher(用于处理走缓存请求的调度线程)、NetworkDispatcher数组(默认有4个,用于处理走网络请求的调度线程),一个ResponseDelivery(返回结果分发接口,默认走主线程),通过 start() 函数启动CacheDispatcher和NetworkDispatchers,这里还具体实现了add方法,决定了当前request应该放入到哪个阻塞队列里面。
  • BasicNetwork:实现了Network接口具体实现performRequest方法,里面通过死循环不停的执行外部传入的request,并获取response输入流然后构造NetworkResponse返回回去并做部分异常重试机制
  • CacheDispatcher:一个线程,用于调度处理走缓存的请求。启动后会不断从缓存请求队列中获取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery去执行后续处理。当结果未缓存过、缓存失效或缓存需要刷新的情况下,该请求都需要重新进入NetworkDispatcher去调度处理。
  • NetworkDispatcher:一个线程,用于调度处理走网络的请求。启动后会不断从网络请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery去执行后续处理,并判断结果是否要进行缓存。
  • ResponseDelivery:对response进行分发的接口,具体由ExecutorDelivery实现,它是跟随RequestQueue一起创建,并且handler在主线程。
  • Request:表示一个请求的抽象类。StringRequest、JsonRequest、ImageRequest 都是它的子类,表示某种类型的请求。
  • HttpStack:处理 Http 请求,返回请求结果。目前 Volley 中有基于 HttpURLConnection 的HurlStack和 基于 Apache HttpClient 的HttpClientStack。
  • Cache:缓存请求结果,Volley 默认使用的是基于 sdcard 的DiskBasedCache。NetworkDispatcher得到请求结果后判断是否需要存储在 Cache,CacheDispatcher会从 Cache 中取缓存结果。

Connection获取

    public URLConnection openConnection() throws java.io.IOException {
        return handler.openConnection(this);
    }

    private static URLStreamHandler createBuiltinHandler(String protocol)
            throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        URLStreamHandler handler = null;
        if (protocol.equals("file")) {
            handler = new sun.net.www.protocol.file.Handler();
        } else if (protocol.equals("ftp")) {
            handler = new sun.net.www.protocol.ftp.Handler();
        } else if (protocol.equals("jar")) {
            handler = new sun.net.www.protocol.jar.Handler();
        } else if (protocol.equals("http")) {
            handler = (URLStreamHandler)Class.
                    forName("com.android.okhttp.HttpHandler").newInstance();
        } else if (protocol.equals("https")) {//通过反射获取OKhttp的HttpsHandler对象
            handler = (URLStreamHandler)Class.
                    forName("com.android.okhttp.HttpsHandler").newInstance();
        }
        return handler;
    }
image.png

通过跟踪和查看源码,最终connection是通过OkHttpClient 获取的,所以这里也就使用到了OKhttp的连接池复用

重试机制

代码大致逻辑如下:

public NetworkResponse performRequest(Request request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            List
responseHeaders = Collections.emptyList() //.......移除部分代码...... try { httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders); int statusCode = httpResponse.getStatusCode(); InputStream inputStream = httpResponse.getContent(); if (inputStream != null) { responseContents = NetworkUtility.inputStreamToBytes( inputStream, httpResponse.getContentLength(), mPool); } else { responseContents = new byte[0]; } return new NetworkResponse( statusCode, responseContents, /* notModified= */ false, SystemClock.elapsedRealtime() - requestStart, responseHeaders); } catch (IOException e) { RetryInfo retryInfo = NetworkUtility.shouldRetryException( request, e, requestStart, httpResponse, responseContents); NetworkUtility.attemptRetryOnException(request, retryInfo); } } } static void attemptRetryOnException(final Request request, final RetryInfo retryInfo) throws VolleyError { //.......移除部分代码...... final RetryPolicy retryPolicy = request.getRetryPolicy(); final int oldTimeout = request.getTimeoutMs(); try { retryPolicy.retry(retryInfo.errorToRetry); } catch (VolleyError e) { throw e; } } public void retry(VolleyError error) throws VolleyError { mCurrentRetryCount++; mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier); if (!hasAttemptRemaining()) { throw error; } }

通过上面的代码逻辑可以得出以下结论:

  1. performRequest中通过一个死循环来执行网络请求
  2. 当发生网络异常时会被catch住,并通过attemptRetryOnException发起重试机制
  3. attemptRetryOnException中通过retry方法控制是否需要重试,如果需要重试则不做任何处理,继续执行死循环,如果不需要重试或者重试发生异常直接抛出exception跳出死循环
  4. 如果我们需要自定义重试逻辑可以实现RetryPolicy接口,并通过request的setRetryPolicy方法进行配置

RetryPolicy内容如下:

public interface RetryPolicy {
    int getCurrentTimeout();
    int getCurrentRetryCount();
    void retry(VolleyError error) throws VolleyError;
}

volley内部有一个DefaultRetryPolicy类,该类默认实现了RetryPolicy接口,并且我们通过volley创建的每个request默认都会设置这个重试实现,DefaultRetryPolicy的主要逻辑如下:

  1. 设置默认的超时时间为15000ms、默认的最大重试次数为1次、默认的补偿倍数为1
  2. 每次重试的超时时间 = 当前超时时间 + 当前超时时间 * 补偿倍数

自定义DNS解析

在HurlStack的executeRequest方法中才是真正做链接创建和IO流的收发

public HttpResponse executeRequest(Request request, Map additionalHeaders)
            throws IOException, AuthFailureError {
        String url = request.getUrl();//获取request中的请求地址URL
        if (mUrlRewriter != null) {//控制是否重写URL,这里我们就可以做自定义dns解析
            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);
                      //.......移除部分代码......
        try {
            setConnectionParametersForRequest(connection, request);
            int responseCode = connection.getResponseCode();
            return new HttpResponse(
                    responseCode,
                    convertHeaders(connection.getHeaderFields()),
                    connection.getContentLength(),
                    createInputStream(request, connection));
        } finally {

        }
    }

public interface UrlRewriter {
    @Nullable
    String rewriteUrl(String originalUrl);
}

通过上面代码我们可以知道,volley在真正发起网络请求之前会判断UrlRewriter是否为null,如果不为null则会执行它的rewriteUrl方法,我们就可以通过实现UrlRewriter接口并重写rewriteUrl来实现自定义DNS预解析的操作,具体实现可以通过继承HurlStack,在初始化父类HurlStack时传入自己实现的UrlRewriter接口

自定义日志输出

    request.addMarker("cache-queue-take");  //添加日志信息通过VolleyLog输出
    request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_STARTED); //如果设置了RequestEventListener则通过onRequestEvent方法将数据发送出去

    public void addMarker(String tag) {
        if (MarkerLog.ENABLED) {
            mEventLog.add(tag, Thread.currentThread().getId());
        }
    }
    void sendEvent(@RequestQueue.RequestEvent int event) {
        if (mRequestQueue != null) {
            mRequestQueue.sendRequestEvent(this, event);
        }
    }
    private final List mEventListeners = new ArrayList<>();

    void sendRequestEvent(Request request, @RequestEvent int event) {
        synchronized (mEventListeners) {
            for (RequestEventListener listener : mEventListeners) {
                listener.onRequestEvent(request, event);
            }
        }
    }
    public interface RequestEventListener {
        void onRequestEvent(Request request, @RequestEvent int event);
    }

在查看volley源码的过程中我们可以看到很多request.addMarker和request.sendEvent,这两个方法就是在记录请求过程的日志信息:

  • request.addMarker:是通过volley自带的VolleyLog做日志的记录和输出
  • request.sendEvent:是通过RequestEventListener将请求过程发送出去

所以我们可以通过实现RequestEventListener接口并重写onRequestEvent方法来做请求过程日志的持久化,可以通过获取RequestQueue然后通过addRequestEventListener方法将自定义的RequestEventListener传递进去。

总结

  1. 底层connection的获取还是通过OKhttp来处理的
  2. 默认会创建4个NetworkDispatcher和1个CacheDispatcher并发执行请求。
  3. android2.3以下使用HttpClient请求网络接口,以上使用HttpURLConnection。
  4. 创建了一个大小为4096的ByteArrayPool池,用于IO操作时减少内存消耗。
  5. NetworkDispatcher和CacheDispatcher通过阻塞队列去执行request请求。
  6. 通过responseContents =NetworkUtility.inputStreamToBytes(inputStream, httpResponse.getContentLength(), mPool); 将response放入byte[]中,即全部加载进内存。

下面放一张Google官方提供一张线程切换大图:


image.png

你可能感兴趣的:(Volley源码分析)