Google网络请求框架Volley源码浅析(三)

上一章中,我们分析了Volley中的Cache缓存机制以及Request请求队列的执行流程。但是,更深入的执行流程,我们还没有接触到,本章带大家深挖一下请求的执行流程。

Dispatcher

我们在上一章中,知道了在Request的start方法中会启动两个调度器,这两个调度器会进行Request的分发操作,他们分别是操作缓存的CacheDispatcher和操作网络的NetworkDispatcher,那我们就从这两个调度器开始进行深挖吧。

CacheDispatcher

我们先来看看这个CacheDispatcher,该分发器用于操作缓存队列,跟进去一看,原来是一个继承了Thread的线程,构造参数有四个,分别是:

  1. mCacheQueue:存储了分流到缓存request的队列,从RequestQueue中传递进来的,我们在之前已经分析过它了
  2. mNetworkQueue:存储了分流到网络请求的request队列,和上边那个一样,它的作用就是当缓存操作失败的时候,我们还可以把request插入到这个网络队列中,保证我们可以获取到请求数据
  3. mCache:既然是缓存操作就肯定需要缓存接口,这个mCache就是我们要操作的缓存接口,通过之前的分析我们应该知道这个接口的子类对象应该是DiskBasedCache
  4. mDelivery:请求结果的回调器,用于将操作数据回调给主线程

这个缓存分发器还有一个quit方法:

/**
 * 强制性立即停止调度器.  如果在队列中还有其他请求, 它们不能保证被处理 .
 */
public void quit() {
    mQuit = true;
    interrupt();
}

这个方法我们在RequestQueue中看到被stop调用过,用于停止分发器的工作,我们看到先设置了mQuit变量为true,然后使用interrupt停止线程。
分发器的具体工作还是放在run中来执行的,我们看看在这个子线程中是怎么工作的吧:

@Override
public void run() {
    if (DEBUG) VolleyLog.v("开启一个新的调度器");
    // 设置线程优先级,THREAD_PRIORITY_BACKGROUND:后台线程
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

    // 初始化缓存对象.
    // 在子线程内部调用
    mCache.initialize();

    Request<?> request;
    // 无限循环
    while (true) {
        // 释放先前的请求对象来避免请求对象的泄漏
        // 这一句是有必要的
        request = null;
        try {
            // 从缓存队列中取出一个request.
            request = mCacheQueue.take();
        } catch (InterruptedException e) {
            // 取出request发生中断
            // 如果外部要求退出循环,在这里return退出无限循环.
            if (mQuit) {
                return;
            }
            // 否则继续向下进行下一个请求
            continue;
        }
        try {
            // 标记该request已经被取出
            request.addMarker("cache-queue-take");

            // 如果请求被取消,请不要打扰它 .
            if (request.isCanceled()) {
                request.finish("cache-discard-canceled");
                continue;
            }

            // 试图从缓存中检索这个项目 .
            Cache.Entry entry = mCache.get(request.getCacheKey());
            if (entry == null) {
                request.addMarker("cache-miss");
                // 缓存中找不到;发送到网络调度程序 .
                mNetworkQueue.put(request);
                continue;
            }

            // 缓存已经过期,发送到网络调度程序.
            if (entry.isExpired()) {
                request.addMarker("cache-hit-expired");
                request.setCacheEntry(entry);
                mNetworkQueue.put(request);
                continue;
            }

            // 找到对应的缓存; 解析数据返回给 request.
            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 用的是软超时的缓存,需要刷新.
                response.intermediate = true;

                // 把数据立即传递给用户 然后我们需要立即请求网络获取新的资源.
                final Request<?> finalRequest = request;
                mDelivery.postResponse(request, response, new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // 把请求放进网络队列中
                            mNetworkQueue.put(finalRequest);
                        } catch (InterruptedException e) {
                            // 除此之外我们做不了别的了.
                        }
                    }
                });
            }
        } catch (Exception e) {
            VolleyLog.e(e, "未知异常 %s", e.toString());
        }
    }
}

在子线程中,一开始先设置线程的优先级为background,这样做可以理解,我们的线程本来就不需要和外部进行交互,设成后台线程很有好处。然后调用了Cache的initialize方法进行初始化,这个方法的内部我们之前已经分析过了,DiskBasedCache应该会去做遍历缓存目录等那一套动作。紧接着开启了一个无限循环,开始进行工作了。
第一步先初始化局部变量request为null,是做了一个保险动作,防止上一次操作的request遗留下来,影响本次的操作,然后从mCacheQueue中take出一个request出来,而且取出失败还会根据mQuit判断是退出循环还是继续遍历下一个。取出成功后根据request的isCanceled判断用户是不是已经取消请求,这个也是很有必要的,因为request对象是用户持有的,他随时可以取消这个request,既然用户已经取消了本次请求,我们又何必浪费资源来操作它呢?然后我们使用Cache的get方法取出和CacheKey对应的Entry出来,就获取了对应的缓存资源,这些步骤我们之前分析Cache的时候就已经分析透彻了,下面这一步就像我们之前说的那样,如果取出来的缓存资源为空的话,我们还得把这个request放到网络队列中去,让它从网络中获取资源,也不至于造成获取不到数据的结果。isExpired用于判断缓存是否过期,如果过期的话,同样需要放进网络队列中去获取新的资源,把缓存封装成Response之后还需要判断资源的refreshNeeded,这个方法我们知道,用于判断缓存是否软过期,也就是说已经过期了但是还可以临时使用的情况,如果已经软过去的话,response还需要打上intermediate标记,同时回传响应数据的时候还需要回传一个Runnable,这个Runnable的工作就是及时的从网络更新资源。
So Easy !很简单嘛,经过层层过滤之后我们拿到了缓存数据,然后就调用ResponseDelivery的postResponse就把数据返回到主线程了。我们把思路理的清楚一点:

  1. 第一种是已经isCanceled的Request,用户取消了操作,直接胡略掉
  2. 第二种是没有找到Entry的Request,它会被安排在网络队列中让别人帮忙处理
  3. 第三种是缓存已经过期的Request,本地缓存是不能要了,所以也得去网路队列
  4. 剩下的就都是找到缓存且没有过期的Request,这也分两种情况:
    1. 缓存没有软过期的,直接返回到主线程
    2. 缓存暂且还可以用一下的,打上标记,并标明需要马上从网络刷新,再返回给主线程

