第3章:常见权限不足症状速查表
权限不足问题在 ExoPlayer 集成中极为隐蔽——播放器可能只抛出一个通用异常,甚至静默失败。本章建立“症状 → 权限映射表”,并解析典型用户反馈,帮助您从现象快速定位缺失权限。
3.1 现象识别:症状与权限对应矩阵
下表汇总了最常见的播放失败症状及其指向的 Android 权限,以及典型日志关键词。当您遇到任一症状时,优先对照此表。
| 症状(用户可见) | 最可能缺失的权限 | 典型日志/异常关键词 | 影响场景 |
|---|---|---|---|
| 点击播放后立即静默停止,无错误提示 | INTERNET |
Network request failed、java.net.UnknownHostException |
URL 流媒体 / Dash / HLS |
| 黑屏但有声音,或画面卡在第一帧 | INTERNET(网络超时)或 READ_EXTERNAL_STORAGE(本地视频) |
DrmSessionException、MediaSourceException |
DRM 内容 / 本地高清文件 |
| 提示“无法打开文件”或“文件不存在” | READ_EXTERNAL_STORAGE(Android 12 及以下)/READ_MEDIA_VIDEO(Android 13+) |
java.io.FileNotFoundException: Permission denied |
本地 MP4 / MKV 文件 |
| 播放器初始化成功,但进度条不动,无缓冲事件 | INTERNET(断网)或 ACCESS_NETWORK_STATE(无法检测网络变化) |
WARN ExoPlayerImpl: No internet connectivity |
在线直播 / 自适应码率 |
| 音频正常播放,视频画面全黑或绿色 | READ_EXTERNAL_STORAGE(存储路径错误)或 INTERNET(视频流损坏) |
MediaCodecException: Codec not found(不常见权限外因素) |
本地文件或加密流 |
| 点击播放按钮后无任何响应,Logcat 无输出 | 权限被系统完全静默拒绝(如 INTERNET 或存储权限) |
E/ActivityThread: Failed to find provider info(少数情况) |
所有流媒体 |
⚠️ 权限作用范围注意
INTERNET 权限在 Android 6.0+ 仍属于普通权限(无需运行时申请),但若被用户或 adb 强制撤销,播放器会静默失败。存储权限在 Android 13 上被拆分为更细粒度的 READ_MEDIA_VIDEO 和 READ_MEDIA_AUDIO,切勿遗漏。
3.2 典型用户反馈解析
将模糊的用户报告转化为权限排查线索,是问题排查的核心能力。以下三条真实反馈经脱敏处理,附解析步骤。
- 反馈 A:“我用 ExoPlayer 播放手机录制的一个 4K 视频,打开 App 后黑屏,但能听到系统提示音。”
-
- 现象定性:有声音无画面 → 视频解码器问题或权限获取失败导致 Surface 未准备。
- 排查方向:确认是否因存储权限被拒,导致 ExoPlayer 无法读取视频文件头信息(仅读取元数据也需要读权限)。
- 实际根因:Android 11 及以上 Scoped Storage 下,App 未声明
READ_EXTERNAL_STORAGE,且未使用 SAF 选择文件。 - 修复:在
AndroidManifest.xml添加READ_EXTERNAL_STORAGE(Android 12 及以下)或READ_MEDIA_VIDEO(Android 13+),并动态请求。
- 反馈 B:“播放 m3u8 直播流时,前 10 秒正常,然后卡住不动,点击暂停/继续无反应。”
-
- 现象定性:初始加载成功,后续断流 → 网络中断或带宽不足;权限问题通常表现为一上来就失败。
- 排查修正:检查接口返回 403 或 SSL 握手失败(需
INTERNET权限),但本案例 Logcat 显示WARN ExoPlayerImpl: No internet connectivity。 - 根因:
ACCESS_NETWORK_STATE权限未申请,导致系统网络监听回调未触发,ExoPlayer 内部网络状态机卡在“待机”模式。 - 修复:在 Manifest 中添加
ACCESS_NETWORK_STATE,并在onStart()中调用ConnectivityManager.registerDefaultNetworkCallback刷新状态。
- 反馈 C:“同一套代码在旧手机(Android 9)正常,新手机(Android 14)直接闪退,无任何错误弹窗。”
-
- 现象定性:版本兼容性崩溃 → 大概率是新版本权限模型变化导致的启动崩溃。
- 排查方向:在
AndroidManifest.xml中寻找是否包含android:maxSdkVersion="31"限制旧权限的声明。若READ_EXTERNAL_STORAGE被限制在maxSdkVersion=32,Android 14 上该权限不被识别,会导致RuntimeException: Missing permission(某些系统版本)。 - 实际根因:App 同时声明了
WRITE_EXTERNAL_STORAGE(已废弃)和READ_EXTERNAL_STORAGE,在 Android 14 上被拒绝访问,播放器初始化时尝试写入临时缓存文件失败。 - 修复:移除
WRITE_EXTERNAL_STORAGE声明(播放器不需要),并针对 Android 13+ 单独声明媒体分组权限。
3.3 故障树:播放失败 → 权限缺失定位
以下故障树可以帮助您从最终用户看到的症状,逐层推理到特定权限问题。图中使用标准颜色标识:🔴 红色节点为终端症状,🟡 黄色为中间判据,🟢 绿色为排查结论。
3.4 数据采集:日志与调试命令
当速查表无法明确匹配时,通过以下命令采集现场证据:
- 采集 App 启动日志(过滤 ExoPlayer 和权限相关):
adb logcat -b main -b events -v time | grep -E "(ExoPlayer|Permission|SecurityException|MediaCodec)" - 检查 Manifest 中声明的权限列表:
adb shell dumpsys package your.package.name | grep permission - 强制触发权限拒绝以复现行为(需 root 或测试设备):
adb shell pm revoke your.package.name android.permission.INTERNET
💡 实战技巧
在
在
ExoPlayer.Builder 设置 setLogTAG("ExoPlayerTag") 自定义 TAG,方便过滤;此外,在 Player.Listener.onPlayerError 回调中打印 error.message 和 error.cause,可直接看到底层异常链。
3.5 修复动作与复测
根据定位结果执行对应修复,然后使用以下脚本快速复测:
// Kotlin 示例:动态请求存储权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ActivityCompat.requestPermissions(activity,
arrayOf(Manifest.permission.READ_MEDIA_VIDEO), REQUEST_CODE)
} else {
ActivityCompat.requestPermissions(activity,
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), REQUEST_CODE)
}
复测步骤:
- 清除 App 数据或卸载重装,重置权限状态。
- 首次启动时拒绝权限请求,确认播放器能优雅降级(显示提示而非崩溃)。
- 授予权限后重新播放,观察是否恢复到正常画面。
- 使用
adb shell dumpsys验证权限已成功授予。
3.6 预防措施清单
- 统一权限管理类:封装
PermissionManager代理所有运行时请求,避免每个 Activity 重复代码。 - Manifest 声明全量覆盖:同时声明旧版和新版权限(使用
maxSdkVersion限制旧权限范围)。 - 预处理检测:在播放前调用
ContextCompat.checkSelfPermission,若未授权则弹出引导对话框,而非直接崩溃。 - 集成自动化测试:使用 Espresso 或 UI Automator 模拟权限授予/拒绝场景,验证播放器状态机。
本章小结:通过症状速查表快速映射权限缺失类型;利用故障树从用户反馈归纳排查路径;始终在 Android 13+ 设备上使用
READ_MEDIA_VIDEO 代替旧存储权限。