热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

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

这篇文章主要介绍了从源码分析Android的Volley应用开发框架的工作流程,文中对Volley的请求处理和缓存部分介绍得比较详细,需要的朋友可以参考下

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)来往任务队列中增加请求:

2016219154911119.png (411×631)

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

1.Set> mCurrentRequests对应所有请求队列。所有调用add的Request必然都会添加到这里面来。
2.PriorityBlockingQueue> mNetworkQueue 对应网络队列。如果一个Request不需要缓存,那么add之后会被直接添加到网络队列中。
3.PriorityBlockingQueue> mCacheQueue对应缓存请求。如果一个Request需要缓存,并且当前的RequestQueue中并没有一个Request的getCacheKey和当前Request相同(可以认为一个请求),那么加入缓存队列,让缓存工作线程来处理。
4.Map>> mWaitingRequests对应等待队列。如果RequestQueue中已经有一个相同请求在处理,这里只需要将这个Request放到等待队列中,等之前的Request结果回来之后,进行处理即可。

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

@Override
public int compareTo(Request other) {
 Priority left = this.getPriority();
 Priority right = other.getPriority();
  //mSequence表示请求序列号,add时,会通过一个计数器来指定
  return left == right ?
  this.mSequence - other.mSequence :
  right.ordinal() - left.ordinal();
}

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

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

缓存工作线程处理

 @Override
 public void run() {
 //初始化Cache
 mCache.initialize();
 Request<&#63;> 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<&#63;> 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<&#63;> 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个线程,它不停地从网络队列中取任务执行。

public void run() {
 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
 Request<&#63;> 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<&#63;> 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 parseNetworkResponse(NetworkResponse response);方法即可。

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

 public class JsonGRequest extends Request {

private static Gson gson = new Gson();

private Response.Listener 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 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来设置内存缓存。


推荐阅读
  • 使用正则表达式爬取36Kr网站首页新闻的操作步骤和代码示例
    本文介绍了使用正则表达式来爬取36Kr网站首页所有新闻的操作步骤和代码示例。通过访问网站、查找关键词、编写代码等步骤,可以获取到网站首页的新闻数据。代码示例使用Python编写,并使用正则表达式来提取所需的数据。详细的操作步骤和代码示例可以参考本文内容。 ... [详细]
  • Android JSON基础,音视频开发进阶指南目录
    Array里面的对象数据是有序的,json字符串最外层是方括号的,方括号:[]解析jsonArray代码try{json字符串最外层是 ... [详细]
  • 如何查询zone下的表的信息
    本文介绍了如何通过TcaplusDB知识库查询zone下的表的信息。包括请求地址、GET请求参数说明、返回参数说明等内容。通过curl方法发起请求,并提供了请求示例。 ... [详细]
  • 在Android中解析Gson解析json数据是很方便快捷的,可以直接将json数据解析成java对象或者集合。使用Gson解析json成对象时,默认将json里对应字段的值解析到java对象里对应字段的属性里面。然而,当我们自己定义的java对象里的属性名与json里的字段名不一样时,我们可以使用@SerializedName注解来将对象里的属性跟json里字段对应值匹配起来。本文介绍了使用@SerializedName注解解析json数据的方法,并给出了具体的使用示例。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • jmeter实践:从csv中获取带引号的数据详情的技巧和运行全部数据的方法
    本文分享了jmeter实践中从csv中获取带引号的数据的解决办法,包括设置CSV Data Set Config和运行脚本获取数据的方法。另外还介绍了循环运行csv中全部数据的解决方法,避免每次修改csv用例都需要修改脚本的麻烦。通过了解和掌握工具的细节点,可以更好地解决问题和提高技术水平。 ... [详细]
  • 单点登录原理及实现方案详解
    本文详细介绍了单点登录的原理及实现方案,其中包括共享Session的方式,以及基于Redis的Session共享方案。同时,还分享了作者在应用环境中所遇到的问题和经验,希望对读者有所帮助。 ... [详细]
  • 本文讨论了Kotlin中扩展函数的一些惯用用法以及其合理性。作者认为在某些情况下,定义扩展函数没有意义,但官方的编码约定支持这种方式。文章还介绍了在类之外定义扩展函数的具体用法,并讨论了避免使用扩展函数的边缘情况。作者提出了对于扩展函数的合理性的质疑,并给出了自己的反驳。最后,文章强调了在编写Kotlin代码时可以自由地使用扩展函数的重要性。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • 本文介绍了前端人员必须知道的三个问题,即前端都做哪些事、前端都需要哪些技术,以及前端的发展阶段。初级阶段包括HTML、CSS、JavaScript和jQuery的基础知识。进阶阶段涵盖了面向对象编程、响应式设计、Ajax、HTML5等新兴技术。高级阶段包括架构基础、模块化开发、预编译和前沿规范等内容。此外,还介绍了一些后端服务,如Node.js。 ... [详细]
  • React项目中运用React技巧解决实际问题的总结
    本文总结了在React项目中如何运用React技巧解决一些实际问题,包括取消请求和页面卸载的关联,利用useEffect和AbortController等技术实现请求的取消。文章中的代码是简化后的例子,但思想是相通的。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • SpringMVC接收请求参数的方式总结
    本文总结了在SpringMVC开发中处理控制器参数的各种方式,包括处理使用@RequestParam注解的参数、MultipartFile类型参数和Simple类型参数的RequestParamMethodArgumentResolver,处理@RequestBody注解的参数的RequestResponseBodyMethodProcessor,以及PathVariableMapMethodArgumentResol等子类。 ... [详细]
  • uniapp开发H5解决跨域问题的两种代理方法
    本文介绍了uniapp开发H5解决跨域问题的两种代理方法,分别是在manifest.json文件和vue.config.js文件中设置代理。通过设置代理根域名和配置路径别名,可以实现H5页面的跨域访问。同时还介绍了如何开启内网穿透,让外网的人可以访问到本地调试的H5页面。 ... [详细]
author-avatar
一米静心的阳光
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有