深入浅出 RenderThread
yeemic
8年前
<p>RenderThread是Android Lollipop中引入的新组件,相关文档很少。事实上,在我写这篇文章的时候,只找到三篇相关引用,以及下面这个很模糊的定义:</p> <p>RenderThread是一个新的由系统控制的处理线程,它可以在UI线程阻塞时保持动画平滑。</p> <p>为了理解其真实功能,我们需要先介绍几个概念。</p> <p>当设备开启硬件加速时,Android不再在每一帧内都执行绘制任务,而是使用一个叫做“展示列表”的(隐藏的)组件,它通过RenderNode类(曾经是DisplayList类)记录绘制操作集合。</p> <p>这种间接的方式可以带来诸多好处:</p> <ol> <li> <p>一个展示列表可以被多次绘制,而不需要重新执行业务逻辑。</p> </li> <li> <p>特定的操作(如转换、放缩等等)可以覆盖整个列表,无需重新安排某个绘制操作。</p> </li> <li> <p>一旦所有的绘制操作已知,就可以进行优化:比如,如果可能,所有的文字都一起绘制。</p> </li> <li> <p>展示列表的处理工作可能可以分发给另一个线程执行。</p> </li> </ol> <p>上面的第四点正是RenderThread的工作之一:处理优化操作与GPU分发,减轻UI线程的压力。</p> <p>在Lollipop之前你可能会注意到,进行如Activity切换等重量级工作时,想要使View属性动画平滑进行是不可能的。而在Lollipop以后,这些动画,包括如水波等其他效果在相同场景下竟可以流畅进行,其中就依靠RenderThread的帮助。</p> <p>渲染的真正执行者是GPU,而它自己是不懂任何动画的。展示动画的唯一方法就是对于每一帧发布不同的绘制指令,这个逻辑不是GPU可以处理的。当这些逻辑需要在UI线程中执行时,重量级工作会妨碍新的绘制指令及时发布,于是产生卡顿现象,无论在进行哪种动画。</p> <p>前面提到了, RenderThread可以负责展示列表流水线的部分工作,但要注意展示列表的创建与修改还是需要在UI线程中完成。</p> <p>那么如何在子线程中更新动画呢?</p> <p>当通过硬件加速进行绘制时,Canvas的实现类叫做DisplayListCanvas(曾经叫GLES20Canvas),它有许多绘制方法的重载方法,这些方法不是接收一个直接的参数,而是一个CanvasProperty的引用,这个CanvasProperty封装了需要的参数值。这样一来在UI线程创建的展示列表仍然可以静态地调用绘制方法,而且这些调用的参数可以通过CanvasProperty映射被动态修改(在RenderThread中异步修改)。</p> <p>之后还有一步:CanvasProperty的值需要通过RenderNodeAnimator来随时间变动,由此动画被配置并启动。</p> <p>产生的动画有这些有趣的属性:</p> <ul> <li> <p>目标DisplayListCanvas:需要被人工设定,且之后不可以再修改。</p> </li> <li> <p>即发即弃:一旦被启动就只能被取消,也就是说无法暂停/继续。而且不能知道当下的值。</p> </li> <li> <p>可以提供一个自定义的Interpolator,其代码会在RenderThread中调用。</p> </li> <li> <p>如有延迟启动,会在RenderThread中进行等待。</p> </li> </ul> <p>下面是 能在RenderThread中操作的动画(到现在为止):</p> <h3>View属性(可以通过View.animate访问):</h3> <ul> <li> <p>变换(X、Y、Z)</p> </li> <li> <p>放缩(X、Y)</p> </li> <li> <p>旋转(X、Y)</p> </li> <li> <p>透明度Alpha</p> </li> <li> <p>圆形展开动画 <a href="/misc/goto?guid=4959673751044967458" rel="nofollow,noindex">Circular Reveal</a></p> </li> </ul> <h3>Canvas方法(通过Canvas属性)</h3> <ul> <li> <p>画圈drawCircle(centerX, centerY, radius, paint)</p> </li> <li> <p>画圆角矩形drawRoundRect(left, top, right, bottom, cornerRadiusX, cornerRadiusY, paint)</p> </li> </ul> <h3>Paint属性</h3> <ul> <li> <p>透明度Alpha</p> </li> <li> <p>宽度Stroke Width</p> </li> </ul> <p>看起来Google只是封装了所有实现Material Design动画所需的绘制操作。这看起来虽然很有限,但是只需要一点创造力就可以实现各种不同的动画,从各种水波动画到全新的效果。这样的动画操作的好处是可以提供不在UI线程运行的不卡顿的动画。</p> <p>现在看起来在Android N中RenderThread的能力会被加强(比如AnimatedVectorDrawable将会在其中完成),或许有朝一日它会进入public API。</p> <p>我可以让我自己的动画运行在RenderThread中吗?</p> <p>官方的简短回答:除了View.animate与ViewAnimationUtils.createCircularReveal提供的动画都不可以。</p> <p>非官方的长一些的回答:本文所说的每一个组件都是隐藏的,所以如果要使用哪个组件都需要通过反射获得所需类和方法的引用,进行封装以保证类型安全,提供获取失败的回调方法等等。详情见我的 <a href="/misc/goto?guid=4959673751126660423" rel="nofollow,noindex">这个repo</a></p> <p>或许这种方法不应该被实际应用,这一点需要你自己把握。</p> <p>使用RenderThread很简单,一般有三步:</p> <pre> <code class="language-java">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. 创建绘制动画所需要的所有CanvasProperty centerXProperty = RenderThread.createCanvasProperty(canvas, initialCenterX); centerYProperty = RenderThread.createCanvasProperty(canvas, initialCenterY); radiusProperty = RenderThread.createCanvasProperty(canvas, initialRadius); paintProperty = RenderThread.createCanvasProperty(canvas, paint); // 2. 创建一个或多个Animator,与你想操作的属性对应 radiusAnimator = RenderThread.createFloatAnimator(this, canvas, radiusProperty, targetRadius); alphaAnimator = RenderThread.createPaintAlphaAnimator(this, canvas, paintProperty, targetAlpha); radiusAnimator.start(); alphaAnimator.start(); } // 3. 绘制到Canvas上 RenderThread.drawCircle(canvas, centerXProperty, centerYProperty, radiusProperty, paintProperty); } </code></pre> <p>在上面的Repo中有完整的示例。</p> <p>欢迎关注我的公众号“androidway”,将零碎时间都用在刷干货上!</p> <p><img src="https://simg.open-open.com/show/01f69ba17681458d9fc0b6ecc1c92964.jpg"></p> <p> </p> <p>来自: <a href="/misc/goto?guid=4959673751213670505" rel="nofollow">http://blog.chengdazhi.com/index.php/190</a></p> <p> </p>