Android ExoPlayer 缓存策略深度解析:如何优化媒体流加载效率
·
背景痛点
在移动端音视频开发中,ExoPlayer 的默认 SimpleCache 存在几个明显短板:
- 固定缓存大小:默认分配固定存储空间,可能导致低端设备内存不足或高端设备资源浪费
- 无智能预加载:每次播放重新下载内容,相同视频反复消耗流量(实测重复播放消耗 100% 原始流量)
- 冷启动延迟:尤其在 HLS/DASH 流媒体场景下,分片加载等待明显

技术方案
1. 缓存数据源改造
内置 CacheDataSource 的局限性:
- 仅支持单一缓存目录
- 无法自定义缓存淘汰策略
- 预加载逻辑简单粗暴
推荐组合方案:
val cache = SimpleCache(
File(context.cacheDir, "media_cache"),
LeastRecentlyUsedCacheEvictor(MAX_CACHE_SIZE),
null // 可注入自定义数据库实现
)
2. LRU 缓存淘汰策略
LeastRecentlyUsedCacheEvictor 工作原理:
- 维护访问时间戳链表
- 当缓存空间不足时
- 优先删除最久未使用的缓存块

3. 分片预加载机制
针对 HLS/DASH 的优化技巧:
val dataSourceFactory = CacheDataSource.Factory().apply {
setCache(cache)
setUpstreamDataSourceFactory(HttpDataSource.Factory())
setFlags(CacheDataSource.FLAG_BLOCK_ON_CACHE) // 强制优先读缓存
}
// 预加载后续3个分片
player.setForegroundMode(true)
player.prepare(mediaSource, false, true)
代码实现
完整配置示例:
// 初始化缓存(200MB上限)
val cacheSize = 200L * 1024 * 1024
val cacheEvictor = LeastRecentlyUsedCacheEvictor(cacheSize)
val cache = SimpleCache(cacheDir, cacheEvictor)
// 构建数据源链
val upstreamFactory = OkHttpDataSource.Factory()
.setUserAgent("MyMediaApp")
.setConnectTimeoutMs(10_000)
val cacheFactory = CacheDataSource.Factory()
.setCache(cache)
.setUpstreamDataSourceFactory(upstreamFactory)
.setCacheWriteDataSinkFactory(null) // 边播边存
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)
// 统计命中率
val hitCounter = AtomicInteger()
val missCounter = AtomicInteger()
cache.addListener(object : Cache.Listener {
override fun onSpanAdded(cache: Cache, span: CacheSpan) {
missCounter.incrementAndGet()
}
override fun onSpanTouched(cache: Cache, oldSpan: CacheSpan, newSpan: CacheSpan) {
hitCounter.incrementAndGet()
}
})
性能考量
缓存大小建议
| 设备类型 | 推荐缓存大小 | 内存占用 | |------------|--------------|----------| | 低端手机 | 50-100MB | <30MB | | 中端设备 | 100-200MB | 50MB左右 | | 旗舰机型 | 200-500MB | 可控 |
存储介质选择
- 内存缓存:适合频繁访问的短视频(实现方式:
CachedContentIndex+MemoryCache) - SSD缓存:长视频首选,注意并发写入控制
避坑指南
-
权限问题:
Android 11+ 需改用<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />Context#getExternalFilesDir() -
多实例竞争:
- 全局单例 Cache 实例
-
使用
Cache#startReadWriteNonBlocking() -
加密流处理:
setCacheKeyFactory { dataSpec -> if (dataSpec.uri.path?.contains("/encrypted/") == true) { "${dataSpec.uri}_${DRM_SESSION_ID}" } else dataSpec.key }
开放性思考
结合 CDN 的分级缓存策略可考虑:
- 本地缓存热门内容(LRU策略)
- 边缘节点缓存近期内容(TTL策略)
- 源站处理长尾请求
如何设计智能调度算法?欢迎在评论区分享你的方案!
更多推荐


所有评论(0)