前言

AndroidVideoCache 是用来帮助实现视频音频边缓存边播放的开源库,最近有个视频播放的功能,为了避免视频数据重复加载,就使用了这个库,感觉很棒,而且这个库的实现思路非常值得学习研究,就花点时间学习下它的源码。

原理

实现原理可以很简单的描述下:创建一个本地代理服务器,获取数据的请求不直接服务服务器而是访问代理服务器,代理服务器先判断请求是否有缓存,已经有缓存了的话就直接返回缓存数据,没有缓存的话代理服务器就服务资源服务器把资源缓存到本地再将缓存数据返回。

代码鉴赏

AndroidVideoCache使用很简单,创建一个代理服务器并返回一个代理地址,直接使用这个地址即可:

HttpProxyCacheServer proxy = AppApplication.getProxy(activity);
String proxyUrl = proxy.getProxyUrl(url);
videoView.setPath(proxyUrl);private HttpProxyCacheServer proxy;
public static HttpProxyCacheServer getProxy(Context context) {return proxy;}private HttpProxyCacheServer newProxy() {return new HttpProxyCacheServer.Builder(this).cacheDirectory(cacheDirFile).maxCacheSize((1024+512)* 1024 * 1024 ).build();}

可以看到HttpProxyCacheServer使用的是建造者模式进行创建。既然是创建了一个代理服务器进行处理客户端发起的请求,那它是怎么创建代理服务器的,还有它是怎么监听并处理请求的?这个是我们必须要搞明白的。

首先我们先看看它是如何创建一个代理服务器的,注释很清楚了:

