Volley源码剖析

栏目: IOS · Android · 发布时间: 6年前

内容简介:Volley是Google在2013年推出来的HTTP库,旨在帮助开发者更快更简便的实现网络请求。说说为什么要分析Volley的源码吧,因为Volley中线程的转换时通过我们先整体看下 Volley 是如何进行一个完整的网络请求的:上面代码主要做了三件事:
Volley is an HTTP library that makes networking for Android apps easier and most importantly, faster.

Volley是Google在2013年推出来的HTTP库,旨在帮助开发者更快更简便的实现网络请求。说说为什么要分析Volley的源码吧,因为Volley中线程的转换时通过 ThreadHandler 来实现的,跟之前的两篇都有着很大的联系(ps:Okhttp和Retrofit都撕不动^_^),哈哈,后面会一步一步的给大家带来Okhttp和Retrofit等更多的源码分析!

执行一个网络请求

我们先整体看下 Volley 是如何进行一个完整的网络请求的:

val requestQueue: RequestQueue = Volley.newRequestQueue(context)
val url = "https://www.baidu.com"
val request = StringRequest(url,
        Response.Listener<String> {
            Log.d("taonce", "request result is: $it")
        },
        Response.ErrorListener { })

requestQueue.add(request)
复制代码

上面代码主要做了三件事:

  • 创建一个请求队列 RequestQueue : Volley.newRequestQueue(context)
  • 创建一个请求 Request : StringRequest(String url, Listener<String> listener, @Nullable ErrorListener errorListener)
  • Request 加入到 RequestQueue : requestQueue.add(request)

接下来通过源码的方法来看看这三步内部做了什么操作。

创建 RequestQueue 和 Request进入 Volley.newRequestQueue(context) 源码:

进入 Volley.newRequestQueue(context) 源码:

public static RequestQueue newRequestQueue(Context context) {
    // 实际上是调用了另外一个构造方法
    return newRequestQueue(context, (BaseHttpStack) null);
}
复制代码

继续查看 newRequestQueue(Context context, BaseHttpStack stack) :

BasicNetwork network;
if (stack == null) {
    // 判断是否大于等于 Android 2.3 版本
    if (Build.VERSION.SDK_INT >= 9) {
        // 如果是 Android 2.3 及其以上,就用 HurlStack() 进行网络请求
        network = new BasicNetwork(new HurlStack());
    } else {
        // 如果是 Android 2.3 以下,那么就采用 HttpClientStack(HttpClient) 进行网络请求
        network = new BasicNetwork(
                        new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));
    }
} else {
    // 如果stack不为空,那么就采用传进来的stack
    network = new BasicNetwork(stack);
}

return newRequestQueue(context, network);
复制代码

可以得出:

  • 创建一个 BasicNetwork 对象
  • stack 为空 ----> Android 2.3 及其以上创建 HurlStack() 对象,并且传给 networkHurlStack() 采用的是 HttpURLConnetion 进行网络请求的。
  • stack 为空 ----> Android 2.3 以下创建 HttpClientStack() 对象,并且传给 networkHttpClientStack() 采用的则是 HttpClient 进行网络请求,不过现在( 当前版本1.1.1 ) new HttpClientStack(HttpClient client) 已经被标记了 @Deprecated 了,因为它采用的 HttpClient Google 在 Android 6.0 中移除了对 Apache HTTP 客户端的支持,并且从 Android P 开始, org.apache.legacy 库将从 bootclasspath 中删除。
  • stack 不为空 ----> 直接将 stack 传给 network
  • 创建一个 RequestQueue() 对象并返回

到此为止,大家只要记住上面几个对象就好,接下来会慢慢的讲解对象分别做了什么工作。

这里还是要着重介绍下 newRequestQueue() 方法:

private static RequestQueue newRequestQueue(Context context, Network network) {
    File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
    // 创建请求队列
    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
    queue.start();
    return queue;
}
复制代码

创建 RequestQueue() 的过程中有一个很重要的点,我们来看看它的构造方法:

