ExoPlayer的缓存 – 三 Cache的使用

文章目录

  • CacheDataSource 读取数据
    • 创建 CacheDataSource
    • TeeDataSource 写入缓存数据
    • CacheDataSource#open
    • openNextSource 中选择合适的DataSource
    • CacheDataSink 保存数据
      • CacheDataSink 两个属性
      • CacheDataSink 写数据
      • 分片文件的 命名规则
  • 缓存 SimpleCache
    • startFile
    • commitFile
    • addSpan
    • startReadWriteNonBlocking
    • cache 中的数据库
      • SimpleCache 中CacheFileMetadataIndex:
      • SimpleCache 中CachedContentIndex:
      • DownloadManager 中的DefaultDownloadIndex
      • 整体上的数据库设计

CacheDataSource 读取数据

创建 CacheDataSource

和 exoplayer 的其他 DataSource 一样,CacheDataSource 的生成也是通过 Factory 模式生成,

  private CacheDataSource createDataSourceInternal(@Nullable DataSource upstreamDataSource, @Flags int flags, int upstreamPriority) {Cache cache = checkNotNull(this.cache);@Nullable DataSink cacheWriteDataSink;if (cacheIsReadOnly || upstreamDataSource == null) {cacheWriteDataSink = null;} else if (cacheWriteDataSinkFactory != null) {cacheWriteDataSink = cacheWriteDataSinkFactory.createDataSink();} else {cacheWriteDataSink = new CacheDataSink.Factory().setCache(cache).createDataSink();}return new CacheDataSource(cache,upstreamDataSource,cacheReadDataSourceFactory.createDataSource(),cacheWriteDataSink,cacheKeyFactory,flags,upstreamPriorityTaskManager,upstreamPriority,eventListener);}
}

CacheDataSource 构造函数的参数和读写有关的有

  1. Cache cache 使用的缓存

  2. @Nullable DataSource upstreamDataSource 根据url 生成的 DataSource, 可为null, 只能从Cache 中读取数据

  3. cacheReadDataSource 从cache 中读取数据用的 DataSource

  4. @Nullable DataSink cacheWriteDataSink 写缓存用的DataSink,可为null, 不可向Cache 中写数据

  5. @Nullable CacheKeyFactory cacheKeyFactory 用来生成 Cache ID, 用来匹配Cache

private CacheDataSource(Cache cache,@Nullable DataSource upstreamDataSource,DataSource cacheReadDataSource,@Nullable DataSink cacheWriteDataSink,@Nullable CacheKeyFactory cacheKeyFactory,......
) {......this.upstreamDataSource = upstreamDataSource;this.cacheWriteDataSource =cacheWriteDataSink != null? new TeeDataSource(upstreamDataSource, cacheWriteDataSink): null;
}

在 CacheDataSource 的构造函数中 通过 upstreamDataSourcecacheWriteDataSink 生成cacheWriteDataSource 类型为TeeDataSource。 缓存的保存正是通过TeeDataSource 完成。

TeeDataSource 写入缓存数据

在TeeDataSource 的read 函数中,先从upstream 中 读取数据,然后写入到dataSink 中。

