ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ExoPlayer database 模块解析:媒体组件共享 SQLite 数据库基础设施

ExoPlayer database 模块解析:媒体组件共享 SQLite 数据库基础设施 音视频移动开发【免费下载链接】ExoPlayerThis project is deprecated and stale. The latest ExoPlayer code is available in https://github.com/androidx/media项目地址https://gitcode.com/gh_mirrors/ex/ExoPlayer点击查看免费下载导读本文围绕 ExoPlayer 仓库中library/database模块展开系统讲解它为离线下载DownloadManager、缓存索引SimpleCache等媒体功能提供的 SQLite 数据库抽象DatabaseProvider接口如何统一读写入口、StandaloneDatabaseProvider与DefaultDatabaseProvider两种接入方式有何区别、VersionTable如何让各功能组件独立管理自身表结构版本。读完本文你将掌握在自有应用中为 ExoPlayer 提供数据库、以及理解其缓存/下载索引落库机制的完整方案。说明本仓库中的com.google.android.exoplayer2包已标记为Deprecated官方后续代码迁移至 androidx.media3但本模块的接口设计与使用模式在 media3 中得以延续仍具参考价值。模块定位供媒体模块内部使用的数据库层library/database模块的官方定位在其 README 中写得非常克制Provides database functionality for use by other media modules. Application code will not normally need to depend on this module directly.即它为其他媒体模块提供数据库能力普通应用代码通常不需要直接依赖本模块。这一点与仓库结构完全吻合——该模块只包含 6 个源文件全部位于包com.google.android.exoplayer2.database下DatabaseProvider.java核心接口StandaloneDatabaseProvider.java独立数据库实现DefaultDatabaseProvider.java包装应用自有SQLiteOpenHelper的实现ExoDatabaseProvider.java已被废弃的旧实现DatabaseIOException.java异常封装VersionTable.java功能组件版本管理工具package-info.java包级注解声明NonNullApi可以看到这是一个小而专的基础设施模块真正的消费方是library/core离线下载与library/datasource缓存等上层模块。DatabaseProvider媒体组件的数据库读写契约一切围绕 DatabaseProvider.java 这个接口展开。它定义了媒体库组件获取SQLiteDatabase实例的统一方式public interface DatabaseProvider { /** 媒体库组件可读写表名的统一前缀 */ String TABLE_PREFIX ExoPlayer; /** 创建并打开用于读写的数据库成功后会被缓存可反复调用 */ SQLiteDatabase getWritableDatabase() throws SQLiteException; /** 创建并打开数据库与 getWritableDatabase 通常返回同一对象 * 仅在磁盘已满等问题导致只能以只读方式打开时返回只读实例 */ SQLiteDatabase getReadableDatabase() throws SQLiteException; }两个关键设计点表名前缀约定TABLE_PREFIX ExoPlayer所有媒体库组件创建的表都必须以ExoPlayer开头避免与宿主应用自己的表发生命名冲突。例如离线下载索引表为ExoPlayerDownloads见下文缓存内容索引表为ExoPlayerCacheIndex。从源码结构看这是 media 组件与应用数据隔离的命名空间约定。读写实例的语义差异getWritableDatabase()失败权限不足、磁盘已满时抛SQLiteExceptiongetReadableDatabase()在极端情况下可退化为只读实例但一旦问题修复、getWritableDatabase()成功旧的只读实例会被关闭并替换。文档注释明确说明数据库打开成功后被缓存因此可以每次写入时都调用无需自行持有单例。两种接入方式独立数据库 vs 复用应用数据库仓库提供了两种DatabaseProvider实现对应两类接入场景。StandaloneDatabaseProvider应用无自有数据库时使用StandaloneDatabaseProvider.java 继承自SQLiteOpenHelper自己管理一个独立的 SQLite 文件public class StandaloneDatabaseProvider extends SQLiteOpenHelper implements DatabaseProvider { /** 独立数据库文件名 */ public static final String DATABASE_NAME exoplayer_internal.db; private static final int VERSION 1; public StandaloneDatabaseProvider(Context context) { super(context.getApplicationContext(), DATABASE_NAME, /* factory */ null, VERSION); } }关键事实数据库文件名为exoplayer_internal.db通过Context.getDatabasePath(DATABASE_NAME)定位默认位于应用的内部存储数据库目录构造时传入context.getApplicationContext()避免持有 Activity 等短生命周期上下文造成泄漏onCreate与onUpgrade均为空实现——因为功能组件自己建表、自己升级// Features create their own tables./// Features handle their own upgrades.数据库整体版本恒为 1各模块的表结构演进完全由VersionTable机制承担见后文onDowngrade时会执行wipeDatabase(db)遍历sqlite_master对所有非sqlite_sequence的实体执行DROP type IF EXISTS name即整体清库重建。注释也提示若库中含外键约束清库可能不完整。类上注释明确了其适用对象适合没有自己数据库、或希望媒体库表与自身数据隔离在独立库中的应用其他应用应优先用DefaultDatabaseProvider包装自己的SQLiteOpenHelper。DefaultDatabaseProvider复用应用自有数据库DefaultDatabaseProvider.java 是一个轻量适配器把DatabaseProvider的两个方法直接委托给应用传入的SQLiteOpenHelperpublic final class DefaultDatabaseProvider implements DatabaseProvider { private final SQLiteOpenHelper sqliteOpenHelper; public DefaultDatabaseProvider(SQLiteOpenHelper sqliteOpenHelper) { this.sqliteOpenHelper sqliteOpenHelper; } Override public SQLiteDatabase getWritableDatabase() { return sqliteOpenHelper.getWritableDatabase(); } Override public SQLiteDatabase getReadableDatabase() { return sqliteOpenHelper.getReadableDatabase(); } }适合应用已经拥有自己的数据库的场景让 ExoPlayer 的媒体表与应用业务表共存于同一库中通过ExoPlayer前缀隔离。注意它不负责创建/升级表——应用自己的SQLiteOpenHelper需要保证数据库本身可打开媒体组件表仍由各组件自行创建。ExoDatabaseProvider已被废弃的旧入口ExoDatabaseProvider.java 如今只是一个空壳子类Deprecated public final class ExoDatabaseProvider extends StandaloneDatabaseProvider { public ExoDatabaseProvider(Context context) { super(context); } }其 Javadoc 明确写着deprecated Use {link StandaloneDatabaseProvider}新代码应直接使用父类。DatabaseIOExceptionSQL 异常的 IO 化包装媒体库的下载/缓存读写常常运行在 I/O 线程与异步框架中。DatabaseIOException.java 将SQLException包装为IOExceptionpublic final class DatabaseIOException extends IOException { public DatabaseIOException(SQLException cause) { super(cause); } public DatabaseIOException(SQLException cause, String message) { super(message, cause); } }这样上层以IOException为统一错误模型的组件如DownloadManager的下载流程无需分别处理 SQL 异常。VersionTable功能组件的独立版本管理这是本模块最具设计价值的工具类。VersionTable.java 解决的核心问题是媒体库各功能组件下载、缓存等需要独立于宿主数据库整体版本进行表结构演进。它维护一张版本表表名为DatabaseProvider.TABLE_PREFIX Versions即ExoPlayerVersions建表 SQL 如下源码原文CREATE TABLE IF NOT EXISTS ExoPlayerVersions ( feature INTEGER NOT NULL, instance_uid TEXT NOT NULL, version INTEGER NOT NULL, PRIMARY KEY (feature, instance_uid) )主键为(feature, instance_uid)即功能类型 实例标识唯一确定一条版本记录。内置功能类型常量常量值用途FEATURE_OFFLINE0离线下载功能相关表FEATURE_CACHE_CONTENT_METADATA1缓存内容元数据表FEATURE_CACHE_FILE_METADATA2缓存文件元数据表FEATURE_EXTERNAL1000外部功能供第三方扩展使用VERSION_UNSET-1getVersion在未设置版本时返回其中IntDef注解Feature约束了合法取值用于静态检查。三个核心静态方法// 设置某功能某实例的版本表不存在则先建表然后 replaceOrThrow 写入 setVersion(SQLiteDatabase writableDatabase, Feature int feature, String instanceUid, int version) throws DatabaseIOException; // 删除某功能某实例的版本记录表不存在时静默返回 removeVersion(SQLiteDatabase writableDatabase, Feature int feature, String instanceUid) throws DatabaseIOException; // 查询版本无记录或表不存在时返回 VERSION_UNSET(-1) getVersion(SQLiteDatabase database, Feature int feature, String instanceUid) throws DatabaseIOException;所有 SQL 执行异常统一包装为DatabaseIOException抛出getVersion与removeVersion都会先用Util.tableExists判断表是否存在避免首次运行时的空表查询。此外该类的静态初始化块调用了ExoPlayerLibraryInfo.registerModule(goog.exo.database)为库的版本信息登记模块名。实际消费方缓存与离线下载如何落地结合仓库源码可以清晰看到DatabaseProvider的真实调用链。缓存索引CachedContentIndexlibrary/datasource 是缓存模块的核心索引。它接受DatabaseProvider构造内部DatabaseStorage使用表名private static final String TABLE_PREFIX DatabaseProvider.TABLE_PREFIX CacheIndex; // ExoPlayerCacheIndex即缓存内容索引以ExoPlayerCacheIndex表存在数据库中CachedContentIndex.delete(DatabaseProvider, long uid)等静态方法也直接以DatabaseProvider为参数供清理时删除对应索引记录。下载索引DefaultDownloadIndexlibrary/core 是DownloadManager的默认下载索引实现其表名前缀同样取自接口常量private static final String TABLE_PREFIX DatabaseProvider.TABLE_PREFIX Downloads; // ExoPlayerDownloads结合源码与测试如 VersionTableTest、DefaultDownloadIndexTest可以看出离线下载与会话索引的表结构升级正是通过VersionTable的FEATURE_OFFLINE等键值记录各自版本实现表结构独立演进。典型接入方式由于应用通常不需要直接依赖library/database模块实际接入只需在初始化下载/缓存组件时传入数据库实例即可例如// 方案一应用无自有数据库使用独立库 DatabaseProvider databaseProvider new StandaloneDatabaseProvider(context); // 方案二应用已有 SQLiteOpenHelper复用同一库 DatabaseProvider databaseProvider new DefaultDatabaseProvider(yourOwnSQLiteOpenHelper); // 交由 DownloadManager / SimpleCache 等组件使用 DownloadManager downloadManager new DownloadManager( context, new DefaultDataSource.Factory(context), /* cache */ simpleCache, databaseProvider, /* executor */ ...);总结与迁移提示library/database是 ExoPlayer 架构中小而关键的基础设施模块通过DatabaseProvider接口 ExoPlayer表名前缀让下载、缓存等媒体组件可以在不感知宿主数据布局的前提下安全落库StandaloneDatabaseProvider独立exoplayer_internal.db与DefaultDatabaseProvider复用应用库覆盖两种典型集成场景VersionTable以(feature, instance_uid)为键的版本表实现了媒体组件相对宿主数据库的独立演进是模块化表结构管理的范本。最后需要提醒本仓库中的com.google.android.exoplayer2.database全部类均已标记Deprecated官方建议迁移至 androidx.media3media3-migration.sh 脚本可辅助迁移。在迁移前理解本模块的接口与版本管理设计将帮助你平稳过渡到 media3 的对应能力。赞分享音视频移动开发【免费下载链接】ExoPlayerThis project is deprecated and stale. The latest ExoPlayer code is available in https://github.com/androidx/media项目地址https://gitcode.com/gh_mirrors/ex/ExoPlayer点击查看免费下载相关推荐MDPDF实战案例如何用3行命令将技术文档转换为精美PDFMDPDF实战案例如何用3行命令将技术文档转换为精美PDF 想要快速将Markdown技术文档转换为专业PDF格式吗MDPDF是一款强大的命令行工具能够轻CLI开发工具文档Mac 菜单栏图标被刘海吞掉Ice 菜单管理 3 步救回来Mac 菜单栏图标被刘海吞掉Ice 菜单管理 3 步救回来 MacBook 顶部图标一多电池和时间就被挤到刘海后面只剩半截露脸。Ice 是一款免费的 ma桌面应用Databend Common分布式数据库公共基础设施模块全景解析Databend Common分布式数据库公共基础设施模块全景解析 导读 Databend 是一个基于 Rust 构建的 Data Agent Ready W数据库数据分析向量数据库全文检索云原生AI 应用上一篇ansi项目架构解析理解这个纯Bash工具的设计哲学下一篇终极Zsh语法高亮指南解决10个最常见问题的完整方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进