【源码】"拆" 网络请求库-Volley(下)

简介: Volley跟AsyncTask一样,都是老古董了,最早发布于2013年的Google I/O大会,初衷就是:让Android开发者少写重复的请求代码。

0x4、缓存设计


首先,缓存适用于那些不会更新服务端数据的请求,所以一般是缓存GET请求,很少对POST请求进行缓存。


其次,缓存是非必要的,但加了会带来两个好处:


  • 客户端/浏览器 → 减少网络延迟,加快页面打开速度;
  • 后台 → 减少带宽消耗,降低服务器压力;


在扒Volley缓存的实现细节前,我们试试自己来设计一个缓存,从一个简陋的方案开始:


  • 键值对集合存储,URL做Key,缓存(响应结果)为Value;
  • 执行请求前,先到集合里根据URL查,查到直接返回缓存,查不到执行请求,请求结束后存起来;


问题来了:后台的资源是会变的,上一秒是这个,下一秒是那个,此时客户端还用本地缓存,结果可能是不对的!


  • 后台配合,给资源加上有效期,可以是 直接过期时间,也可以是 资源生产时间 + 有效时长,客户端缓存的时候绑定上这个;
  • 客户端请求时,如果命中缓存,校验缓存是否在有效期内,在直接返回缓存,没有则执行请求;


问题又来了:后台资源变化,客户端是无感知的,以为缓存还在有效期内,不会去请求新数据。


  • 后台变化,主动通知客户端显然不太行 (硬要主动推送让客户端刷新也可以);
  • 客户端还是得主动去请求下后台;
  • 后台为资源添加上 标记,第一次请求时丢给客户端,客户端下次请求相关链接时丢给后台,后台判断TAG和自己算的是否相等,不等说明资源发生改变,给予客户端不同的反馈。比如:没发生改变,只返回一个304的状态码,发生改变,返回 200的状态码 + 新的响应,客户端根据状态码,决定是读缓存,还是解析新响应。这样还有个好处:后台的资源有可能一直没变,虽然过了有效期,但是没必要刷新,这样客户端也不需要更新缓存。


2333,上面的流程其实就是 保证HTTP缓存一致性 的部分思路,上面说到的 附加信息 都放在 信息报头 中:


网络异常,图片无法展示
|


HTTP缓存由多种规则,根据是否需要重新向服务器发起请求来分类,分为两大类:


  • 强制缓存 → 缓存数据未失效,直接使用缓存数据,用响应头中的 Expires/Cache-Control 来标明失效规则;
  • 对比缓存 → 第一次请求时后台将缓存标识一起返回给客户端,客户端再次请求数据时,将备份的缓存标识发送给服务器,服务器根据标识进行判断,返回304代表客户端可以使用缓存数据,其他则说明不能使用缓存数据。然后这个缓存标识一般分两种:资源修改时间 → Last-Modified/If-Modified-Since后台生成的资源标识 → Etag/If-None-Match后者优先级高于前者


两类缓存可以混合使用!具体的HTTP缓存策略流程图如下:


网络异常,图片无法展示
|


笔者对此也只知道个大概,感兴趣想深入了解的可见:《浏览器 HTTP 协议缓存机制详解 》


Volley中也是这样的缓存策略:


网络异常,图片无法展示
|


接着看看缓存的定义 Cache 接口,包含了一个实体缓存实体 Entity 和缓存相关的操作方法。


网络异常,图片无法展示
|


跟到具体实现类 DiskBasedCache,关注缓存增删,先看看 put()


网络异常,图片无法展示
|


网络异常,图片无法展示
|


简单说下流程:将消息报头和响应数据依次写入文件,然后存一个消息报头到缓存集合中,请求时判定缓存是否可用时用到。接着看看 get()


网络异常,图片无法展示
|


很好理解,缓存命中后,响应实体从流 → 字节数组 → Entity实例返回。其它情况返回Null,表示没有缓存可用。其它方法先不看了,接着看看这个 CacheKey 是怎么设计的?


