深入淺出 RenderThread

yeemic 8年前發布 | 14K 次閱讀 安卓開發 Android開發 移動開發

RenderThread是Android Lollipop中引入的新組件,相關文檔很少。事實上,在我寫這篇文章的時候,只找到三篇相關引用,以及下面這個很模糊的定義:

RenderThread是一個新的由系統控制的處理線程,它可以在UI線程阻塞時保持動畫平滑。

為了理解其真實功能,我們需要先介紹幾個概念。

當設備開啟硬件加速時,Android不再在每一幀內都執行繪制任務,而是使用一個叫做“展示列表”的(隱藏的)組件,它通過RenderNode類(曾經是DisplayList類)記錄繪制操作集合。

這種間接的方式可以帶來諸多好處:

  1. 一個展示列表可以被多次繪制,而不需要重新執行業務邏輯。

  2. 特定的操作(如轉換、放縮等等)可以覆蓋整個列表,無需重新安排某個繪制操作。

  3. 一旦所有的繪制操作已知,就可以進行優化:比如,如果可能,所有的文字都一起繪制。

  4. 展示列表的處理工作可能可以分發給另一個線程執行。

上面的第四點正是RenderThread的工作之一:處理優化操作與GPU分發,減輕UI線程的壓力。

在Lollipop之前你可能會注意到,進行如Activity切換等重量級工作時,想要使View屬性動畫平滑進行是不可能的。而在Lollipop以后,這些動畫,包括如水波等其他效果在相同場景下竟可以流暢進行,其中就依靠RenderThread的幫助。

渲染的真正執行者是GPU,而它自己是不懂任何動畫的。展示動畫的唯一方法就是對于每一幀發布不同的繪制指令,這個邏輯不是GPU可以處理的。當這些邏輯需要在UI線程中執行時,重量級工作會妨礙新的繪制指令及時發布,于是產生卡頓現象,無論在進行哪種動畫。

前面提到了, RenderThread可以負責展示列表流水線的部分工作,但要注意展示列表的創建與修改還是需要在UI線程中完成。

那么如何在子線程中更新動畫呢?

當通過硬件加速進行繪制時,Canvas的實現類叫做DisplayListCanvas(曾經叫GLES20Canvas),它有許多繪制方法的重載方法,這些方法不是接收一個直接的參數,而是一個CanvasProperty的引用,這個CanvasProperty封裝了需要的參數值。這樣一來在UI線程創建的展示列表仍然可以靜態地調用繪制方法,而且這些調用的參數可以通過CanvasProperty映射被動態修改(在RenderThread中異步修改)。

之后還有一步:CanvasProperty的值需要通過RenderNodeAnimator來隨時間變動,由此動畫被配置并啟動。

產生的動畫有這些有趣的屬性:

  • 目標DisplayListCanvas:需要被人工設定,且之后不可以再修改。

  • 即發即棄:一旦被啟動就只能被取消,也就是說無法暫停/繼續。而且不能知道當下的值。

  • 可以提供一個自定義的Interpolator,其代碼會在RenderThread中調用。

  • 如有延遲啟動,會在RenderThread中進行等待。

下面是 能在RenderThread中操作的動畫(到現在為止):

View屬性(可以通過View.animate訪問):

  • 變換(X、Y、Z)

  • 放縮(X、Y)

  • 旋轉(X、Y)

  • 透明度Alpha

  • 圓形展開動畫 Circular Reveal

Canvas方法(通過Canvas屬性)

  • 畫圈drawCircle(centerX, centerY, radius, paint)

  • 畫圓角矩形drawRoundRect(left, top, right, bottom, cornerRadiusX, cornerRadiusY, paint)

Paint屬性

  • 透明度Alpha

  • 寬度Stroke Width

看起來Google只是封裝了所有實現Material Design動畫所需的繪制操作。這看起來雖然很有限,但是只需要一點創造力就可以實現各種不同的動畫,從各種水波動畫到全新的效果。這樣的動畫操作的好處是可以提供不在UI線程運行的不卡頓的動畫。

現在看起來在Android N中RenderThread的能力會被加強(比如AnimatedVectorDrawable將會在其中完成),或許有朝一日它會進入public API。

我可以讓我自己的動畫運行在RenderThread中嗎?

官方的簡短回答:除了View.animate與ViewAnimationUtils.createCircularReveal提供的動畫都不可以。

非官方的長一些的回答:本文所說的每一個組件都是隱藏的,所以如果要使用哪個組件都需要通過反射獲得所需類和方法的引用,進行封裝以保證類型安全,提供獲取失敗的回調方法等等。詳情見我的 這個repo

或許這種方法不應該被實際應用,這一點需要你自己把握。

使用RenderThread很簡單,一般有三步:

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);
}

在上面的Repo中有完整的示例。

歡迎關注我的公眾號“androidway”,將零碎時間都用在刷干貨上!

 

來自: http://blog.chengdazhi.com/index.php/190

 

 本文由用戶 yeemic 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!