阅读 1022

[译]理解 RenderThread

原文链接:Understanding the RenderThread

RenderThread 是在 Android Lollipop 中新加入的组件。关于该组件的文档描述甚少,仅有一个模糊的定义:

RenderThread 是一个由系统管理的线程。较之 UI 线程的延迟,RenderThread 的动画播放更为流畅。

为了解 RenderThread 的工作机制,需要介绍一些基本的概念。

当启动硬件加速后,Android 使用 “display list” 组件进行绘制而非直接使用 CPU 绘制每一帧。Display List 是一系列绘制操作的记录,抽象为 RenderNode 类。

这样间接的进行绘制操作的优点颇多:

  1. Display List 可以按需多次绘制而无须同业务逻辑交互。
  2. 特定的绘制操作(如 translation, scale 等)可以作用于整个 display list 而无须重新分发绘制操作。
  3. 当知晓了所有绘制操作后,可以针对其进行优化:例如,所有的文本可以一起进行绘制一次。
  4. 可以将对 display list 的处理转移至另一个线程(非 UI 线程)。

最后一点恰好是 RenderThread 负责的:在 UI 线程外执行优化操作与将绘制操作分发给 GPU。

在 Lollipop 之前,执行“重”操作的同时对 View 属性执行动画(例如在 Activity 间执行 transition 动画)几乎不可能。而 Lollipop 及以上的 Android 版本,这类动画以及其它一些效果(例如 ripple )却可以流畅的执行。这样的黑科技便源自 RenderThread 的帮助。

渲染工作的真正执行者是 GPU,而 GPU 对于动画一无所知:执行动画的唯一途径便是将每一帧的不同绘制操作分发给 GPU,但该逻辑本身不能在 GPU 上执行。而如果在 UI 线程执行该操作,任意的重操作都将阻塞新的绘制指令及时分发,于是动画便出现了延迟。

如前文所述,RenderThread 可以处理 display list 流程的某些部分,但要注意到 display list 的创建和修改仍需要在 UI 线程中执行。

那么动画是怎样从不同的线程中进行更新的呢?

开启硬件加速后,CanvasDisplayListCanvas 类实现,该类重载了部分绘制方法,方法参数类型使用 CanvasProperty 对象替换原有的基本类型(例如用 CanvasProperty<Float> 替换 float 类型),CanvasProperty 是原有类型的包装类。这样,dislplay list 及其对应的绘制操作便可以在 UI 线程中创建,而绘制方法的参数可以通过 CanvasProperty 的映射动态地、通过 RenderThread 异步地修改。

实现上述操作还需一步:CanvasProperty 的值需要通过 RenderNodeAnimator 执行动画操作,RenderNodeAnimator 中包含了动画的配置及初始值。

此类动画有一些有趣的特性:

  • 目标 DisplayListCanvas 需要手动设置且不可修改
  • 该动画发送后无须关注:动画开始执行后只能取消(没有暂停和恢复)且无法知晓此刻的属性值
  • 可以设置自定义插值器,插值器将在 RenderThread 执行
  • start delay 一般在 RenderThread 上等待

时至今日,可以通过 RenderThread 执行的动画如下:

View 属性 (通过 View 的 animate 方法执行)

  • Translation (X, Y, Z)
  • Scale (X, Y)
  • Rotation (X, Y)
  • Alpha
  • Circular reveal animation (通过 ViewAnimationUtilscreateCircularReveal 执行)

Canvas 方法与 Canvas 属性

  • drawCircle(centerX, centerY, radius, paint)
  • drawRoundRect(left, top, right, bottom, cornerRadiusX, cornerRadiusY, paint)

Paint 属性

  • Alpha
  • Stroke width

似乎 Google 仅将 Material Design 动画中所需的绘制操作进行了封装。

在 Android N 及以上,RenderThread 的能力的得以拓展(例如,AnimatedVectorDrawable 将使用 RenderThread 执行动画),也许今后 RenderThread 会成为开放 API。

简而言之,我可以在 RenderThread 执行动画吗?

依据文档,不可以。 除非使用 View.animate 或是 ViewAnimationUtils.createCircularReveal

通过 Hack ,可以实现。 对于未开放的组件,我们都可以通过反射获取对相关类、方法的引用,通过包装类以保证类型安全,反射失败时提供反馈等等。

笔者实现了使用 RenderThread 执行动画的库

使用该库十分简单,仅需 3 步:


CanvasProperty<Float> centerXProperty;
CanvasProperty<Float> centerYProperty;
CanvasProperty<Float> radiusProperty;
CanvasProperty<Paint> paintProperty;

Animator radiusAnimator;
Animator alphaAnimator;

@Override
protected void onDraw(Canvas canvas) {

    if (!animationInitialised) {

      // 1. create as many CanvasProperty as needed with the initial animation values
      centerXProperty = RenderThread.createCanvasProperty(canvas, initialCenterX);
      centerYProperty = RenderThread.createCanvasProperty(canvas, initialCenterY);
      radiusProperty = RenderThread.createCanvasProperty(canvas, initialRadius);
      paintProperty = RenderThread.createCanvasProperty(canvas, paint);

      // 2. create one or more Animator with the properties you want to animate
      radiusAnimator = RenderThread.createFloatAnimator(this, canvas, radiusProperty, targetRadius);
      alphaAnimator = RenderThread.createPaintAlphaAnimator(this, canvas, paintProperty, targetAlpha);
      radiusAnimator.start();
      alphaAnimator.start();
    }

    // 3. draw to the Canvas
    RenderThread.drawCircle(canvas, centerXProperty, centerYProperty, radiusProperty, paintProperty);
}
复制代码
关注下面的标签,发现更多相似文章
评论