现象

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"

小结

排查这类“栈里全是第三方库”的崩溃,有效的顺序是:

  1. 用 adb logcat -b crash 直接拿完整栈,不要在全量日志里找。
  2. 看栈顶几帧判断触发场景(这里是加载出错后的降级处理),再结合崩溃前日志解释时间特征。
  3. 把异常信息里的数字(length=1; index=1)对回库源码,还原出两个长度为什么对不上。
  4. 先排除自己的代码是否参与,再去查库的发布说明和 issue。
  5. 明确写出哪些是实测、哪些是推断。