20. 多窗口与画中画支持:多窗口生命周期、PIP模式下的渲染与同步
多窗口和画中画(PIP),这两个功能现在已经是Android系统的标配了。我记得刚做Stagefright那会儿,Android还只有单窗口模式,后来Android 7.0引入多窗口,再到Android 8.0正式支持PIP,每一次改动都让多媒体栈经历了一次不小的重构。
说白了,多窗口和PIP的核心挑战就两个:生命周期怎么管,渲染怎么同步。今天我们就来拆解这两个问题。
20.1 多窗口下的生命周期变化
先聊聊多窗口。当你的视频应用进入分屏模式,Activity的生命周期会怎么走?
嗯,这里有个关键点:多窗口模式下,Activity不会被销毁。它只是从“前台可见”变成了“部分可见”。但Stagefright的MediaPlayer呢?它可不管这些,它只认Surface的状态。
我在项目中遇到过一个问题:分屏后视频播放突然卡顿。排查了半天,发现是Surface被系统回收了,但MediaPlayer还在往上面送数据。这就是生命周期没对齐的典型表现。
具体来说,你需要监听以下几个回调:
onMultiWindowModeChanged()— 进入/退出多窗口onSurfaceChanged()— Surface尺寸变化onSurfaceDestroyed()— Surface被销毁
我个人习惯的做法是:在onMultiWindowModeChanged()中暂停播放,等Surface稳定后再恢复。你想想看,如果Surface尺寸从1080p突然变成540p,解码器输出的分辨率没跟上,那画面不就撕裂了吗?
20.2 PIP模式下的渲染挑战
画中画模式比多窗口更特殊。PIP窗口通常只有200dp左右,但视频源可能是4K的。这里就涉及到一个问题:解码器要不要降分辨率?
我的建议是:不要降。为什么?
第一,降分辨率需要重新配置解码器,这个操作很重,会导致明显的卡顿。第二,用户随时可能把PIP窗口拖回全屏,如果解码器还在低分辨率模式,那全屏后的画质就惨不忍睹了。
那怎么办?让SurfaceFlinger来做缩放。解码器输出全分辨率帧,SurfaceFlinger负责缩放到PIP窗口大小。这样既保证了画质,又避免了解码器重启。
20.3 渲染同步:一个容易被忽视的坑
多窗口和PIP模式下,渲染同步是个大问题。为什么?因为多个窗口可能共享同一个显示管道。
举个例子:你的视频应用在分屏左侧播放,右侧是另一个应用。两个应用都在往SurfaceFlinger提交Buffer。如果同步没做好,就会出现“掉帧”或“画面撕裂”。
Stagefright是怎么处理的?它依赖BufferQueue的同步机制。每个Surface对应一个BufferQueue,生产者(MediaCodec)和消费者(SurfaceFlinger)通过dequeue/queue操作来同步。
但这里有个坑:PIP模式下,BufferQueue的深度可能不够。全屏时你可能用了3个Buffer,但PIP窗口小,SurfaceFlinger处理得快,3个Buffer就浪费了。我建议动态调整BufferQueue的深度:
// 全屏模式
SurfaceHolder holder = getSurfaceHolder();
holder.setBufferSize(3);
// PIP模式
holder.setBufferSize(2); // 减少Buffer数量,降低延迟
嗯,这里要注意:setBufferSize()不是所有设备都支持。我在某款低端机上就踩过这个坑,调用后直接崩溃。所以一定要加try-catch,或者先检查API版本。
20.4 知识体系总览
下面这张图总结了多窗口和PIP的核心逻辑:
20.5 避坑指南
最后分享几个我踩过的坑:
我曾经在PIP窗口里做seek操作,结果画面卡住了。原因是PIP模式下解码器处于低功耗状态,seek会触发解码器重启,但SurfaceFlinger那边还在等Buffer。解决方案:PIP模式下禁用seek,或者先退出PIP再seek。
用户从分屏切回全屏,中间可能只有几百毫秒。如果你在
onMultiWindowModeChanged()里直接释放解码器,那切回来时就得重新创建,卡顿感很明显。我建议用Handler.postDelayed()延迟释放,给用户一个“反悔”的时间。
PIP窗口默认只响应拖动和双击。如果你想支持更多手势(比如滑动调节音量),需要自己实现
onTouchEvent()。但注意:PIP窗口的触摸区域很小,手势识别要做得灵敏一些。
好了,多窗口和PIP的内容就聊到这里。核心就是记住:生命周期跟着Surface走,渲染同步靠BufferQueue。下次遇到多窗口下的播放问题,先检查这两个地方,基本能解决80%的问题。
公众号:蓝海资料掘金营,微信deep3321