private HttpProxyCacheServer(Config config) {try {//获取本机的地址InetAddress inetAddress = InetAddress.getByName(PROXY_HOST);//创建一个ServerSocket去监听本地客户端发起的请求(使用localAddress参数来将ServerSocket绑定到PROXY_HOST,就是本机ip)this.serverSocket = new ServerSocket(0, 8, inetAddress);this.port = serverSocket.getLocalPort();//IgnoreHostProxySelector是继承ProxySelector的,它的作用是过滤一些需要忽略的请求//IgnoreHostProxySelector实际只用来pingIgnoreHostProxySelector.install(PROXY_HOST, port);CountDownLatch startSignal = new CountDownLatch(1);//WaitRequestsRunnable是一直循环等待请求进来this.waitConnectionThread = new Thread(new WaitRequestsRunnable(startSignal));this.waitConnectionThread.start();startSignal.await(); //freeze thread, wait for server starts//这个Pinger是用来ping处理请求的本地代理服务器判断是否可用,注意不是器远程//获取资源的代理服务器,是上面的PROXY_HOST serverSocket服务器this.pinger = new Pinger(PROXY_HOST, port);LOG.info("Proxy cache server started. Is it alive? " + isAlive());} catch (IOException | InterruptedException e) {..}}

从上面,可以看得到:创建了ServerSocket也就是那个代理服务器来监听客户端的请求。 接下来我们看它是如何监听发起的请求还有是怎么处理请求的

从上面知道,WaitRequestsRunnable是用来监听请求的,它执行了一个方法,在循环等待请求进来:

private void waitForRequest() {while (!Thread.currentThread().isInterrupted()) {Socket socket = serverSocket.accept();socketProcessor.submit(new SocketProcessorRunnable(socket));}}private final class SocketProcessorRunnable implements Runnable {...@Overridepublic void run() {processSocket(socket);}}private void processSocket(Socket socket) {try {//客户端发来的数据其实就是请求数据,读取出来封装成GetRequestGetRequest request = GetRequest.read(socket.getInputStream());LOG.debug("Request to cache proxy:" + request);String url = ProxyCacheUtils.decode(request.uri);if (pinger.isPingRequest(url)) {pinger.responseToPing(socket);} else {//代理缓存服务器Client处理这个请求,clients会缓存起来//如果没有将创建一个HttpProxyCacheServerClientsHttpProxyCacheServerClients clients = getClients(url);clients.processRequest(request, socket);}} catch (SocketException e) {...}

可以看到了processSocket进行了处理请求的,但是看起来有点怪, GetRequest.read(socket.getInputStream())怎么读出来是一个请求?其实这也是很合理了,客户端发起的就是get请求来获取数据,所以socketServer服务器收到客户端发来的数据,其实就是请求数据,没有其他了,把这个数据读取出来,封装成GetRequest请求。

public static GetRequest read(InputStream inputStream) throws IOException {BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));StringBuilder stringRequest = new StringBuilder();String line;while (!TextUtils.isEmpty(line = reader.readLine())) { // until new line (headers ending)stringRequest.append(line).append('\n');}return new GetRequest(stringRequest.toString());}

把请求读取出来后最终使用了HttpProxyCacheServerClients的processRequest去处理这个请求。

public void processRequest(GetRequest request, Socket socket)  {//开始处理请求,startProcessRequest方法这里为了创建HttpProxyCachestartProcessRequest();try {clientsCount.incrementAndGet();//使用HttpProxyCache处理请求proxyCache.processRequest(request, socket);} finally {finishProcessRequest();}}private HttpProxyCache newHttpProxyCache() throws ProxyCacheException {//可以看到,实质上还是通过HttpUrlSource去获取资源数据HttpUrlSource source = new HttpUrlSource(url, config.sourceInfoStorage, config.headerInjector);//缓存文件路径FileCache cache = new FileCache(config.generateCacheFile(url), config.diskUsage);HttpProxyCache httpProxyCache = new HttpProxyCache(source, cache);httpProxyCache.registerCacheListener(uiCacheListener);return httpProxyCache;}

可以看到HttpProxyCache才是最终处理请求的类,它获取资源数据以及判断资源文件大小实质上还是通过HttpUrlSource。
HttpProxyCache的processRequest如下:

public void processRequest(GetRequest request, Socket socket) throws IOException, ProxyCacheException {//先响应一个请求头OutputStream out = new BufferedOutputStream(socket.getOutputStream());String responseHeaders = newResponseHeaders(request);out.write(responseHeaders.getBytes("UTF-8"));long offset = request.rangeOffset;//判断是否需要使用缓存,//判断是否使用缓存满足条件:// 1.远程文件大小可以缓存// 2.不是特殊的请求(比如不是get请求)// 3.请求文件跳过的长度rangeOffset不能超过本地缓存文件的大小if (isUseCache(request)) {//如果是使用缓存的话,调用responseWithCache处理responseWithCache(out, offset);} else {//不使用缓存的话,直接使用HttpUrlSource读取文件并将读取回来的文件写出responseWithoutCache(out, offset);}}private void responseWithoutCache(OutputStream out, long offset) throws ProxyCacheException, IOException {HttpUrlSource newSourceNoCache = new HttpUrlSource(this.source);try {newSourceNoCache.open((int) offset);byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];int readBytes;while ((readBytes = newSourceNoCache.read(buffer)) != -1) {out.write(buffer, 0, readBytes);offset += readBytes;}out.flush();} finally {newSourceNoCache.close();}}

上面的流程其实到这里已经很清楚了,还有个问题是,加载回来的数据是怎么缓存起来的,是何如读取的呢?接下来我们看responseWithCache就明白了。

private void responseWithCache(OutputStream out, long offset) throws ProxyCacheException, IOException {byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];int readBytes;while ((readBytes = read(buffer, offset, buffer.length)) != -1) {out.write(buffer, 0, readBytes);offset += readBytes;}out.flush();}

下面只要看read方法,就会恍然大悟了:

public int read(byte[] buffer, long offset, int length) throws ProxyCacheException {ProxyCacheUtils.assertBuffer(buffer, offset, length);//加载请求要求的数据长度回来,while (!cache.isCompleted() && cache.available() < (offset + length) && !stopped) {//异步加载数据回来并进行缓存readSourceAsync();//等待一秒时间来等待数据加载回来并缓存到本地waitForSourceData();//检测数据长度是否异常checkReadSourceErrorsCount();}//获取数据并缓存完成后,将缓存数据发送出去//还记得创建HttpProxyCache时传入的那个FileCache吗,//这个cache就是那个FileCache,如果缓存完成后//percentsAvailable变为100int read = cache.read(buffer, offset, length);if (cache.isCompleted() && percentsAvailable != 100) {percentsAvailable = 100;onCachePercentsAvailableChanged(100);}return read;}

整个流程已经分析完成了,还是比较容易明白的,作者代码写的很好,很容易理解,下面总结下画个简单的
转载注明:https://mp.csdn.net/mdeditor/83277930#
流程图:

AndroidVideoCache源码赏析相关推荐

  1. commons-math3-3.6.1-org.apache.commons.math3.analysis.function-包下的类(三)-中英对照文档及源码赏析

    commons-math3-3.6.1-org.apache.commons.math3.analysis.function-包下的类(三)-中英对照文档及源码赏析 摘要:中英对照文档.源码赏析.or ...

  2. commons-math3-3.6.1-org.apache.commons.math3.analysis.function-包下的类(二)-中英对照文档及源码赏析

    commons-math3-3.6.1-org.apache.commons.math3.analysis.function-包下的类(二)-中英对照文档及源码赏析 摘要:中英对照文档.源码赏析.or ...

  3. OpenJDK源码赏析之二:java虚拟机启动流程到首函数调用全流程

    承接上一谈 OpenJDK源码赏析之一:漫谈java的历史渊源_星空_AZ的博客-CSDN博客 JAVA从启动到第一个函数执行的发生的流程: WinMain->JLI_Launch->JV ...

  4. OpenJDK源码赏析之三:Java命令参数的读取处理流程

    承接上一篇 OpenJDK源码赏析之二:java虚拟机启动流程到首函数调用全流程_星空_AZ的博客-CSDN博客 这篇这要解析Java虚拟机创建时候配置读取时候命令行参数的读取过程,这次采取逆向思维分 ...

  5. OpenJDK源码赏析之四(jli_util中的工具函数)

    上一篇: OpenJDK源码赏析之三:Java命令参数的读取_星空_MAX的博客-CSDN博客 不承接上一篇,这篇单独开始分析jli_util.h(java工具函数)里的一些函数 JLI_MemAll ...

  6. android视频缓存框架 [AndroidVideoCache](https://github.com/danikula/AndroidVideoCache) 源码解析与评估

    文章目录 android视频缓存框架 [AndroidVideoCache](https://github.com/danikula/AndroidVideoCache) 源码解析与评估 引言 使用方 ...

  7. commons-math3-3.6.1-org.apache.commons.math3.analysis.integration-包下的类-中英对照文档及源码赏析

    commons-math3-3.6.1-org.apache.commons.math3.analysis.integration-包下的类-中英对照文档及源码赏析 摘要:中英对照文档.源码赏析.or ...

  8. commons-math3-3.6.1-org.apache.commons.math3.analysis.differentiation-包下的接口-中英对照文档及源码赏析

    commons-math3-3.6.1-org.apache.commons.math3.analysis.differentiation-包下的接口-中英对照文档及源码赏析 摘要:中英对照文档.源码 ...

  9. commons-math3-3.6.1-org.apache.commons.math3.analysis.integration.gauss-包下的类-中英对照文档及源码赏析

    commons-math3-3.6.1-org.apache.commons.math3.analysis.integration.gauss-包下的类-中英对照文档及源码赏析 摘要:中英对照文档.源 ...

最新文章

  1. IT从花钱到赚钱——惠普IT转型记
  2. python发声-python写报警程序中的声音实现winsound
  3. C语言位、字节、半字、字的概念和内存位宽
  4. ABP入门系列(8)——Json格式化
  5. linux然后防止ip欺骗,linux – 如何在iptables中防止ip欺骗?
  6. ORACLE账号注册之后,要修改密码才能用
  7. Android开机速度优化(第三篇)
  8. learn words by steps 8 英语单词
  9. weblogic安装配置教程
  10. Python多行注释/取消注释快捷键
  11. 无聊的时候氵一些小套路
  12. 吞食天地2蜀汉英雄传1.5版图文攻略
  13. 制作网站及论坛的过程
  14. 山东理工acm 3926 bLue的二叉树
  15. 新势力新名片-上海度普新能源通过ASPICE CL2评估!
  16. 后台数据不清理android,android 后台被数据清理后切换到前台数据丢失问题
  17. 通过canvas画出爱心图案,表达你的爱意!
  18. 弹簧振子运动方程推导
  19. Google File System中文版
  20. chrome无法从该网站添加应用、扩展程序和用户脚本

热门文章

  1. 掌上题库V1.2.2全开源版本小程序带后端
  2. 1030 Travel Plan (30 分)
  3. 免费(无辜)ARP与代理ARP
  4. 以前听不懂的歌词如今都成了共鸣
  5. Microsoft Excel 出现错误。很抱歉,您的Office安装无法正常工作,请使用控制面板中的“程序与功能”选项修复您的产品。您也可以联机查找更多帮助。
  6. 【读码JDK】Java synthetic的介绍
  7. qq2009破解流程[图]
  8. 【无标题】上课了上课了
  9. 学python处理数据结构_从零开始学Python - 第009课:常用数据结构之字符串
  10. 安装软件时出现不能打开要写入的文件怎么解决?