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 验证:

Choreographer 实例获取与线程绑定流程 调用线程 getInstance() sThreadInstance.get() 已有实例? 是 返回已有实例 否 Looper.myLooper() Looper存在? 否 IllegalStateException 是 new Choreographer() → 设置ThreadLocal 线程局部存储 每个线程独立副本

3.4 调试与常见错误排查

错误场景异常日志原因解决方案
子线程未调用 Looper.prepare()
java.lang.IllegalStateException: 
The thread doesn't have a looper.
ThreadLocal initialValue 检测到 Looper 为 null 在调用 getInstance() 前执行 Looper.prepare()
HandlerThread 未调用 start()
Calling Choreographer.getInstance() 
on a thread without looper.
HandlerThread 的 Looper 尚未就绪 确保 handlerThread.start() 后再获取实例
主线程意外退出 Looper
Lost Looper in main thread.
某些测试框架或魔法代码调用了 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 实例并保存在全局单例中,既保证了线程安全,又便于调试时快速获取帧回调。