跟下哪里调的put()方法 → NetworkDispatcher → processRequest()


网络异常,图片无法展示
|


跟下 Request → getCacheKey()


网络异常,图片无法展示
|


所以Key就是两种:


  • GET || DEPRECATED_GET_OR_POST → 直接url
  • 其它请求方式数值-url


直接URL做Key,真·简单粗暴啊!


0x5、请求处理细节


回到Volley的入口方法 newRequestQueue(),啧啧,扩展性体现之一:


网络异常,图片无法展示
|


HurlStack 对应HttpUrlConnection的封装,HttpClientStack 对应HttpClient的封装,还可以自行定制,继承 BaseHttpStack 类,重写 executeRequest() 返回处理后的响应即可。


另外,这里用 代理模式 套了一层,代理类是 BasicNetwork,在 HttpClientStack 的基础上做一些附加操作。


网络异常,图片无法展示
|


网络异常,图片无法展示
|


所以,这个附加操作就是:获取缓存相关的请求头,对响应结果/异常的处理。然后,看到异常重试都是调用的 attemptRetryOnException()


网络异常,图片无法展示
|


跟下 RetryPolicy 发现是一个接口:


网络异常,图片无法展示
|


看下哪里实现了这个接口,跟到默认实现类 DefaultRetryPolicy,可以看到默认最大重试次数为1:


网络异常,图片无法展示
|


调用一次 retry() 计数加1,如果<=最大重试次数,抛出异常


网络异常,图片无法展示
|


可以看到这里只是做了一个计数,并没有进行执行请求,或者请求入队的操作,那请求是怎么重新发起的?


网络异常,图片无法展示
|


注意这里的死循环,以及 重试方法的层级,是在catch里的,如果此时超过重试次数,抛出异常,就能退出这个死循环。


这样不会崩溃吗?当然不会,因为在调用此方法的 NetworkDispatcher 中套了一层try-catch兜底:


网络异常,图片无法展示
|


当真是妙啊!!!


最后还有一个点:Volley使用 内存缓存 来存放请求获得的数据,而不是直接在内存中开辟一个区域存放。


这是为啥?因为网络请求一般会很频繁,不停创建byte[],会引起频繁的GC,间接对APP性能造成影响。所以Volley定义了 ByteArrayPool 来缓存数据。


网络异常,图片无法展示
|


看着有点懵是吧,我简单说说:


  • 规定了缓存池的默认大小为:DEFAULT_POOL_SIZE = 4096字节 = 4KB,池中维护两个列表,一个按照 最近使用顺序 排序(表①),一个按照 byte[]大小 排序(表②);
  • 从缓存区取空间:不直接开辟空间,先循环迭代查找列表②中是否有 len>=所需字节 的已开辟空间,有的话返回此空间,没有的话开辟新的空间返回;
  • 将空间返还给缓存区:检查插入数据是否超出边界,有的话直接返回,没有的话添加到表①尾部,然后二分查找找到表②中合适的位置插入,当前开辟字节数增加。同时判断是否超过最大值,是回收表②的第一个元素;


0x6、可扩展性


Tips:其实看下都有哪些接口和抽象类,就知道有啥可以自定义的~


RequestQueue构造方法传入


  • Cache缓存,默认是将响应数据放磁盘中,你可以弄成二三级缓存,实现接口;
  • Network请求框架封装,默认封了HttpUrlConnection和HttpClient,弄成其他请求库也可以;
  • ResponseDelivery响应交付,就是请求响应的后续处理,正常与异常情况的处理;


Request构造方法传入


  • RetryPolicy → 请求失败重试策略;


其它


  • Request请求,toolbox里有常用的实现类,如:JsonRequest、ImageRequest、StringRequest等;
  • Authenticator令牌授权


大概就这些吧,toolbox里还送了一个图片加载的工具,不过性能不算特别好,就不展开讲了。


0x7、小结