public int read(byte[] buffer, int offset, int length) throws IOException {if (bytesRemaining == 0) {return C.RESULT_END_OF_INPUT;}int bytesRead = upstream.read(buffer, offset, length);if (bytesRead > 0) {// TODO: Consider continuing even if writes to the sink fail.dataSink.write(buffer, offset, bytesRead);if (bytesRemaining != C.LENGTH_UNSET) {bytesRemaining -= bytesRead;}}return bytesRead;
}

CacheDataSource#open

在open 函数中,先根据cacheKeyFactory 生成的key 构建DataSpec, 然后调用openNextSource 函数

public long open(DataSpec dataSpec) throws IOException {try {String key = cacheKeyFactory.buildCacheKey(dataSpec);DataSpec requestDataSpec = dataSpec.buildUpon().setKey(key).build();this.requestDataSpec = requestDataSpec;if (bytesRemaining > 0 || bytesRemaining == C.LENGTH_UNSET) {openNextSource(requestDataSpec, false);}return dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : bytesRemaining;}
}

openNextSource 中选择合适的DataSource

在openNextSource 中,首先通过

  1. cache.startReadWriteNonBlocking 获取cache 中的CacheSpan 的nextSpan,
  2. 如果nextSpan 为null, Cache 中无缓存数据但是数据被锁定,直接走 upstreamDataSource
  3. 如果nextSpan 不为空而且已经缓冲完成,直接使用缓冲的nextSpan, 使用cacheReadDataSource
  4. 如果nextSpan 不为空而且还在缓冲中,当前的实际位置开始读数据 , 使用cacheWriteDataSource
private void openNextSource(DataSpec requestDataSpec, boolean checkCache) throws IOException {@Nullable CacheSpan nextSpan;String key = castNonNull(requestDataSpec.key);if (currentRequestIgnoresCache) {...} else if (blockOnCache) {...} else {nextSpan = cache.startReadWriteNonBlocking(key, readPosition, bytesRemaining);}DataSpec nextDataSpec;DataSource nextDataSource;if (nextSpan == null) {// The data is locked in the cache, or we're ignoring the cache. Bypass the cache and read// from upstream.nextDataSource = upstreamDataSource;nextDataSpec =requestDataSpec.buildUpon().setPosition(readPosition).setLength(bytesRemaining).build();} else if (nextSpan.isCached) {// Data is cached in a span file starting at nextSpan.position.Uri fileUri = Uri.fromFile(castNonNull(nextSpan.file));nextDataSpec =requestDataSpec.buildUpon().setUri(fileUri).setUriPositionOffset(filePositionOffset).setPosition(positionInFile).setLength(length).build();nextDataSource = cacheReadDataSource;} else {// Data is not cached, and data is not locked, read from upstream with cache backing.nextDataSpec =requestDataSpec.buildUpon().setPosition(readPosition).setLength(length).build();if (cacheWriteDataSource != null) {nextDataSource = cacheWriteDataSource;} else {}}long resolvedLength = nextDataSource.open(nextDataSpec);
}

CacheDataSink 保存数据

在createDataSourceInternal 函数中通过

cacheWriteDataSink = new CacheDataSink.Factory().setCache(cache).createDataSink();

CacheDataSink 两个属性

  1. fragmentSize 分片大小 数据可以分片保存,对应Cache中的CacheSpan
  2. bufferSize 保存时使用的buffer 大小
  /** Default {@code fragmentSize} recommended for caching use cases. */public static final long DEFAULT_FRAGMENT_SIZE = 5 * 1024 * 1024;/** Default buffer size in bytes. */public static final int DEFAULT_BUFFER_SIZE = 20 * 1024;public CacheDataSink(Cache cache, long fragmentSize, int bufferSize) {

CacheDataSink 写数据

首先检查当前文件的长度,如果长度等于分片大小,先关闭当前流,然后打开下一个分片流的

public void write(byte[] buffer, int offset, int length) throws CacheDataSinkException {@Nullable DataSpec dataSpec = this.dataSpec;try {int bytesWritten = 0;while (bytesWritten < length) {if (outputStreamBytesWritten == dataSpecFragmentSize) {closeCurrentOutputStream();openNextOutputStream(dataSpec);}int bytesToWrite =(int) min(length - bytesWritten, dataSpecFragmentSize - outputStreamBytesWritten);castNonNull(outputStream).write(buffer, offset + bytesWritten, bytesToWrite);bytesWritten += bytesToWrite;outputStreamBytesWritten += bytesToWrite;dataSpecBytesWritten += bytesToWrite;}}
}

分片文件的 命名规则

在 openNextOutputStream 中调用cache.startFile 生成新的文件。

private void openNextOutputStream(DataSpec dataSpec) throws IOException {file =cache.startFile(castNonNull(dataSpec.key), dataSpec.position + dataSpecBytesWritten, length);FileOutputStream underlyingFileOutputStream = new FileOutputStream(file);......
}
#SimpleCache
@Override
public synchronized File startFile(String key, long position, long length) throws CacheException {CachedContent cachedContent = contentIndex.get(key);File cacheSubDir = new File(cacheDir, Integer.toString(random.nextInt(SUBDIRECTORY_COUNT)));long lastTouchTimestamp = System.currentTimeMillis();return SimpleCacheSpan.getCacheFile(cacheSubDir, cachedContent.id, position, lastTouchTimestamp);
}
# SimpleCacheSpan
public static File getCacheFile(File cacheDir, int id, long position, long timestamp){return new File(cacheDir, id + "." + position + "." + timestamp + SUFFIX);
}

数据库中保存的文件

缓存 SimpleCache

public SimpleCache(File cacheDir, CacheEvictor evictor, DatabaseProvider databaseProvider)
  1. File cacheDir 缓存目录
  2. CacheEvictor evictor 缓存存储策略 Exoplayer 提供了两种 LeastRecentlyUsedCacheEvictor NoOpCacheEvictor
  3. DatabaseProvider databaseProvider 数据库

startFile

public synchronized File startFile(String key, long position, long length) throws CacheException

参考 分片文件的 命名规则, 用于在当前缓存目录生成一个新的文件

commitFile

在文件下载完成后,用于向Cache 的CacheFileMetadataIndex fileIndex数据库提交信息。

首先生成一个SimpleCacheSpan,

public synchronized void commitFile(File file, long length) throws CacheException {if (fileIndex != null) {String fileName = file.getName();try {fileIndex.set(fileName, span.length, span.lastTouchTimestamp);} catch (IOException e) {}}addSpan(span)
}

addSpan

Add Splan 在内存中保存当前的缓存信息

同一个文件的缓存保存在一个 CachedContent, CachedContent 中的keyToContent 保存所有的span 文件。

// Adds a cached span to the in-memory representation.
// Params:
// span – The span to be added.
private void addSpan(SimpleCacheSpan span) {contentIndex.getOrAdd(span.key).addSpan(span);totalSpace += span.length;notifySpanAdded(span);
}public CachedContent getOrAdd(String key) {CachedContent cachedContent = keyToContent.get(key);
}

startReadWriteNonBlocking

public synchronized CacheSpan startReadWriteNonBlocking(String key, long position) throws CacheException

从数据库中或者内存中查找 CacheSpan 文件

public synchronized CacheSpan startReadWriteNonBlocking(String key, long position)throws CacheException {SimpleCacheSpan span = getSpan(key, position);if (span.isCached) {// Read case.return touchSpan(key, span);}CachedContent cachedContent = contentIndex.getOrAdd(key);if (!cachedContent.isLocked()) {// Write case.cachedContent.setLocked(true);return span;}// Lock not available.return null;
}

cache 中的数据库

SimpleCache 中CacheFileMetadataIndex:

SimpleCache 中CachedContentIndex:

DownloadManager 中的DefaultDownloadIndex

DownloadManager 中用于记录下载的表

整体上的数据库设计

在Exoplayer 的默认实现中,使用了一个数据库,数据库中建了三张表。

ExoPlayer的缓存 三 SimpleCache的使用相关推荐

  1. Exoplayer的缓存 一 使用简介

    Exoplayer 的 缓存-- 一 使用简介 文章目录 创建下载服务 创建下载管理器 添加下载 删除下载 开始和停止下载 设置和清除下载停止原因 暂停和恢复所有下载 设置下载进度要求 设置最大并行下 ...

  2. PHP数组缓存:三种方式JSON、序列化和var_export的比较

    使用PHP的站点系统,在面对大数据量的时候不得不引入缓存机制.有一种简单有效的办法是将PHP的对象缓存到文件里.下面我来对这3种缓存方法进行说明和比较. 第一种方法:JSON JSON缓存变量的方式主 ...

  3. Exoplayer的缓存 二 下载服务DownloadService

    Exoplayer的缓存 – 二 下载服务 DownloadService 文章目录 DownloadService send 指令 onStartCommand 解析Intent DownloadM ...

  4. hibernate二级缓存(三) 自定义实现一个简单的hibernate二级缓存

    hibernate二级缓存(三) 自定义实现一个简单的hibernate二级缓存 前面我们已经提及过hibernate内部为二级缓存的扩展做了很多的实现.我们只需要实现RegionFactoryTem ...

  5. 微信小程序入门篇4---新闻网 本地缓存 三个页面

    首先,这个小程序较前三个难度加大 项目目录 首页 index.js */var common = require('../../utils/common.js')Page({data: {swiper ...

  6. android缓存框架SimpleCache

    1.它可以缓存什么东西? 普通的字符串.JsonObject.JsonArray.Bitmap.Drawable.序列化的java对象,和 byte数据. 2.它有什么特色? 特色主要是: 1:轻,轻 ...

  7. redis分布式缓存(三)

  8. 彻底弄懂 HTTP 缓存机制 —— 基于缓存策略三要素分解法

    导语 HTTP 缓存机制作为 Web 性能优化的重要手段,对从事 Web 开发的小伙伴们来说是必须要掌握的知识,但最近我遇到了几个缓存头设置相关的题目,发现有好几道题答错了,有的甚至在知道了正确答案后 ...

  9. 【腾讯Bugly干货分享】彻底弄懂 Http 缓存机制 - 基于缓存策略三要素分解法

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/qOMO0LIdA47j3RjhbCWUEQ 作者:李 ...

最新文章

  1. 详解 TCP 和 UDP
  2. python win32模块详解_python模块:win32com用法详解
  3. 算法(9)--两个数的最大公约数
  4. 向内存中连续存入数据_内存节省到极致!Redis中这个数据结构,值得每个程序员了解...
  5. [Vue.js] Vuex的使用
  6. 体验VMware Converter Client 6.2与Veeam BR 10迁移ESXi 6.0 vm到vCenter 6.7 u3
  7. SpringCloud工作笔记081---SpringCloud Hystrix Turbine(断路器聚合监控)的基本使用
  8. ulipad双击无反应
  9. VirtualBox安装Windows XP图文教程
  10. LOAP引擎:clickhouse06:简单介绍几个JSON函数
  11. halcon之屌炸天的自标定
  12. 2018你那计算机考试新题型,2018年421多省公务员考试判断推理新题型、新趋势
  13. 3. mysql-视图
  14. java jcifs ntlm_Java 使用NTLM身份验证使用soap服务
  15. 项目管理中的项目干系人
  16. 图神经网络 基础与前沿,神经系统知识网络图
  17. 快速排序——寻找数组第K大数(由浅入深,四种方法对比讲解!)
  18. 华为鸿蒙2.0系统电脑安装步骤,华为鸿蒙系统2.0怎么安装,鸿蒙系统2.0安装教程...
  19. 阿里云免费教你使用营销引擎
  20. 外汇平台正规排行榜 Flyerinternational稳居前三

热门文章

  1. google firefor 历史版本下载谷歌火狐浏览器版本下载大全
  2. MMPBSA结合自由能计算原理
  3. 给自己八年前的“网红面经”写个续篇
  4. 在Linux上安装TimesTen
  5. 第1个Qt项目:计算器
  6. 自行车租借管理系统c语言,校园自行车管理系统.doc
  7. Python pip install 安装包报错ERROR: Could not find a version that satisfies the requirement XXX解决方法
  8. python大神代码_求python大神写一个windows可运行的代码,学习学习。
  9. 非洲瓜哇JAVA布的特点_非洲人的服装及其着装特点
  10. jsonEditor API介绍 JSON编辑器