从源码分析Android的Volley库的工作流程

时间:2022-12-05 17:44:15

volley现在已经被官方放到aosp里面,已经逐步成为android官方推荐的网络框架。

类抽象
对http协议的抽象
requeset

顾名思义,对请求的封装,实现了comparable接口,因为在volley中是可以指定请求的优先级的,实现comparable是为了在request任务队列中进行排序,优先级高的request会被优先调度执行。
networkresponse
http响应的封装,其中包括返回的状态码 头部 数据等。
response
给调用者返回的结果封装,它比networkresponse更加简单,只包含三个东西:数据 异常 和 cache数据。
network
对httpclient的抽象,接受一个request,返回一个networkresponse

反序列化抽象
所谓反序列化,就是将网络中传输的对象变成一个java对象,volley中是通过扩展request类来实现不同的反序列化功能,如jsonrequest stringrequest,我们也可以通过自己扩展一些request子类,来实现对请求流的各种定制。

请求工作流抽象

requestqueue
用来管理各种请求队列,其中包含有4个队列
a) 所有请求集合,通过requestqueue.add()添加的request都会被添加进来,当请求结束之后删除。
b) 所有等待request,这是volley做的一点优化,想象一下,我们同时发出了三个一模一样的request,此时底层其实不必真正走三个网络请求,而只需要走一个请求即可。所以request1被add之后会被调度执行,而request2 和request3被加进来时,如果request1还未执行完毕,那么request2和 request3只需要等着request1的结果即可。
c) 缓存队列,其中的request需要执行查找缓存的工作
d) 网络工作队列 其中的request需要被执行网络请求的工作

networkdispatcher
执行网络request的线程,它会从网络工作队列中取出一个请求,并执行。volley默认有四个线程作为执行网络请求的线程。

cachedispatcher
执行cache查找的线程,它会从缓存队列中取出一个请求,然后查找该请求的本地缓存。volley只有一个线程执行cache任务。

responsedelivery
请求数据分发器,可以发布request执行的结果。

cache
对cache的封装,主要定义了如何存储,获取缓存,存取依据request中的getcachekey()来描述。

提交请求
volley通过requestqueue.add(request)来往任务队列中增加请求:

从源码分析Android的Volley库的工作流程

一个request被提交之后有几个去处:

1.set<request<?>> mcurrentrequests对应所有请求队列。所有调用add的request必然都会添加到这里面来。
2.priorityblockingqueue<request<?>> mnetworkqueue 对应网络队列。如果一个request不需要缓存,那么add之后会被直接添加到网络队列中。
3.priorityblockingqueue<request<?>> mcachequeue对应缓存请求。如果一个request需要缓存,并且当前的requestqueue中并没有一个request的getcachekey和当前request相同(可以认为一个请求),那么加入缓存队列,让缓存工作线程来处理。
4.map<string, queue<request<?>>> mwaitingrequests对应等待队列。如果requestqueue中已经有一个相同请求在处理,这里只需要将这个request放到等待队列中,等之前的request结果回来之后,进行处理即可。

volley提交任务到队列中是不是很简单?下面来说说优先级请求的事情吧,你可能已经注意到了,上面两个存放需要执行任务的队列都是priorityblockingqueue,前面说了request现实了comparable,看看这个方法:

?
1
2
3
4
5
6
7
8
9
@override
public int compareto(request<t> other) {
 priority left = this.getpriority();
 priority right = other.getpriority();
  //msequence表示请求序列号,add时,会通过一个计数器来指定
  return left == right ?
  this.msequence - other.msequence :
  right.ordinal() - left.ordinal();
}

所以,如果我们的工作线程(networkdispatcher,cachedispatcher)取任务时,自然会从头部开始取。

这里的优先级,仅仅是保证一个请求比另外一个请求先处理,而并不能保证一个高优先级请求一定会比低优先级的请求先回来

缓存工作线程处理

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@override
 public void run() {
 //初始化cache
 mcache.initialize();
 request<?> request;
 while (true) {
   //阻塞 获取一个cache任务
   request = mcachequeue.take();
  try {
   //已经被取消
    if (request.iscanceled()) {
    request.finish("cache-discard-canceled");
    continue;
   }
   //如果拿cache未果,放入网络请求队列
   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;
   }
   //根据cache构造response
   response<?> response = request.parsenetworkresponse(
     new networkresponse(entry.data, entry.responseheaders));
   //是否超过软过期
   if (!entry.refreshneeded()) {
    // 直接返回cache
    mdelivery.postresponse(request, response);
   } else {
    request.setcacheentry(entry);
    //设置中间结果
    response.intermediate = true;
    //发送中间结果
    final request<?> finalrequest = request;
    mdelivery.postresponse(request, response, new runnable() {
     @override
     public void run() {
      try {
       //中间结果完事之后,讲请求放入网络队列
       mnetworkqueue.put(finalrequest);
      } catch (interruptedexception e) {
       // not much we can do about this.
      }
     }
    });
   }
  } catch (exception e) {
 
  }
 }
}

