Android后台视频缓存实现咨询:频道视频未播放预缓存方案
Hey there! 针对你在开发Android视频应用时遇到的「选中频道后批量缓存未播放视频」的需求,我整理了几个实用的实现方案和关键细节,帮你搞定这个功能:
核心思路梳理
首先明确我们要完成的核心流程:
- 拿到目标频道下所有视频的源地址
- 批量发起缓存请求,将视频资源写入本地缓存目录
- 实时管理缓存状态(进度、成功/失败、空间占用)
- 后续播放时优先读取本地缓存,无缓存再请求网络
具体实现方案
方案一:基于ExoPlayer的缓存扩展(推荐)
如果你的应用已经在用ExoPlayer播放视频,那这个方案最省心——ExoPlayer自带的缓存机制默认只处理已播放内容,但我们可以用它提供的CacheWriter类直接缓存未播放的视频,无需实际播放。
步骤说明
- 初始化全局缓存实例:先配置好缓存目录、大小限制和淘汰策略(比如用最近最少使用算法清理旧缓存)
- 单视频缓存逻辑:针对每个视频地址,创建
CacheWriter并执行缓存任务 - 批量缓存频道视频:遍历频道下的所有视频地址,逐个发起缓存请求
代码示例(Kotlin)
// 全局单例的SimpleCache(建议放在Application里初始化) private fun initVideoCache(context: Context): SimpleCache { val cacheDir = context.getExternalFilesDir(Environment.DIRECTORY_MOVIES) ?: context.filesDir val maxCacheSize = 100 * 1024 * 1024 // 100MB缓存上限 val cacheEvictor = LeastRecentlyUsedCacheEvictor(maxCacheSize.toLong()) return SimpleCache(cacheDir, cacheEvictor) } // 缓存单个视频的方法 fun cacheSingleVideo(cache: SimpleCache, videoUrl: String) { val httpDataSourceFactory = DefaultHttpDataSource.Factory() val cacheDataSourceFactory = CacheDataSource.Factory() .setCache(cache) .setUpstreamDataSourceFactory(httpDataSourceFactory) .setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR) val mediaItem = MediaItem.fromUri(videoUrl) val cacheWriter = CacheWriter( cacheDataSourceFactory.createDataSource(), mediaItem.localConfiguration!!.uri, null, object : CacheWriter.ProgressListener { override fun onProgress( requestLength: Long, bytesCached: Long, newBytesCached: Long ) { // 实时更新缓存进度,可回调到UI层展示 val progress = (bytesCached * 100 / requestLength).toInt() Log.d("VideoCache", "缓存进度:$progress%") } } ) // 开启后台线程执行缓存,避免阻塞主线程 Executors.newSingleThreadExecutor().execute { try { cacheWriter.cache() Log.d("VideoCache", "视频缓存完成:$videoUrl") } catch (e: Exception) { Log.e("VideoCache", "缓存失败:${e.message}") } } } // 批量缓存频道下的所有视频 fun cacheChannelVideos(cache: SimpleCache, videoUrls: List<String>) { videoUrls.forEach { url -> cacheSingleVideo(cache, url) } }
关键注意点
- 用WorkManager替代普通线程:如果需要应用退到后台仍能继续缓存,或者在WiFi/充电状态下自动执行,WorkManager是更好的选择,它能适配Android的后台限制
- 网络状态判断:缓存前先检查网络类型,默认只在WiFi下执行(可让用户手动开启移动流量缓存)
- 缓存状态持久化:用Room数据库记录每个视频的缓存状态(未开始/缓存中/已完成/失败),方便UI展示
方案二:自定义下载管理器(灵活度更高)
如果不想依赖ExoPlayer,也可以自己实现下载逻辑,用OkHttp或Retrofit发起请求,将视频流写入本地文件。
核心要点
- 支持断点续传:通过
Range请求头实现,避免网络中断后重新下载整个文件 - 任务队列管理:控制同时下载的任务数量,避免网络拥堵
- 文件命名规则:用视频ID作为文件名,避免重复或冲突
代码示例(OkHttp实现断点续传)
fun downloadVideo(videoUrl: String, saveDir: File, videoId: String) { val saveFile = File(saveDir, "$videoId.mp4") val downloadedBytes = saveFile.length() val client = OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .build() val request = Request.Builder() .url(videoUrl) .addHeader("Range", "bytes=$downloadedBytes-") // 断点续传请求头 .build() client.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { Log.e("VideoDownload", "下载失败:${e.message}") } override fun onResponse(call: Call, response: Response) { if (!response.isSuccessful) { Log.e("VideoDownload", "服务器错误:${response.code}") return } val body = response.body ?: return // 以追加模式写入文件 val outputStream = RandomAccessFile(saveFile, "rw").apply { seek(downloadedBytes) }.outputStream() try { body.byteStream().use { inputStream -> inputStream.copyTo(outputStream) } Log.d("VideoDownload", "下载完成:$videoId") } catch (e: IOException) { Log.e("VideoDownload", "写入文件失败:${e.message}") } } }) }
优化与细节处理
- 缓存清理策略:提供手动清理缓存的功能,或者自动清理超过7天未播放的缓存文件;也可以根据剩余存储空间自动调整缓存上限
- 权限适配:Android 10+推荐使用应用私有目录存储缓存(无需额外权限),如果要存到公共目录,需要申请
MANAGE_EXTERNAL_STORAGE权限 - 缓存有效性检测:定期对比视频源的ETag或最后修改时间,若资源更新则重新缓存
- 播放优先级:播放视频时,先检查本地缓存文件是否存在,存在则直接播放本地文件,否则请求网络
内容的提问来源于stack exchange,提问作者Hitesh Matnani
相关产品推荐
相关产品推荐

