【进阶android】Volley源码分析——Volley的线程

来源:互联网 发布:域名备案有什么用 编辑:程序博客网 时间:2024/05/16 07:51

        在上一篇文章中,我们主要分析了Volley一次网络请求的总体流程,并在此基础上初步分析了Request和RequestQueue两个Volley框架中较为重要的类。

       而本片文章,将在上一篇【进阶android】Volley源码分析——Volley的流程的基础上,更加深入结合Volley的源代码,进一步分析Volley的处理流程,及关于Volley两种线程的处理流程分析。

       本文的结构如下:首先我们会分析网络线程的处理流程;接着会在网络线程的基础上分析缓存线程。

       对网络流程的分析,我们主要侧重点于该子线程本身的处理逻辑;而对缓存线程的分析,我们则主要侧重于缓存线程与主线程之间的交互。

一、网络线程;

       根据上一篇文章的分析,我们知道网络线程对应的类是NetWorkDispatcher类;同时,默认情况下Volley框架会开启4个网络线程(size为4的网络线程池)。

      整个NetWorkDispatcher类的逻辑并不复杂,其属性无非是网络请求队列以及从RequestQueue类中传入的NetWork、Cache及ResponseDelivery三个引用。

    由于NetWorkDispatcher类是一个线程的映射类,所以其主要的方法就是run方法;run方法定义了网络线程所有的处理流程,而网络线程的作用就是根据一个Request对象,执行一次网络请求,并将请求的结果(响应或者错误)传递给主线程的过程。

    下面我们就根据这一认识,具体分析NetWorkDispatcher类中的run方法:

