Android中使用CacheDataSource.Factory预缓存视频列表的高效实现方案
实现ExoPlayer视频列表预缓存(后台线程版)
核心思路
预缓存本质是把视频文件的部分或全部数据提前下载到ExoPlayer的SimpleCache中,不需要启动播放。用WorkManager做后台任务管理是最优选择——它能自动处理进程被杀、设备重启后的任务恢复,还能基于网络、充电状态调度任务,比普通Service更稳定可靠。
具体实现步骤
1. 封装预缓存工具类
先写一个通用的缓存任务类,负责单个视频的预缓存逻辑:
public class PreCacheTask { private final SimpleCache simpleCache; private final Context context; public PreCacheTask(SimpleCache simpleCache, Context context) { this.simpleCache = simpleCache; this.context = context; } // 预缓存指定URI的视频,可设置最大缓存字节数(比如缓存前10分钟内容) public void preCacheVideo(String videoUri, long maxBytesToCache) throws IOException { DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(context, new DefaultHttpDataSource.Factory()); CacheDataSource.Factory cacheDataSourceFactory = new CacheDataSource.Factory() .setCache(simpleCache) .setUpstreamDataSourceFactory(dataSourceFactory) .setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR); // 创建MediaSource但不关联播放器 ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(cacheDataSourceFactory) .createMediaSource(MediaItem.fromUri(videoUri)); // 用ExoPlayer内置的CacheUtil主动缓存数据 CacheUtil.cache( mediaSource, new DefaultCacheKeyFactory(), simpleCache, maxBytesToCache, CacheUtil.DEFAULT_BUFFER_SIZE, (requestLength, cachedLength, newBytesCached) -> { // 监听缓存进度,可用于更新WorkManager任务进度 float progress = (float) cachedLength / requestLength; // 这里可以处理进度回调,比如记录日志或通知UI }, null // 取消信号,用于中断任务 ); } }
2. 用WorkManager创建后台缓存任务
创建WorkRequest调度批量缓存任务,支持多视频处理:
public class PreCacheWorker extends CoroutineWorker { public static final String KEY_VIDEO_URIS = "video_uris"; public static final String KEY_MAX_CACHE_BYTES = "max_cache_bytes"; public PreCacheWorker(@NonNull Context context, @NonNull WorkerParameters params) { super(context, params); } @NonNull @Override public Result doWork() { List<String> videoUris = getInputData().getStringArray(KEY_VIDEO_URIS) != null ? Arrays.asList(getInputData().getStringArray(KEY_VIDEO_URIS)) : Collections.emptyList(); // 默认每个视频缓存50MB,可根据需求调整 long maxCacheBytes = getInputData().getLong(KEY_MAX_CACHE_BYTES, 1024 * 1024 * 50); PreCacheTask preCacheTask = new PreCacheTask(SimpleMediaPlayer.simpleCache, getApplicationContext()); for (String uri : videoUris) { try { // 先判断是否已缓存足够数据,避免重复下载 CacheKey cacheKey = new DefaultCacheKeyFactory().buildCacheKey(MediaItem.fromUri(uri)); long cachedBytes = CacheUtil.getCachedBytes(simpleCache, cacheKey, 0, maxCacheBytes); if (cachedBytes >= maxCacheBytes) { continue; } preCacheTask.preCacheVideo(uri, maxCacheBytes - cachedBytes); } catch (IOException e) { // 单个视频缓存失败,记录日志后继续处理下一个 Log.e("PreCacheWorker", "Cache failed for " + uri, e); } } return Result.success(); } // 启动缓存任务的静态方法 public static void startPreCache(Context context, List<String> videoUris, long maxCacheBytes) { Data inputData = new Data.Builder() .putStringArray(KEY_VIDEO_URIS, videoUris.toArray(new String[0])) .putLong(KEY_MAX_CACHE_BYTES, maxCacheBytes) .build(); OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(PreCacheWorker.class) .setInputData(inputData) .setConstraints(new Constraints.Builder() .setRequiredNetworkType(NetworkType.UNMETERED) // 可选:仅WiFi下缓存 .setRequiresDeviceIdle(false) // 可选:是否设备空闲时执行 .build()) .build(); WorkManager.getInstance(context).enqueue(workRequest); } }
3. 触发预缓存任务
在需要预缓存的场景(比如进入视频列表页时)调用:
List<String> videoList = Arrays.asList( "https://xxxxxx.s3.us-north-2.amazonaws.com/video1.mp4", "https://xxxxxx.s3.us-north-2.amazonaws.com/video2.mp4" ); // 每个视频缓存50MB PreCacheWorker.startPreCache(this, videoList, 1024 * 1024 * 50);
关键优化点
- 缓存大小控制:不要缓存整个视频,根据视频时长设置合理的缓存量(比如前5-10分钟),避免占用过多存储空间。
- 重复缓存判断:用
CacheUtil.getCachedBytes检查已缓存数据,避免重复下载。 - 任务调度策略:通过WorkManager的Constraints设置仅在WiFi、充电时缓存,减少用户流量消耗和设备负载。
- 进度监听:可通过WorkManager的
getWorkInfoByIdLiveData监听任务整体进度,给用户展示缓存状态。
注意事项
- 确保
SimpleCache是全局单例,避免多个实例导致缓存数据混乱。 - 需申请
INTERNET权限,Android 10+需注意后台网络访问限制,WorkManager的Constraints会自动适配系统规则。
内容的提问来源于stack exchange,提问作者ThinQ
相关产品推荐
相关产品推荐

