3. Surface生命周期与Activity/Fragment生命周期的耦合:onResume/onPause、onStart/onStop对齐规则
3.1 生命周期耦合的本质
Surface的生命周期并非独立存在,而是与Activity或Fragment的窗口可见性及前台状态深度绑定。系统通过onResume/onPause、onStart/onStop回调向App传递“Surface可用性”的变化。理解这两层生命周期的对齐规则,是避免黑屏、闪退或内存泄漏的前提。
核心认知:
onSurfaceCreated 一定发生在 onResume 之后、onPause 之前;onSurfaceDestroyed 一定发生在 onStop 之后、onDestroy 之前。但具体时序依赖于窗口焦点和硬件合成器的调度。
| Activity 回调 | Surface 回调对齐规则 | 典型触发场景 |
|---|---|---|
onCreate |
Surface 尚未创建(surface == null) |
App启动,Activity构造 |
onStart |
Surface 可能已创建(onSurfaceCreated 在 onResume 前刚完成) |
App从后台返回前台(未锁屏) |
onResume |
保证 Surface 已经创建且有效,可以开始渲染 | 用户可见且可交互 |
onPause |
Surface 仍然有效,但应立即停止提交新帧(渲染线程挂起) | 按Home键、来电、跳转其他Activity |
onStop |
Surface 可能即将销毁(onSurfaceDestroyed 在 onStop 之后不久触发) |
App完全不可见 |
onDestroy |
Surface 已销毁 | Activity销毁/结束 |
3.2 对齐规则与实现流程
SurfaceView 和 TextureView 在实现上略有差异,但共同遵循以下对齐契约:
- 创建对齐:
onSurfaceCreated必须在当前 Activity 的onResume完成之前被调用。如果onResume返回后 Surface 仍无效,渲染器必须等待下一次surfaceChanged。 - 暂停对齐:当
onPause被调用时,渲染线程必须停止向 Surface 提交缓冲区。否则可能导致dequeueBuffer超时或BufferQueue溢出。 - 销毁对齐:
onSurfaceDestroyed在onStop之后,但可能在onDestroy之前。渲染器必须在收到该回调后释放所有 EGL 或 Vulkan 资源。
// 标准生命周期耦合代码(SurfaceHolder.Callback + Activity)
public class MainActivity extends AppCompatActivity {
private SurfaceView surfaceView;
private RenderThread renderThread;
@Override
protected void onResume() {
super.onResume();
// Surface 此时保证有效(若 SurfaceView 已附加到窗口)
if (surfaceView.getHolder().getSurface().isValid()) {
renderThread = new RenderThread(surfaceView.getHolder().getSurface());
renderThread.startRendering();
}
}
@Override
protected void onPause() {
super.onPause();
// 必须停止提交帧,避免 ANR
if (renderThread != null) {
renderThread.pauseRendering();
}
}
@Override
protected void onStop() {
super.onStop();
// Surface 即将销毁,清理资源
if (renderThread != null) {
renderThread.releaseResources();
renderThread = null;
}
}
// SurfaceHolder.Callback
private SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
// 通知渲染线程 Surface 已就绪
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// 尺寸变化时重置视口
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// 渲染线程必须在此回调返回前释放所有 GPU 资源
if (renderThread != null) {
renderThread.waitForFinish();
}
}
};
}
3.3 时序对齐 SVG 图
3.4 常见错误与调试手段
| 错误模式 | 现象 | 根本原因 | 解决办法 |
|---|---|---|---|
渲染线程在 onPause 后仍提交帧 |
dequeueBuffer 返回 -EINTR,App 卡顿甚至 ANR |
未使用 onPause 信号挂起渲染循环 |
在 onPause 中设置 renderPaused = true,渲染循环检查标志 |
onSurfaceDestroyed 后访问 Surface |
IllegalStateException: "Surface was already destroyed" | 渲染线程未等待 surfaceDestroyed 回调完成 |
使用 CountDownLatch 确保资源释放后再返回 |
| 多窗口模式(split-screen)尺寸未重置 | 画面拉伸或裁剪 | surfaceChanged 未同步更新视口 |
在 surfaceChanged 中立即调用 glViewport |
| TextureView 的 SurfaceTexture 生命周期不同步 | 黑屏或最后一帧冻结 | 未正确处理 onSurfaceTextureAvailable / onSurfaceTextureDestroyed |
与 SurfaceHolder.Callback 类似的标志位管理,推荐使用 StateMachine |
3.5 调试日志锚点
在关键生命周期方法中加入带时间戳的日志,便于对齐检查:
// 在 Activity 中
@Override
protected void onResume() {
super.onResume();
Log.d("LifecycleAlignment", "[Activity] onResume at " + System.nanoTime());
}
// 在 SurfaceHolder.Callback 中
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.d("LifecycleAlignment", "[Surface] surfaceCreated at " + System.nanoTime() +
" (diff to onResume: " + (System.nanoTime() - lastResumeNs) / 1e6 + " ms)");
}
稳定性关键:在部分低端设备上,
onSurfaceCreated 可能延迟到 onResume 之后 100~200ms 才触发。渲染器必须实现等待队列机制,而非假设 Surface 立即可用。推荐使用 CompletableFuture 或 PendingFrame 模式缓存渲染请求。
3.6 优化实践:对齐契约检查表
- 创建检查:在
onResume中通过surface.isValid()判断,若无效则暂缓渲染,等待surfaceChanged。 - 暂停检查:渲染循环每帧开始前检查
isPaused标志,该标志由onPause设置为 true。 - 销毁检查:
onSurfaceDestroyed回调内部使用同步屏障,确保所有 GPU 命令执行完毕才返回。 - 多窗口适配:监听
onMultiWindowModeChanged并重新设置缓冲区尺寸,避免surfaceChanged丢帧。
// 渲染线程的核心循环(伪代码)
while (running) {
synchronized (pauseLock) {
if (isPaused) {
pauseLock.wait(); // 等待 onResume 唤醒
}
}
if (!surface.isValid()) {
continue; // 等待 surfaceChanged
}
// 锁定画布、渲染、提交
Canvas canvas = surfaceHolder.lockCanvas();
if (canvas != null) {
doDraw(canvas);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
3.7 边界案例与性能影响
当 Activity 处于 onPause → onStop 过渡期间,Surface 可能仍然可见(如画中画或透明 Activity 叠加)。此时不应销毁渲染资源,仅停止提交新帧。
若渲染线程在 onPause 后立即释放 EGL 上下文,重新进入时需重建,导致额外 50~200ms 的停顿。优化策略:保留 EGL 上下文但暂停渲染循环,利用 eglMakeCurrent 切换。
最佳实践:采用“三态模型”——Active(渲染中)、Paused(Surface有效,渲染挂起)、Stopped(Surface无效,资源待释放)。状态转换由 Activity 生命周期回调驱动,避免对 Surface 回调的过度依赖。