1. 概述:ExoPlayer启动流程与瓶颈分析
搞多媒体播放的兄弟都知道,用户打开视频那几秒的等待感有多要命。我接手过一个直播项目,启动花了快3秒,用户直接骂娘。说白了,ExoPlayer的启动速度直接影响留存率,今天咱们就把这层窗户纸捅破。
我先画个流程图,你大概看一眼整体脉络。后面咱们一截一截拆。
启动流程概览
ExoPlayer 从创建到出第一帧,大致走五步:
- 创建实例 —— new Builder 一把梭,其实里面做了好多活,比如初始化 Looper、建立 HandlerThread。
- 配置 MediaSource —— 根据你的 URL 或文件生成对应的 MediaSource,本地文件还好,网络流就要开始 DNS 解析了。
- 调用 prepare() —— 这是异步的,但主线程还是得等消息回调。内部会下载 Manifest、创建 TrackSelector、初始化 Renderer。
- 解码器初始化 —— 这一步最要命,MediaCodec 创建、配置、Start,AudioTrack 也要折腾一遍。我遇到过有些低端机光解码器创建就花 500ms。
- 第一帧渲染 —— 视频渲染器收到第一帧数据后输出到 Surface,同时触发
onRenderedFirstFrame()。这个回调才是用户真正看到画面的时刻。
关键指标: 从 new ExoPlayer.Builder() 到 onRenderedFirstFrame() 的耗时,业界普遍追求 800ms 以内。超过 1500ms 用户就会觉得「卡」。
瓶颈在哪里
我这些年调启动性能,发现痛点主要集中在下面几个环节:
| 阶段 | 典型耗时 | 原因 |
|---|---|---|
| 创建实例 | 10~50ms | HandlerThread 创建、默认组件初始化 |
| MediaSource 配置 | 10~200ms | 如果传的是 File 还好,传 Uri 需要提取类型 |
| prepare() 加载 Manifest | 100~1500ms | 网络流下载 m3u8 / mpd,DNS 耗时、带宽波动 |
| 解码器初始化(核心瓶颈) | 200~800ms | MediaCodec 创建慢、Surface 绑定、格式协商 |
| 第一帧渲染等待 | 50~300ms | 需要等到第一个关键帧、音视频同步延迟 |
你想想看,最痛的就是 解码器初始化 和 Manifest 下载。我曾在一个短视频 App 里发现,每次启动都要重新创建解码器,纯属浪费。后来做了预热池,直接砍掉 400ms。
注意: 解码器创建耗时不可忽略。尤其是在 Android 8.0 以下机型,MediaCodec 异步模式不完善,主线程可能会被阻塞几十毫秒。我曾经因为没注意这一点,导致丢帧率飙升。
实测数据 – 以 HLS 直播为例
我在测试机(Redmi Note 8 Pro,Android 10)上用 ExoPlayer 2.18 播放一条 1080p HLS 流,抓了 20 次启动日志,平均耗时分布如下:
阶段 平均耗时 占比
----------------------------------------------
ExoPlayer 创建 28ms 2.1%
MediaSource 构建 45ms 3.4%
prepare() 回调 210ms 15.9%
解码器初始化(视频) 412ms 31.2%
解码器初始化(音频) 156ms 11.8%
第一帧渲染 73ms 5.5%
其他(线程调度、缓冲等) 398ms 30.1%
----------------------------------------------
总耗时:1.32s
看到没有?解码器初始化占了 43%,这就是我们要啃的硬骨头。其他项目里还有更夸张的,比如某台荣耀 9X,解码器创建花了 780ms,用户直接黑屏 1 秒多。
小经验: 如果你发现总耗时异常高,先在 prepare 之前做一次 MediaCodec.createDecoderByType() 预热。注意要在后台线程做,别堵住 UI 线程。我曾经试过,预热后解码器初始化从 500ms 降到 120ms。
当然,网络流也有坑。有一次客户抱怨启动慢,我抓包发现 Manifest 下载用了 1.8 秒,结果是 CDN 回源慢。后来加了 本地缓存 Manifest,第二次启动直接减掉 1 秒。这些技巧咱们后面章节会细聊。
好,整体轮廓就是这样。记住一句话:启动速度的核心矛盾在解码器预热和网络预加载。后面我们会一步步教你怎么「偷」时间。今天先到这儿,动手试试测一下你自己的项目启动耗时,你会发现惊喜的。