3. 获取 Choreographer 实例:Choreographer.getInstance()、线程绑定、Looper
3.1 核心原理
Choreographer 是 Android 渲染管线中的帧率协调者,每个线程只能拥有一个实例。它的获取方式严格按照“线程 + Looper”约束:
- 单例绑定:通过
Choreographer.getInstance()获得的实例是线程局部单例,底层使用ThreadLocal存储。 - Looper 必需:调用线程必须关联一个
Looper(通常是主线程或 HandlerThread),否则会抛出IllegalStateException。 - 主线程自动持有:主线程在 ActivityThread 启动时已创建 Looper,可直接获取;子线程需要先调用
Looper.prepare()和Looper.loop()。
错误的实例获取方式会导致空指针异常、帧回调不执行或跨线程数据竞争。
关键源码路径:
android.view.Choreographer#getInstance() 内部调用 sThreadInstance.get(),ThreadLocal 的 initialValue 方法要求线程必须有 Looper。
3.2 获取实例的标准代码
在主线程获取 Choreographer:
Choreographer choreographer = Choreographer.getInstance();
// 无需额外配置,主线程已具备 Looper
在子线程获取(需要手动准备 Looper):
Thread workerThread = new Thread(() -> {
Looper.prepare(); // 为当前线程创建 Looper
Choreographer workerChoreo = Choreographer.getInstance();
Looper.loop(); // 开启消息循环
});
workerThread.start();
使用 HandlerThread 的推荐方式:
HandlerThread handlerThread = new HandlerThread("ChoreoWorker");
handlerThread.start();
Looper looper = handlerThread.getLooper();
// Choreographer 自动绑定到 handlerThread 的 Looper
Choreographer choreographer = Choreographer.getInstance();
3.3 线程绑定与 Looper 关系流程图
下图展示了实例获取的完整链路,从 getInstance() 到 ThreadLocal 检查,再到 Looper 验证:
3.4 调试与常见错误排查
| 错误场景 | 异常日志 | 原因 | 解决方案 |
|---|---|---|---|
子线程未调用 Looper.prepare() |
|
ThreadLocal initialValue 检测到 Looper 为 null | 在调用 getInstance() 前执行 Looper.prepare() |
HandlerThread 未调用 start() |
|
HandlerThread 的 Looper 尚未就绪 | 确保 handlerThread.start() 后再获取实例 |
| 主线程意外退出 Looper | |
某些测试框架或魔法代码调用了 Looper.quit() |
避免在主线程手动调用 quit() |
重要: 在
Looper.loop() 之前获取 Choreographer 实例是安全的(initialValue 会在首次调用 get 时延迟执行),但必须在同一线程内完成。不要在尚未调用 Looper.prepare() 的线程中调用 getInstance(),否则会立即抛出异常。
3.5 性能与稳定性优化建议
- 复用实例:在 Activity/Fragment 生命周期内,仅获取一次 Choreographer 实例并缓存为成员变量,避免频繁调用
getInstance()(虽然开销很小,但强调良好习惯)。 - 子线程 Looper 生命周期管理:如果要在子线程使用 Choreographer,推荐使用
HandlerThread,并在不再需要时调用quitSafely()释放资源。 - 避免跨线程回调:通过 Choreographer 注册的 FrameCallback 会在绑定线程的消息队列中执行,不要在回调中调用可能切换到其他线程的方法,以免破坏帧率同步。
- 使用正确的 Looper 模式:对于后台帧率监控(如自定义帧率记录),创建独立的 HandlerThread 并获取 Choreographer 实例,与主线程隔离。
实践建议: 在应用启动时(如 Application.onCreate)主动获取一次主线程 Choreographer 实例并保存在全局单例中,既保证了线程安全,又便于调试时快速获取帧回调。