public void run() {
        //将网络线程的优先级设置为后台运行线程        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);        Request request;        while (true) {            try {                // Take a request from the queue.
                //第一步                request = mQueue.take();//从请求队列之中获取顶部的Request对象            } catch (InterruptedException e) {                // We may have been interrupted because it was time to quit.                if (mQuit) {                    return;                }                continue;            }            try {                request.addMarker("network-queue-take");                // If the request was cancelled already, do not perform the                // network request.
                //第二步                if (request.isCanceled()) {                    request.finish("network-discard-cancelled");//结束请求                    continue;                }                ...                // Perform the network request.第三步                NetworkResponse networkResponse = mNetwork.performRequest(request);                request.addMarker("network-http-complete");                // If the server returned 304 AND we delivered a response already,                // we're done -- don't deliver a second identical response.第四步                if (networkResponse.notModified && request.hasHadResponseDelivered()) {                    request.finish("not-modified");                    continue;                }                // Parse the response here on the worker thread.第五步                Response<?> response = request.parseNetworkResponse(networkResponse);                request.addMarker("network-parse-complete");                // Write to cache if applicable.                // TODO: Only update cache metadata instead of entire record for 304s.                if (request.shouldCache() && response.cacheEntry != null) {第六步                    mCache.put(request.getCacheKey(), response.cacheEntry);                    request.addMarker("network-cache-written");                }                // Post the response back.                request.markDelivered();                mDelivery.postResponse(request, response);//将响应传递至UI线程,第七步            } catch (VolleyError volleyError) {                parseAndDeliverNetworkError(request, volleyError);            } catch (Exception e) {                VolleyLog.e(e, "Unhandled exception %s", e.toString());                mDelivery.postError(request, new VolleyError(e));            }        }    }
   根据以上源码,可以看出run方法中的流程是一个典型的子线程处理流程;一个无限的while循环,一个决定是否退出无限循环的Boolean变量mQuit以及while循环之中的一次逻辑处理流程。

   对于一次处理流程逻辑,我们可以很容易的分析出一次处理逻辑共有七个步骤:

   1、从请求队列中take一个Request对象;NetWorkDispatcher类中的变量mQueue是一个BlockingQueue<Request>对象的引用;BlockingQueue是一个接口,表示一个阻塞队列;而其take方法的作用则是获取并移除该队列的头部元素;如果队列无头部元素,则该方法会一直阻塞直到出现一个可用的头部元素位置。

    2、根据第一步获取的Request对象,判断该Request对象是否已经在其他线程被取消了(例如主线程);如果该对象已经被取消了,则直接调用Request对象的finish方法结束流程,从而不进行网络请求;

    3、调用NetWork接口的performRequest方法进行真正的网络请求操作,并将原始的HttpResponse转换成NetWorkResponse对象;关于NetWork的performRequest方法的具体实现我们将在下文具体分析;至于NetWorkResponse类,它则是按照响应码、响应头参数、响应内容以及服务端的响应内容是否修改简单、初步的解析了一下原始HTTPResponse,它相当于原始HTTPResponse与最终Response<T>之间的一个中间过渡。

    4、根据第三步的NetWorkResponse对象判断服务端的内容是否被修改(例如服务器上的一张图片)?如果响应码为304,则表示服务端的内容自上一次访问后就在未改变;此时,如若当前Request对象已经被发送到UI线程,则结束当前流程,直接返回;

    5、调用Request对象的parseNetworkResponse方法将NetWorkResponse对象转换为最终的Response<T>对象;对于Request类而言,parseNetworkResponse方法是一个抽象方法,它要求子类根据自己的个性化定义这个方法的功能;而这个抽象方法的实质是将HTTPResponse原始响应中的原始响应内容(一般为byte)转换(解析)为一个通用的格式,例如一串字符串、一个JSON对象或者一张Bitmap;

    6、如果Request对象需要缓存,且Response具有其对应的Cache.Entry,则调用Cache接口的put方法将Response对应的Cache.Entry进行缓存;对于Cache接口如何缓存,请参考下一篇关于Volley的分析文章【进阶android】Volley源码分析——Volley的缓存;

    7、调用ResponseDelivery接口的postResponse方法,将Request对象和Response对象作为参数传递给主线程;如何传递,我们将在下面的缓存线程分析中侧重介绍。

     通过以上7步的分析,我们大致知道了网络线程的一次逻辑处理的流程。

        在分析完网络线程的一次逻辑处理的流程后,我们重点再看看第三步,也就是具体执行网络请求的这一步骤。

        根据上文,第三个是通过NetWork接口的performRequest方法来具体执行网络请求;对于NetWork接口,Volley框架有一个默认的实现类,即BasicNetWork类。

       BasicNetWork中有两个比较重要的属性:一个是HttpStack接口,是真正执行网络请求的接口;一个是ByteArrayPool对象,产生一个byte数组缓存响应内容。在我看来,BasicNetWorkHttpStack执行类的一个代理。它从performRequest方法可简单分为网络请求前处理、调用HttpStackperformRequest方法执行真正的网络请求、网络请求后的处理三部分。

       网络请求前处理主要是指根据Request对象的缓存实体添加相应的请求参数;

       HttpStack接口中的performRequest是真正执行网络请求的方法;Volley中有两个HttpStack接口的实现类,分别是HUrlStackHttpClientStack,前者依赖HTTPUrlConnection进行网络请求,后者依赖HTTPClient进行网络请求;

       网络请求后处理则是从原始HTTPResponse中提取响应码、响应头部参数以及响应内容;根据响应码判断该响应是否发生改变,以此来创出是否发生改变的NetWorkResponse对象。

        HttpStack接口中的performRequest方法返回的是原始的HttpResponse对象;NetWork接口中的返回的是NetWorkResponse对象;网络线程中的一次逻辑处理流程则最终得到一个Response<T>对象。

        至此,网络线程我们便大致分析完毕了;接着我们分析缓存线程。

二、缓存线程

       Volley框架中只会开启一条缓存线程,来进行缓存逻辑业务的处理;与网络线程相似,缓存线程也有一个对应的线程类,那就是CacheDispatcher类。

           与NetWorkDispatcher类相比,CacheDispatcher类逻辑会稍微复杂一点,但复杂得有限;CacheDispatcher类中也有四个比较重要的属性:一个缓存队列引用,一个Cache接口,一个ResponseDelivery接口以及一个网络队列引用。与NetWorkDispatcher相比,多了一个缓存队列的引用,少了一个NetWork网络请求接口。

      在网络线程分析之中,我们并未太详细的介绍ResponseDelivery接口;而在缓存线程之中我们除了分析缓存线程逻辑处理的流程外,还要侧重于介绍ResponseDelivery接口。

      与NetWorkDispatcher类似,CacheDispatcher中也只有一个重要的方法,那就是承载线程逻辑的run方法;同时缓存线程也是一个经典的线程模式:一个无限的while循环,一个决定是否退出无限循环的Boolean变量mQuit以及while循环之中的一次处理流程逻辑。

        直接贴上源码:

public void run() {
        ...
        //<strong>第一步</strong>        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);        // Make a blocking call to initialize the cache.        mCache.initialize();//初始化缓存        while (true) {            try {                // Get a request from the cache triage queue, blocking until                // at least one is available.<strong>第二步</strong>                final Request request = mCacheQueue.take();//获取请求                request.addMarker("cache-queue-take");                // If the request has been canceled, don't bother dispatching it.                if (request.isCanceled()) {//取消<strong>第三步</strong>                    request.finish("cache-discard-canceled");                    continue;                }                // Attempt to retrieve this item from cache.<strong>第四步</strong>                Cache.Entry entry = mCache.get(request.getCacheKey());//获取缓存                if (entry == null) {//<strong>第五步</strong>                    request.addMarker("cache-miss");                    // Cache miss; send off to the network dispatcher.                    mNetworkQueue.put(request);//无缓存,添加到网络请求队列之中                    continue;                }                // If it is completely expired, just send it to the network.                if (entry.isExpired()) {//缓存过期<strong>第六步</strong>                    request.addMarker("cache-hit-expired");                    request.setCacheEntry(entry);                    mNetworkQueue.put(request);//添加到缓存之中                    continue;                }                // We have a cache hit; parse its data for delivery back to the request.                //解析缓存中的响应内容及响应头参数<strong>第七步</strong>                request.addMarker("cache-hit");                Response<?> response = request.parseNetworkResponse(                        new NetworkResponse(entry.data, entry.responseHeaders));                request.addMarker("cache-hit-parsed");                //传递响应<strong>第八步</strong>                if (!entry.refreshNeeded()) {                    // Completely unexpired cache hit. Just deliver the response.                    mDelivery.postResponse(request, response);                } else {                    // Soft-expired cache hit. We can deliver the cached response,                    // but we need to also send the request to the network for                    // refreshing.                    request.addMarker("cache-hit-refresh-needed");                    request.setCacheEntry(entry);                    // Mark the response as intermediate.                    response.intermediate = true;                    // Post the intermediate response back to the user and have                    // the delivery then forward the request along to the network.                    mDelivery.postResponse(request, response, new Runnable() {                        @Override                        public void run() {                            try {                                mNetworkQueue.put(request);                            } catch (InterruptedException e) {                                // Not much we can do about this.                            }                        }                    });                }            } catch (InterruptedException e) {                // We may have been interrupted because it was time to quit.停止线程的处理               ...            }        }    }
       缓存线程中一次逻辑处理流程并不复杂,但是却要比网络线程多一个步骤,一共有八个步骤:

       1、在具体进行逻辑处理之前,将缓存线程的优先级设置为后台处理线程,并且初始化Cache接口;第一步大致与网络线程一致,即设置线程的优先级,不过与网络线程不同的是,缓存线程调用了Cache接口的initialize方法对Cache进行了初始化。至于Cache如何初始化,我们同样将在下一篇文章【进阶android】Volley源码分析——Volley的缓存一文中具体介绍;

       2、从队列之中获取一个Request对象;其中这个队列是缓存队列,而网络线程中的队列是网络队列;

       3、与网络线程的第三步一致,判断该Request对象是否已取消;

       4、通过Request对象获取缓存Key;从Cache接口中获取Cache.Entry(缓存实体);在上一篇文章之中我们简单提及了一下Cache接口;Cache接口是一系列Cache.Entry的集合,该接口抽象了一列操作该集合的方法,例如缓存Cache.Entry,获取Cache.Entry等等,这一系列分析我们同样也将在下一篇文章【进阶android】Volley源码分析——Volley的缓存一文中具体介绍;

        5、如果第四步获取的Cache.Entry为空,则表示该Request对象从来没有请求过网络,故而需要将该Request对象添加到网络请求队列之中,让网络线程对其进行网络请求;

        6、如果Cache.Entry对象过期,则需要重新获取网络请求;因此也要如同第五步一样,将该Request对象添加到网络请求队列之中,并且将该Cache.Entry对象映射到Request对象之中;将Cahce.Entry与Request对象映射的作用是网络线程对该Request对象进行网络请求后发现得到的响应并未在服务端发生改变(响应码为304)时,直接通过该Request对象映射Cache.Entry来生成最终的Response<T>对象,而非通过原始的HttpResponse来生成;

        7、直接通过Cache.Entry对象生成一个NetWorkResponse对象,并以此作为该Request对象中parseNetWorkResponse方法的入参,生成最终的Response<T>对象;

        8、通过ResponseDelivery接口中的postResponse方法,将Request对象、Response对象作为入参传递给UI线程;如果生成的Response<T>需要刷新,则将响应传递给UI线程之后,还要讲该Request对象重新添加到网络请求队列之中,进行刷新。

        通过以上八个步骤的梳理,我们也大致明白了缓存线程一次逻辑处理的流程了。

        下面我们根据网络线程和缓存线程进行一下几点总结:

        *   两个流程都是经典的线程处理逻辑,皆可停止;

    * 两个流程中的NetWork、Request(抽象类)、Cache以及ResponseDelivery等不是接口就是抽象类,具有高度的抽象性;由此便可以看出Volley在设计模式上面的一些运用,从而间接说明了Volley框架的可扩展性;

    * Request对象贯穿整个流程;无论是网络请求、解析响应、缓存处理还是与主线程交互都有此对象的参与;

    * 响应的变化

           非304情况:HttpResponse(原始响应)——>NetWorkResponse(过渡产物)——>Response<T>(最终形式);

           是304情况:Cache.Entry——>NetWorkResponse(过渡产物)——>Response<T>(最终形式)

        * 两个流程的相似点:生成NetWorkResponse对象,由此对象生成Response<T>对象,在传递给主线程;

        * 两个流程的不同点:生成NetWorkResponse对象的来源不同,网络线程是来自于网络的原始HttpResponse,缓存线程是来自于缓存的Cache.Entry对象。

         最后我们来分析一下ResponseDelivery接口;ResponseDelivery接口主要负责网络线程或者缓存线程与ui线程进行通信,以便UI线程能够通过网络请求的结果对界面进行一定的更新;与Cache、NetWork等接口相似,Volley框架也为ResponseDeliVery接口提供了一个默认的执行类,那就是ExecutorDelivery类。

    ExecutorDelivery类中只有一个重要的属性:Executor,Executor是一个接口;ExecutorDelivery类通过构造器实例化一个Excutor的执行类,代码如下:

public ExecutorDelivery(final Handler handler) {        // Make an Executor that just wraps the handler.        //生成一个封装handler的Executor        mResponsePoster = new Executor() {            @Override            public void execute(Runnable command) {                handler.post(command);            }        };    }
     通过此构造器我们可以得知,ExecutorDelivery类的本质还是通过Handler机制以消息的形式,向主线程传递一个回调(Runnable)。

     下面我就看看ExecutorDelivery类是如何传递的:

public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {        request.markDelivered();        request.addMarker("post-response");        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));    }
        很显然,ExecutorDelivery类传递给主线程的回调是一个ResponseDeliveryRunnable对象;该对象的构造函数中包含了Request对象和Response对象;如此当主线程在执行该回调时,就可以拿到Request对象和Response对象了。我们看看ResponseDeliveryRunnable对象的run方法,代码如下:

public void run() {            // If this request has canceled, finish it and don't deliver.            //如果请求取消了,调用finish方法            if (mRequest.isCanceled()) {                mRequest.finish("canceled-at-delivery");                return;            }            // Deliver a normal response or error, depending            if (mResponse.isSuccess()) {                //处理响应结果,一般deliveryResponse方法之中都调用某个监听器的方法(监听器模式)                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) {                ...            } else {                mRequest.finish("done");//请求最终完成            }            ...       }
       至此,Volley框架里的两种类型的线程我们就分析结束了。

       当然由于本人自身水平所限,文章肯定有一些不对的地方,希望大家指出!

    愿大家一起进步,谢谢!

    

    





0 0