第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 实现流程(典型步骤)

  1. 在布局中添加 SurfaceView(XML 或代码)。
  2. 在 Activity/Fragment 中获取 SurfaceHolder,并注册 Callback。
  3. 实现 Callback 接口:分别在三个回调中启动渲染线程、更新尺寸、安全停止线程。
  4. 在渲染线程中监听 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 触发,以及对应渲染线程状态转换的过程。

Surface 生命周期回调与渲染线程状态机 应用层 Activity/Fragment SurfaceHolder.Callback 接口 surfaceCreated() 启动渲染线程 surfaceChanged() 更新视口/尺寸参数 surfaceDestroyed() 停止线程并释放资源 渲染线程运行中 渲染线程适配新尺寸 渲染线程已退出 线程 join 返回 说明: ① surfaceCreated 和 surfaceChanged 总是成对出现,但 changed 可能在 created 之前被调用(罕见情况)。 ② 渲染线程必须在 surfaceDestroyed 返回之前完全停止,否则 lockCanvas() 会抛出异常。 ③ 若使用 EGL/Surface 直接写入,需在 surfaceDestroyed 中显式释放 EGL 上下文。

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 部署检查清单

  1. 确认 SurfaceView 的 z-order 正确(若需透明背景则调用 setZOrderOnTop(true))。
  2. 检查 onPause 中是否停止渲染线程?onResume 中是否重新创建 Surface?
  3. 横竖屏切换时,Activity 重建会导致全新的 SurfaceView,Callback 会重新被注册;请在 surfaceDestroyed 中完全清理旧资源。
  4. 使用 SurfaceControl + ViewRootImpl 的 Surface 时(非 SurfaceView),需要监听 ViewTreeObserver.OnPreDrawListener 结合 surfaceCreated 时序。