现象
TV 端播放页在播放约 5 分钟后进程崩溃,logcat 只有这一行关键信息:
E AndroidRuntime: java.lang.ArrayIndexOutOfBoundsException: length=1; index=1
需拿到更多的上下文日志
第一步:拿到完整崩溃栈
设备通过 adb 连接,直接读 crash 缓冲区,不用在海量日志里翻:
adb logcat -d -b crash
得到:
FATAL EXCEPTION: ExoPlayer:Playback
java.lang.ArrayIndexOutOfBoundsException: length=1; index=1
at androidx.media3.exoplayer.trackselection.BaseTrackSelection.isTrackExcluded(BaseTrackSelection.java:197)
at androidx.media3.exoplayer.hls.HlsChunkSource.createFallbackOptions(HlsChunkSource.java:954)
at androidx.media3.exoplayer.hls.HlsChunkSource.createFallbackOptions(HlsChunkSource.java:975)
at androidx.media3.exoplayer.hls.HlsSampleStreamWrapper.onLoadError(HlsSampleStreamWrapper.java:1064)
at androidx.media3.exoplayer.upstream.Loader$LoadTask.handleMessage(Loader.java:530)
...
线索一:这个栈里没有一行应用代码
- 崩溃线程是
ExoPlayer:Playback,ExoPlayer 自己的播放线程。 - 栈从上到下全是
androidx.media3,应用类一个都没出现。说明异常发生在库内部,不是我们在某处错误地访问了数组。
线索二:触发点是“加载出错后的降级处理”
栈里的 onLoadError → createFallbackOptions 表示:HLS 的某个分片或播放列表加载失败,ExoPlayer 正在计算“能不能降级、怎么降级”。
再看崩溃前的日志,分片一直在正常加载(seg-21 到 seg-52,每个约 8 秒),说明不是一开始就播不了,而是播放中途某次加载出错,才进入这条出错处理路径。这也解释了为什么“播了几分钟才崩”。
线索三:length=1; index=1 说明两个长度对不上
对照 media3 源码(HlsChunkSource.java)的 createFallbackOptions(Uri),崩溃行附近的逻辑是:
ExoTrackSelection trackSelection = getTrackSelection();
for (int i = 0; i < redundantGroups.length; i++) {
if (trackSelection.isTrackExcluded(i, nowMs) || ...) {
numberOfExcludedTracks++;
}
}
- 循环次数取自
redundantGroups.length(HLS 冗余组数量), - 却用同一个下标去访问
trackSelection(实际选中的轨道集合)。
当冗余组至少有 2 个,而轨道选择里只选了 1 条轨道时,i = 1 就会越过 isTrackExcluded 内部长度为 1 的数组,正好对应 length=1; index=1。
线索四:排除应用自身的原因
在项目里搜索了 TrackSelectionOverride、DefaultTrackSelector、setTrackSelectionParameters 等,都没有使用,应用没有手动限制轨道。去广告的 M3u8Purifier 只改写 m3u8 文本内容,也不碰轨道选择。所以“轨道选择是可用轨道的子集”是播放器和设备解码能力自己协商出来的结果。
线索五:版本与官方发布说明
项目里 media3 版本是 1.10.0。拉取官方仓库的 RELEASENOTES.md,在 1.10.1 的 HLS 部分找到一条:
修复当轨道选择是可用轨道的子集时,流降级过程中抛出
ArrayIndexOutOfBoundsException(androidx/media#3161)。
这条描述和线索三的机制完全吻合:轨道选择只有一部分,却按全部冗余组去遍历。
修复
media3 各模块共用一个版本变量,改一处即可:
# gradle/libs.versions.toml
media3Exoplayer = "1.10.1"
小结
排查这类“栈里全是第三方库”的崩溃,有效的顺序是:
- 用
adb logcat -b crash直接拿完整栈,不要在全量日志里找。 - 看栈顶几帧判断触发场景(这里是加载出错后的降级处理),再结合崩溃前日志解释时间特征。
- 把异常信息里的数字(
length=1; index=1)对回库源码,还原出两个长度为什么对不上。 - 先排除自己的代码是否参与,再去查库的发布说明和 issue。
- 明确写出哪些是实测、哪些是推断。