本节对请求库Volley的设计进行了多方面的拆解,获益良多,至少现在让我封装一个请求库,不会无从入手了。


源码难啃,但弄懂了设计的原理,就会觉得豁然开朗,妙啊,也顺应了前老大说的,品经典项目源码,如品经典名著般,沁人心脾~


参考文献:



相关文章
|
5月前
|
C++
基于Reactor模型的高性能网络库之地址篇
这段代码定义了一个 InetAddress 类,是 C++ 网络编程中用于封装 IPv4 地址和端口的常见做法。该类的主要作用是方便地表示和操作一个网络地址(IP + 端口)
300 58
|
5月前
|
网络协议 算法 Java
基于Reactor模型的高性能网络库之Tcpserver组件-上层调度器
TcpServer 是一个用于管理 TCP 连接的类,包含成员变量如事件循环(EventLoop)、连接池(ConnectionMap)和回调函数等。其主要功能包括监听新连接、设置线程池、启动服务器及处理连接事件。通过 Acceptor 接收新连接,并使用轮询算法将连接分配给子事件循环(subloop)进行读写操作。调用链从 start() 开始,经由线程池启动和 Acceptor 监听,最终由 TcpConnection 管理具体连接的事件处理。
200 2
|
5月前
基于Reactor模型的高性能网络库之Tcpconnection组件
TcpConnection 由 subLoop 管理 connfd,负责处理具体连接。它封装了连接套接字,通过 Channel 监听可读、可写、关闭、错误等
169 1
|
5月前
|
负载均衡 算法 安全
基于Reactor模式的高性能网络库之线程池组件设计篇
EventLoopThreadPool 是 Reactor 模式中实现“一个主线程 + 多个工作线程”的关键组件,用于高效管理多个 EventLoop 并在多核 CPU 上分担高并发 I/O 压力。通过封装 Thread 类和 EventLoopThread,实现线程创建、管理和事件循环的调度,形成线程池结构。每个 EventLoopThread 管理一个子线程与对应的 EventLoop(subloop),主线程(base loop)通过负载均衡算法将任务派发至各 subloop,从而提升系统性能与并发处理能力。
291 3
|
5月前
基于Reactor模式的高性能网络库github地址
https://github.com/zyi30/reactor-net.git
136 0
|
5月前
基于Reactor模型的高性能网络库之Poller(EpollPoller)组件
封装底层 I/O 多路复用机制(如 epoll)的抽象类 Poller,提供统一接口支持多种实现。Poller 是一个抽象基类,定义了 Channel 管理、事件收集等核心功能,并与 EventLoop 绑定。其子类 EPollPoller 实现了基于 epoll 的具体操作,包括事件等待、Channel 更新和删除等。通过工厂方法可创建默认的 Poller 实例,实现多态调用。
310 60
|
5月前
|
安全 调度
基于Reactor模型的高性能网络库之核心调度器:EventLoop组件
它负责:监听事件(如 I/O 可读写、定时器)、分发事件、执行回调、管理事件源 Channel 等。
299 57
|
5月前
基于Reactor模型的高性能网络库之时间篇
是一个用于表示时间戳(精确到微秒)**的简单封装类
207 57
|
5月前
|
缓存 索引
基于Reactor模式的高性能网络库之缓冲区Buffer组件
Buffer 类用于处理 Socket I/O 缓存,负责数据读取、写入及内存管理。通过预分配空间和索引优化,减少内存拷贝与系统调用,提高网络通信效率,适用于 Reactor 模型中的异步非阻塞 IO 处理。
199 3
|
4月前
|
机器学习/深度学习 算法 数据库
基于GoogleNet深度学习网络和GEI步态能量提取的步态识别算法matlab仿真,数据库采用CASIA库
本项目基于GoogleNet深度学习网络与GEI步态能量图提取技术,实现高精度步态识别。采用CASI库训练模型,结合Inception模块多尺度特征提取与GEI图像能量整合,提升识别稳定性与准确率,适用于智能安防、身份验证等领域。

热门文章

最新文章