第1章 Surface核心对象与生命周期回调
Surface 是 Android 图形系统中最底层的原生绘图目标。每一个窗口(Window)都关联一个或多个 Surface,而 SurfaceView 封装了独立的 Surface,允许应用在主 UI 线程之外进行高效渲染。Surface 的生命周期与承载它的 SurfaceView 的可见性、窗口焦点、资源释放直接相关。Android 通过 SurfaceHolder.Callback 接口向开发者暴露 Surface 的创建、尺寸变更、销毁三个关键事件。
1.1 核心概念与对象关系
- Surface:一个由帧缓冲区(BufferQueue)驱动的原生图形缓冲区,用于组合 UI 图层。每个 SurfaceView 拥有一个独立的 Surface。
- SurfaceView:继承自 View,内部管理一个 Surface,渲染在独立线程中完成,不阻塞主线程。
- SurfaceHolder:操作 Surface 的抽象接口,通过
getHolder()获取。其内部维护一个SurfaceHolder.Callback列表。 - SurfaceHolder.Callback:生命周期监听接口,包含三个必须回调:
surfaceCreated、surfaceChanged、surfaceDestroyed。
关键认知:Surface 的创建与销毁由系统窗口管理器驱动,应用仅能通过 Callback 被动感知。不要在
surfaceDestroyed 返回后继续向 Surface 提交绘制指令,否则会导致崩溃或死锁。
1.2 生命周期回调详解
| 回调方法 | 触发时机 | 典型用途 | 线程 |
|---|---|---|---|
surfaceCreated(SurfaceHolder holder) | Surface 已创建并准备就绪;应用首次显示 SurfaceView 时调用,且每次从不可见变为可见时可能重新调用。 | 初始化渲染线程、分配 GPU 资源、创建 EGL 上下文或 Canvas 渲染器。 | 主线程(需手动切至渲染线程) |
surfaceChanged(SurfaceHolder holder, int format, int width, int height) | Surface 格式或尺寸发生改变(如横竖屏旋转、软键盘弹出导致窗口 resize)。在 surfaceCreated 之后至少调用一次。 | 调整视口(Viewport)、更新投影矩阵、重置显示尺寸相关参数。 | 主线程 |
surfaceDestroyed(SurfaceHolder holder) | Surface 即将被销毁;通常发生在 SurfaceView 从窗口移除、Activity 被销毁或调用到 onPause 之后。 | 停止渲染线程、释放 EGL 上下文、归还 GPU 显存。此回调返回后 Surface 不可用。 | 主线程 |
1.3 实现流程(典型步骤)
- 在布局中添加 SurfaceView(XML 或代码)。
- 在 Activity/Fragment 中获取 SurfaceHolder,并注册 Callback。
- 实现 Callback 接口:分别在三个回调中启动渲染线程、更新尺寸、安全停止线程。
- 在渲染线程中监听 Surface 有效性:通过标志位或锁定对象避免线程在 surfaceDestroyed 后访问 Surface。
1.4 代码实现
以下是一个典型的 SurfaceView 渲染管理器实现片段:
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private RenderThread mRenderThread;
public MySurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
SurfaceHolder holder = getHolder();
holder.addCallback(this);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (mRenderThread == null) {
mRenderThread = new RenderThread(holder);
mRenderThread.start();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (mRenderThread != null) {
mRenderThread.setSurfaceSize(width, height);
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (mRenderThread != null) {
mRenderThread.stopRendering();
boolean retry = true;
while (retry) {
try {
mRenderThread.join();
retry = false;
} catch (InterruptedException e) { }
}
mRenderThread = null;
}
}
}
渲染线程中的核心循环,必须时刻检查 Surface 有效性:
private class RenderThread extends Thread {
private SurfaceHolder mHolder;
private volatile boolean mRunning = true;
private int mWidth, mHeight;
RenderThread(SurfaceHolder holder) {
mHolder = holder;
}
void setSurfaceSize(int w, int h) {
mWidth = w;
mHeight = h;
}
void stopRendering() {
mRunning = false;
interrupt(); // 如果线程在阻塞,则唤醒它
}
@Override
public void run() {
Canvas canvas = null;
while (mRunning) {
synchronized (mHolder) {
try {
canvas = mHolder.lockCanvas(null);
if (canvas != null) {
// 绘制操作
canvas.drawColor(Color.BLACK);
// 解锁并提交
mHolder.unlockCanvasAndPost(canvas);
}
} catch (Exception e) {
// Surface 已无效,跳出循环
break;
} finally {
// 避免泄露
}
}
// 控制帧率
SystemClock.sleep(16);
}
}
}
1.5 生命周期流程图(SVG)
下图展示了从 SurfaceView 可见性变化到 Callback 触发,以及对应渲染线程状态转换的过程。
1.6 调试与日志
在 Callback 回调中添加日志可以帮助定位生命周期时序异常:
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.d("SurfaceLifecycle", "surfaceCreated -- tid=" + Thread.currentThread().getId());
// ...
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
Log.d("SurfaceLifecycle", "surfaceChanged --> " + w + "x" + h);
// ...
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d("SurfaceLifecycle", "surfaceDestroyed -- worker alive? " + (mRenderThread != null && mRenderThread.isAlive()));
// ...
}
调试技巧:使用
adb shell dumpsys SurfaceFlinger 可以查看当前系统中所有 Surface 的状态。结合 logcat -s SurfaceLifecycle 过滤回调日志,快速定位生命周期不匹配问题。
1.7 常见错误与避坑
| 错误场景 | 根本原因 | 解决方案 |
|---|---|---|
渲染线程在 surfaceDestroyed 后仍调用 lockCanvas | 线程未及时停止,或锁竞争导致回调返回时才解锁 | 使用 volatile 标志位 + mRunning = false 并 join 等待线程结束;在 lockCanvas 前检查 Surface 有效性 |
surfaceChanged 中未更新视口,导致绘制变形 | 忽略了与 surfaceCreated 的顺序关系 | 每次 surfaceChanged 都重新设置渲染器的视口;将视口参数以原子变量传递给渲染线程 |
| 多个 SurfaceView 共用一个 Callback 实例 | 混淆了 holder 对应的 Surface | 每个 SurfaceView 单独创建 Callback 实现;或通过 holder 对象区分 |
在 surfaceCreated 中创建耗时资源,导致 UI 线程卡顿 | 回调默认运行在主线程 | 资源初始化放入渲染线程,回调中仅发送信号或创建线程 |
1.8 性能与稳定性优化
- 避免在主线程中锁定 Canvas:所有
lockCanvas/unlockCanvasAndPost必须在独立渲染线程中调用。 - 使用双缓冲或三缓冲:
SurfaceHolder默认支持双缓冲,如需三缓冲可配置setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)(API 11+ 已废弃,但 Legacy 场景可参考)。 - 及时释放 Surface 引用:在
surfaceDestroyed后将 holder 引用置为 null,防止渲染线程意外访问。 - 控制帧率:使用
Choreographer或SystemClock.sleep限制渲染帧率,避免过多 CPU/GPU 开销。 - 考虑 SurfaceControl API(Android 10+):对于更底层的 Surface 生命周期管理,可迁移至
SurfaceControl结合Transaction实现。
稳定性红线:
surfaceDestroyed 返回后,任何对旧 Surface 的操作都会触发 IllegalStateException("Surface was not created")。务必在渲染线程中使用 try-catch 包裹 lockCanvas,并在捕获异常后退出循环。
1.9 部署检查清单
- 确认
SurfaceView的z-order正确(若需透明背景则调用setZOrderOnTop(true))。 - 检查
onPause中是否停止渲染线程?onResume中是否重新创建 Surface? - 横竖屏切换时,Activity 重建会导致全新的 SurfaceView,Callback 会重新被注册;请在
surfaceDestroyed中完全清理旧资源。 - 使用
SurfaceControl+ViewRootImpl的 Surface 时(非 SurfaceView),需要监听ViewTreeObserver.OnPreDrawListener结合surfaceCreated时序。