至此,缓存调度器的工作已经被我们尽收眼底,下面看看网络调度器的工作了。

NetworkDispatcher

NetworkDispatcher一大半都和缓存调度器是一样的,他们是如此的类似,我们就只分析两者不同之处了:

  1. 网络调度器增加了一个addTrafficStatsTag方法,该方法在子线程被调用,用于分析子线程的网络状况。但是只有在API14及以上才会被调用
  2. 网络调度器在调度产生异常的时候记录本次的执行时间,并使用mDelivery发送到主线程中
  3. 网络调度器是通过mNetwork的performRequest方法获取请求资源的,这点是和缓存调度器差别最大的地方
  4. 缓存调度器还对返回的资源做了判断,如果是304系列的返回或者Request的资源已经返回给主线程,就不需要重复发送数据了。这也是一种提高效率的手段。

除此之外,两者在其他方面没有任何区别了,其实两者都是一个线程,最主要的工作都在run里边,分析起来也没有一点难度。

ResponseDelivery

调度器的工作分析完了,但是我们还有一个地方没有搞明白,两个调度器最后都调用了ResponseDelivery的postResponse方法来传递数据给主线程,具体它是怎么传递的呢?到底传递给谁呢?我们有必要仔细跟进去看看。
跟进去ResponseDelivery一看,还以为被忽悠了,ResponseDelivery是个接口,里边就三个接口方法:

/**
 * 把从网络或者缓存中的数据回传给调用者.
 */
void postResponse(Request<?> request, Response<?> response);

/**
 * 把从网络或者缓存中的数据回传给调用者.
 * Runnable将会在数据传递之后立即执行.
 */
void postResponse(Request<?> request, Response<?> response, Runnable runnable);

/**
 * 为指定的request回传异常.
 */
void postError(Request<?> request, VolleyError error);

这三个方法我们在调度器已经见过了,没有什么分析的价值,回过去在RequestQueue中,找到了实现的子类,原来是ExecutorDelivery。

ExecutorDelivery

ExecutorDelivery实现了ResponseDelivery,其中有一个Executor成员变量,Executor是Java中常用的和定时任务相关的接口,我们看到Executor封装了handler,在execute中将Runnable传递给了handler,由handler的post方法将Runnable发送到主线程来执行,postResponse是这样执行的:

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

那么我们的这个Runnable就是new出来的这个ResponseDeliveryRunnable了,它会handler的post方法传递到主线程,那么ResponseDeliveryRunnable的run方法就会在主线程中执行,我们看看它的run:

@SuppressWarnings("unchecked")
 @Override
 public void run() {
     // 运行在主线程中
     // request已经取消,就不用再传递了
     if (mRequest.isCanceled()) {
         mRequest.finish("canceled-at-delivery");
         return;
     }
     // 传送响应或者异常
     if (mResponse.isSuccess()) {
         mRequest.deliverResponse(mResponse.result);
     } else {
         mRequest.deliverError(mResponse.error);
     }

     // 临时响应,增加一个标记, 否则的话我们的任务就完成了,request可以finish了.
     if (mResponse.intermediate) {
         mRequest.addMarker("intermediate-response");
     } else {
         mRequest.finish("done");
     }

     // 如果传递过来的还有runnable,需要把它执行完毕.
     if (mRunnable != null) {
         mRunnable.run();
     }
}
  1. 这里也要判断一下isCanceled,由次可见Volley对效率的注重程度,
  2. 根据isSuccess来判断发送的是响应数据还是异常信息
  3. intermediate为true的话我们知道响应数据是软超时,在这里给request打上一个”intermediate-response”标记
  4. 最后一个参数是一个runnable,如果非空的话就会执行里边的run方法,这个方法的内容我们在缓存调度器中已经见过了,需要知道的是,由于ResponseDeliveryRunnable的run方法运行在主线程,所以最后这个Runnable的run只会另起新的线程运行,这些线程顺序我们需要搞清楚。
  5. Request的finish方法表明该request已经执行完毕,可以回收了

看来这个ExecutorDelivery最主要的工作就是把响应数据带到主线程中,然后调用request的deliverResponse或者deliverError方法进一步回传。

小结

至此,我们把Volley中请求的分发调度,执行流程,响应信息传递以及主线程和子线程之间的交互都分析完了,还差最后一公里了,我们只需要分析request的deliverResponse或者deliverError方法到底干了什么就彻底搞明白了,关于Request家族的故事很庞大,我们将在下一节来仔细研究他们。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值