public RequestQueue(Cache cache, Network network) {
    // 默认 Network Thread 数目为 DEFAULT_NETWORK_THREAD_POOL_SIZE = 4
    this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
    this(
            cache,
            network,
            threadPoolSize,
        	// 注意点就是这:创建 Handler 的时候,传递的是 Looper.getMainLooper(),也就是后面将请求结果回调到主线程的关键。
            new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}

public RequestQueue(
        Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) {
    mCache = cache;
    mNetwork = network;
    mDispatchers = new NetworkDispatcher[threadPoolSize];
    mDelivery = delivery;
}
复制代码

上面第二个构造方法中创建了一个 ExecutorDelivery() , 这个对象实现了 ResponseDelivery 接口的类,用来将网络请求的结果或者缓存中的结果分发到主线程。

再来看看上面 queue.start() 方法做了什么操作:

// 开启5个线程
public void start() {
    // 如果5个线程不为空,先停止它们
    stop(); 
    // 创建 CacheDispatcher,并开启它
    mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
    mCacheDispatcher.start();

    // 创建4个NetworkDispatcher,并开启它们
    for (int i = 0; i < mDispatchers.length; i++) {
        NetworkDispatcher networkDispatcher =
                new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
        mDispatchers[i] = networkDispatcher;
        networkDispatcher.start();
    }
}
复制代码

到此,我们前奏分析完了,有一些概念先不急着看,只要知道它的作用就行,后面我们来一个一个击破。

添加请求到请求队列中 : RequestQueue.add( request )

废话不多了,直接进入源码: RequestQueue.add(request)

public <T> Request<T> add(Request<T> request) {
    // 将request和当前的RequestQueue绑定
    request.setRequestQueue(this);
    // 将request添加到set集合中,用于执行 cancelAll() 操作
    synchronized (mCurrentRequests) {
        mCurrentRequests.add(request);
    }

    // 给request添加一些标记
    request.setSequence(getSequenceNumber());
    request.addMarker("add-to-queue");
    sendRequestEvent(request, RequestEvent.REQUEST_QUEUED);

    // 判断request是否需要缓存,对于 Get 以外的请求,默认关闭缓存	
    if (!request.shouldCache()) {
        mNetworkQueue.add(request);
        return request;
    }
    mCacheQueue.add(request);
    return request;
}
复制代码

添加 request 的操作算是很简单的了,无非是根据 request 是否需要缓存,将 request 添加到 CacheQueue 或者 NetworkQueue 中。

看到这,我们是不是在想,怎么添加之后就没有动作了?非也非也,我们回顾下,在创建 requestQueue 的同时,是不是创建了5个子线程并且开启了它们,它们内部使用的 CacheQueue 或者 NetworkQueue 不就是上面提到的么。接下来,我们先看看 CacheDispatcher 内部做了什么。

CacheDispatcher 调度器剖析:

CacheDispatcher 就是一个继承了 Thread 的类,我们在执行 RequestQueue.start() 的时候创建了它,并开启了它,现在我们来看看它的 run() 方法:

@Override
public void run() {
    // 设置线程优先级为后台线程
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    // 实则是执行了DiskBasedCache的initialize()方法
    mCache.initialize();
    while (true) {
        try {
            processRequest();
        } catch (InterruptedException e) {
            // 中断当前线程,并且退出死循环,mQuit在调用CacheDispatcher的quit()方法之后会被赋值为true
            if (mQuit) {
                Thread.currentThread().interrupt();
                return;
            }
        }
    }
}
复制代码

开了一个死循环,然后调用了 processRequest() , 我们接着看这个方法:

private void processRequest() throws InterruptedException {
        // 从CacheQueue中取出一个可用的request
        final Request<?> request = mCacheQueue.take();
        processRequest(request);
    }

    @VisibleForTesting
    void processRequest(final Request<?> request) throws InterruptedException {
        request.addMarker("cache-queue-take");
        request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_STARTED);

        try {
            //request如果被取消了,就直接返回
            if (request.isCanceled()) {
                request.finish("cache-discard-canceled");
                return;
            }

            Cache.Entry entry = mCache.get(request.getCacheKey());
            // 没有缓存就把request添加到NetworkQueue中
            if (entry == null) {
                request.addMarker("cache-miss");
                // 没有缓存,并且等待队列中也没有此request,那么就直接加入到NetworkQueue中
                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    mNetworkQueue.put(request);
                }
                return;
            }

            // 如果缓存过期了,也是一样把request添加到NetworkQueue中
            if (entry.isExpired()) {
                request.addMarker("cache-hit-expired");
                request.setCacheEntry(entry);
                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    mNetworkQueue.put(request);
                }
                return;
            }

            // 有缓存并且没有过期
            request.addMarker("cache-hit");
            // 根据缓存的内容解析
            Response<?> response =
                    request.parseNetworkResponse(
                            new NetworkResponse(entry.data, entry.responseHeaders));
            request.addMarker("cache-hit-parsed");
			// 是否需要更新
            if (!entry.refreshNeeded()) {
                // 不需要更新,直接将结果调度到主线程
                mDelivery.postResponse(request, response);
            } else {
                request.addMarker("cache-hit-refresh-needed");
                request.setCacheEntry(entry);
                response.intermediate = true;
				// 判断是否有相同缓存键的任务在执行
                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    // 需要更新结果,先将结果调度到主线程,然后执行new runnable(){}
                    // runnable中就是将request添加到NetworkQueue中,更新一下内容
                    mDelivery.postResponse(
                            request,
                            response,
                            new Runnable() {
                                @Override
                                public void run() {
                                    try {
                                        mNetworkQueue.put(request);
                                    } catch (InterruptedException e) {
                                        // Restore the interrupted status
                                        Thread.currentThread().interrupt();
                                    }
                                }
                            });
                } else {
                    // request已经加入到mWaitingRequests中
                    // 直接把结果调度到主线程
                    mDelivery.postResponse(request, response);
                }
            }
        } finally {
            request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_FINISHED);
        }
    }