这里可以看到volley确实对缓存封装很到位,各种情况都考虑到了,其中比较重要的两点:

取出来的cache并不仅仅是数据,同时还包括这次请求的一些header
硬过期 软过期
我们可以看到cache中有两个字段来描述缓存过期: cache.ttl vs cache.softttl。什么区别呢?如果ttl过期,那么这个缓存永远不会被使用了;如果softttl没有过期,这个数据直接返回;如果softttl过期,那么这次请求将有两次返回,第一次返回这个cahce,第二次返回网络请求的结果。想想,这个是不是满足我们很多场景呢?先进入页面展示缓存,然后再刷新页面;如果这个缓存太久了,可以等待网络数据回来之后再展示数据,是不是很赞?
networkdispatcher
执行网络请求的工作线程,默认有4个线程,它不停地从网络队列中取任务执行。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public void run() {
 process.setthreadpriority(process.thread_priority_background);
 request<?> request;
 while (true) {
  long starttimems = systemclock.elapsedrealtime();
  // release previous request object to avoid leaking request object when mqueue is drained.
  request = null;
  try {
   request = mqueue.take();
  } catch (interruptedexception e) {
   if (mquit) {
    return;
   }
   continue;
  }
 
  try {
   request.addmarker("network-queue-take");
   //取消
   if (request.iscanceled()) {
    request.finish("network-discard-cancelled");
    continue;
   }
   //通过http栈实现客户端发送网络请求
   networkresponse networkresponse = mnetwork.performrequest(request);
   request.addmarker("network-http-complete");
 
   // 如果缓存软过期,那么会重新走网络;如果server返回304,表示上次之后请求结果数据本地并没有过期,所以可以直接用本地的,因为之前volley已经发过一次response了,所以这里就不需要再发送response结果了。
   if (networkresponse.notmodified && request.hashadresponsedelivered()) {
    request.finish("not-modified");
    continue;
   }
 
   response<?> response = request.parsenetworkresponse(networkresponse);
   request.addmarker("network-parse-complete");
   //更新缓存
   if (request.shouldcache() && response.cacheentry != null) {
    mcache.put(request.getcachekey(), response.cacheentry);
    request.addmarker("network-cache-written");
   }
   //发送结果
   request.markdelivered();
   mdelivery.postresponse(request, response);
  } catch (volleyerror volleyerror) {
   volleyerror.setnetworktimems(systemclock.elapsedrealtime() - starttimems);
   parseanddelivernetworkerror(request, volleyerror);
  } 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
request中主要封装了一个请求的各类http协议信息,比如 url,请求方法,请求的优先级,请求重试的策略,缓存策略等。

这里说一下其中比较有意思的重发策略,如果一次请求发生超时异常,比如sockettimeoutexception  connecttimeoutexception ,我们可以为request配置一个retrypolicy,你可以指定重发这个request的次数,以及每次失败之后重新设置这个请求的超时时间(第一次失败之后,你可以调整第二次请求的超时时间增加,以减少失败的可能性)。

反序列化
request最重要的功能就是提供了内容的反序列化,通过不同的子类来实现不同的序列化功能。比如,如果请求结果是一个json的对象,我们可以使用jsonobjectrequest,如果是一个普通字符,使用stringrequest,同时,我们也可以很方便的定制自己的request,通过复写response<t> parsenetworkresponse(networkresponse response);方法即可。

默认的jsonrequest使用org.json中的json解析,我们使用gson来进行解析能够构造一个更加通用的处理json返回的request:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class jsongrequest<t> extends request<t> {
 
private static gson gson = new gson();
 
private response.listener<t> mlistener;
 
public jsongrequest(string url, response.errorlistener listener,response.listener responselistener) {
 super(url, listener);
 this.mlistener = mlistener;
}
 
public jsongrequest(int method, string url, response.errorlistener listener) {
 super(method, url, listener);
}
 
@override
protected response<t> parsenetworkresponse(networkresponse response) {
 return response.success(gson.fromjson(new inputstreamreader(new bytearrayinputstream(response.data)),gettype()), httpheaderparser.parsecacheheaders(response))
}
 
@override
protected void deliverresponse(t response) {
 if(mlistener != null) {
  mlistener.onresponse(response);
 }
}
 
//获取指定的泛型类型
 protected type gettype() {
 type superclass;
 for(superclass = this.getclass().getgenericsuperclass(); superclass instanceof class && !superclass.equals(jsongrequest.class); superclass = ((class)superclass).getgenericsuperclass()) {
  ;
 }
 
 if(superclass instanceof class) {
  throw new runtimeexception("missing type parameter.");
 } else {
  parameterizedtype parameterized = (parameterizedtype)superclass;
  return parameterized.getactualtypearguments()[0];
 }
}
}

imagerequest
volley专门为图片请求提供了imagerequest,主要是反序列化了一下数据流到bitmap,还可以制定图片的大小,质量等参数。

imageloader是volley提供的一个用来加载图片的工具,它的内部还是使用imagerequest来实现的,主要新加的功能是增加了内存缓存,你可以通过配置imagecache来设置内存缓存。