复制代码

我们在 processRequest 中可以看到有一个方法经常出现,那就是 mWaitingRequestManager.maybeAddToWaitingRequests(request) ,它的作用是判断当前这个 request 是否有存在相同缓存键的请求已经处于运行状态,如果有,那么就将这个 request 加入到一个等待队列中,等到相同缓存键的请求完成。

总结一下 CacheDispatcher 主要步骤:

  • CacheQueue 中循环取出 request
  • 如果缓存丢失,加入到 NetworkQueue 中;
  • 如果缓存过期,加入到 NetworkQueue 中;
  • 将缓存中的数据解析成 Response 对象;
  • 如果不需要更新,直接将结果回调到主线程, 回调操作等介绍完NetworkDispatcher之后一起深入剖析;
  • 如果需要更新,先将结果回调到主线程,然后再将 request 加入到 NetworkQueue 中。

NetworkDispatcher 调度器剖析:

NetworkDispatcherCacheDispatcher 十分类似,都是 Thread 的子类,下面重点看下它的 run() 方法:

@Override
public void run() {
    // 设置线程优先级为后台线程
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    while (true) {
        try {
            processRequest();
        } catch (InterruptedException e) {
            // 调用quit()方法之后,mQuit就会被赋值true
            if (mQuit) {
                Thread.currentThread().interrupt();
                return;
            }
        }
    }
}
复制代码

继续看 processRequest() 方法:

private void processRequest() throws InterruptedException {
    // 从NetworkQueue中取出request
    Request<?> request = mQueue.take();
    processRequest(request);
}

@VisibleForTesting
void processRequest(Request<?> request) {
    long startTimeMs = SystemClock.elapsedRealtime();
    request.sendEvent(RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED);
    try {
        request.addMarker("network-queue-take");

        // 如果request被取消了,那么就不执行此request
        if (request.isCanceled()) {
            request.finish("network-discard-cancelled");
            request.notifyListenerResponseNotUsable();
            return;
        }

        addTrafficStatsTag(request);

        // 还记得这个mNetwork么,它就是Volley.newRequestQueue()方法里的BasicNetwork对象,一会我们来看看mNetwork.performRequest()方法是如何得到NetworkResponse的
        NetworkResponse networkResponse = mNetwork.performRequest(request);
        request.addMarker("network-http-complete");

        // notModified是服务端返回304,hasHadResponseDelivered()是request已经回调过了
        if (networkResponse.notModified && request.hasHadResponseDelivered()) {
            request.finish("not-modified");
            request.notifyListenerResponseNotUsable();
            return;
        }

        // 将NetworkResponse解析成Response对象,在子线程中执行
        Response<?> response = request.parseNetworkResponse(networkResponse);
        request.addMarker("network-parse-complete");

        // 将request写入缓存
        if (request.shouldCache() && response.cacheEntry != null) {
            mCache.put(request.getCacheKey(), response.cacheEntry);
            request.addMarker("network-cache-written");
        }

        request.markDelivered();
        // 回调结果至主线程
        mDelivery.postResponse(request, response);
        request.notifyListenerResponseReceived(response);
    } 
    // 以下都是处理异常错误,然后也需要回调至主线程
    catch (VolleyError volleyError) {
        volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
        parseAndDeliverNetworkError(request, volleyError);
        request.notifyListenerResponseNotUsable();
    } 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);
        request.notifyListenerResponseNotUsable();
    } finally {
        request.sendEvent(RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_FINISHED);
    }
}
复制代码

通过 NetworkDispatcher.run() 方法可以发现,主要分为以下几步:

  • 通过 BasicNetwork.performRequest(request) 得到 NetworkResponse 对象;
  • 通过 request.parseNetworkResponse(networkResponse) 解析得到 Response 对象;
  • 通过 mDelivery 将成功结果或者失败结果回调到主线程。

现在我们依次来分析下这三步:

  1. 请求网络,得到 NetworkResponse

    BasicNetwork 实现了 Network 接口,其主要代码集中在 NetworkResponse performRequest(Request<?> request) throws VolleyError 中,也就是执行特定的请求。

    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            List<Header> responseHeaders = Collections.emptyList();
            try {
                // 得到请求头信息
                Map<String, String> additionalRequestHeaders =
                        getCacheHeaders(request.getCacheEntry());
                // 具体的网络请求是靠BaseHttpStack执行的
                httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);
                int statusCode = httpResponse.getStatusCode();
    
                responseHeaders = httpResponse.getHeaders();
                // 下面就是根据不同的状态码返回不同的NetworkResponse对象了,具体就不分析了
                if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
                    Entry entry = request.getCacheEntry();
                    if (entry == null) {
                        return new NetworkResponse(
                                HttpURLConnection.HTTP_NOT_MODIFIED,
                                /* data= */ null,
                                /* notModified= */ true,
                                SystemClock.elapsedRealtime() - requestStart,
                                responseHeaders);
                    }
                }
                // 省略大部分代码...
    }
    复制代码

    通过上面源码可以看出, BasicNetwork 就是封装了一下 NetworkResponse 对象蛮,并没有涉及到网络请求,我们继续深入到 BaseHttpStack.executeRequest(request, additionalRequestHeaders) 源码中。

    public abstract class BaseHttpStack implements HttpStack {
        public abstract HttpResponse executeRequest(
                Request<?> request, Map<String, String> additionalHeaders)
                throws IOException, AuthFailureError;
    }
    复制代码

    我们发现 BaseHttpStack 是一个抽象类,那么具体的请求是在哪呢,不知道你是否还有印象,在 Volley.newRequestQueue() 中,我们创建 BasicNetwork 的时候是根据 Android 版本传入不同的 BaseHttpStack 子类,Android 2.3 以上我们传入的是 HurlStack 对象,下面就看看 HurlStack.executeRequest() 方法吧。

    @Override
    public HttpResponse executeRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError {
        // 得到url
        String url = request.getUrl();
        HashMap<String, String> map = new HashMap<>();
        map.putAll(additionalHeaders);
        // Request.getHeaders() takes precedence over the given additional (cache) headers).
        map.putAll(request.getHeaders());
    	...
        URL parsedUrl = new URL(url);
        // 通过HttpURLConnection来请求网络
        HttpURLConnection connection = openConnection(parsedUrl, request);
    }
    复制代码

    其实 HurlStack.executeRequest() 方法里,就是借助了 HttpURLConnection 对象来请求网络,并根据不同的条件返回不同的 HttpResponse 对象的。

  2. 解析 NetworkResponse , 得到 Response

    解析过程是定义在 Request 抽象类的 protected abstract Response<T> parseNetworkResponse(NetworkResponse response) 抽象方法中,对于每一种具体的 Request 类,比如: StringRequestJsonObjectRequest 等等都有自己的实现,下面我们来看看 StringRequest 中的 parseNetworkResponse() 方法:

    @Override
    @SuppressWarnings("DefaultCharset")
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            // 将response中的data信息取出来
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException e) {
            // Since minSdkVersion = 8, we can't call
            // new String(response.data, Charset.defaultCharset())
            // So suppress the warning instead.
            parsed = new String(response.data);
        }
        // 封装成Response对象
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
    }
    复制代码
  3. 回调主线程,如果你看过我之前的Handler源码剖析 文章话,那么这一步就很简单了,我们来理一理:

    回调是通过 ResponseDelivery mDelivery 对象来执行的,这个对象最早是在创建 RequestQueue() 的时候初始化的,我在那一小节特意标注了,再来回顾下:

    public RequestQueue(Cache cache, Network network, int threadPoolSize) {
            this(
                    cache,
                    network,
                    threadPoolSize,
                	// 创建ExecutorDelivery对象,传入一个Handler对象,并且Handler绑定的是主线程的Looper
                    new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }
    
    public class ExecutorDelivery implements ResponseDelivery {
        private final Executor mResponsePoster;
        /**
         * ExecutorDelivery构造函数,内部初始化了mResponsePoster接口
         * 并且在execute()方法里调用了handler.post()方法
         */
        public ExecutorDelivery(final Handler handler) {
            // 初始化Execute对象
            mResponsePoster =
                    new Executor() {
                        @Override
                        public void execute(Runnable command) {
                            handler.post(command);
                        }
                    };
        }
    }
    复制代码

    知道了它的初始化,我们再来看看它是如何实现回调的:

    Volley 中回调是通过 postResponse() 方法的 :

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

    postResponse() 最终会调用 mResponsePoster 对象的 execute() 方法,传入了一个 ResponseDeliveryRunnable 对象,它实现了 Runnable 接口, execute() 方法会通过 Handler.post(runnable)ResponseDeliveryRunnable 放入消息队列。最后我们来看看这个 ResponseDeliveryRunnablerun() 方法在主线程中做了什么操作:

    @SuppressWarnings("unchecked")
    @Override
    public void run() {
    
        // If this request has canceled, finish it and don't deliver.
        if (mRequest.isCanceled()) {
            mRequest.finish("canceled-at-delivery");
            return;
        }
    
        if (mResponse.isSuccess()) {
            // 执行成功的回调,在具体的Request实现类中,比如StringRequest就会调用listener.onResponse(string)回调
            mRequest.deliverResponse(mResponse.result);
        } else {
            // 执行失败的回调,在request中,直接回调了listener.onErrorResponse(error)
            mRequest.deliverError(mResponse.error);
        }
    
        // intermediate默认为false,但是在CacheDispatcher的run()中,如果需要更新缓存,那么就会置为true
        if (mResponse.intermediate) {
            mRequest.addMarker("intermediate-response");
        } else {
            mRequest.finish("done");
        }
    
        // 如果传入了runnable不为空,那就就执行runnable.run()方法
        // 回忆下在CacheDispatcher的run()方法中,如果request有缓存,但是需要更新缓存的时候,mDelivery是不是调用的带runnable的方法
        if (mRunnable != null) {
            mRunnable.run();
        }
    }
    复制代码

    分析到这的时候,大家可以借助下方的流程图理一理Volley的整体流程,主要理解一下缓存线程和网络线程的转换,子线程和主线程的转换:

    Volley源码剖析

    源码分析的文字还在不断的更新,如果本文章你发现的不正确或者不足之处,欢迎你在下方留言或者扫描下方的二维码留言也可!

    Volley源码剖析

    扫描关注公众号


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

软件框架设计的艺术

软件框架设计的艺术

[捷] Jaroslav Tulach / 王磊、朱兴 / 人民邮电出版社 / 2011-3 / 75.00元

本书帮助你解决API 设计方面的问题,共分3 个部分,分别指出学习API 设计是需要进行科学的训练的、Java 语言在设计方面的理论及设计和维护API 时的常见情况,并提供了各种技巧来解决相应的问题。 本书作者是NetBeans 的创始人,也是NetBeans 项目最初的架构师。相信在API 设计中遇到问题时,本书将不可或缺。 本书适用于软件设计人员阅读。一起来看看 《软件框架设计的艺术》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换