閱讀284 返回首頁    go 技術社區[雲棲]


RecyclerView Prefetch功能探究

在Android的開發中,滾動列表是一個出鏡率非常高的組件。這其中RecyclerView是當然不讓的明星。從Native到weex,RecyclerView有著非常廣泛的使用。正因如此,我們持續不斷地針對RecyclerView頁麵進行優化。在最近一次調研RecyclerView優化的過程中,偶然看到google在最新版版本的RecyclerView中增加了Prefetch功能。Prefetch的功能到底做了哪些事情,是不是真的能提升頁麵性能,使用的成本有多高?

針對這些問題,本文將分三步來做一個簡單的探究:

  • (1)功能怎麼用
  • (2)效果怎麼樣
  • (3)原理是什麼

1. Prefetch功能的使用

在研究Prefetch功能的實際效果之前,首先需要能正常使用Prefetch功能。

  • 第一步 :升級 Support Library的版本到 25.3.1

    google官方在 Support Library v25 版本中,為RecyclerView增加了Prefetch。
    並且在 v25.1.0 以及25.3.0版本中進行了完善。在最新的穩定版本25.3.1中已經基本穩定。
    如果需要使用建議升級到25.3.1版本。詳細可見support-library change log

    注意: android support v4 包從24.2.0版本開始拆分成了多個更小的模塊。

    包括:support-compat,support-core-utils,support-core-ui,support-media-compat以及
    support-fragment。如果出現使用

    compile group: 'com.android.support', name: 'support-v4',
    version: '25.3.1', ext: 'jar'
    

    出現了類找不到的情況,需要按照需要依賴上述子包。

  • 第二步:實現LayoutManager

    • 如果你使用的是官方的LayoutManager,那麼直接可以獲取Prefetch的功能,無需任何其他的定製。
    • 但是如果你使用的是自定義的LayoutManager,需要重寫LayoutManager.collectAdjacentPrefetchPositions() 方法。如果嵌套RecyclerView使用需要複寫setInitialPrefetchItemCount 詳細信息可以參見具體的API文檔(點擊直接跳轉)
    • 第三步:設置打開Prefetch

    默認情況下Prefetch的功能是打開的。當然也可以手動選擇關閉:

    LayoutManager.setItemPrefetchEnabled(false);
    

    所以確認一下不要手動關閉預取功能

    在完成上述三個步驟以後,可以像往常一樣使用RecyclerView,並且自帶了prefetch功能。

2.Prefetch實際效果

RecyclerView的Prefetch功能正常運轉以後。那麼這個功能到底能帶來多少性能的收益是現在需要確認的。分幾個部分來看:

demo
  • demo內容:
    使用Android官方提供的StaggeredGridLayoutManager實現了一個瀑布流。
    瀑布流中文字的數量和圖片的大小都是可以變的。展示如下:

    TB1CTI3QFXXXXahXXXXXXXXXXXX-640-960.gif

衡量標準&方法:
  • GPU呈現模式分析 打開的GPU渲染條形圖。圖中的綠線是流暢度的分割線。超過綠線的幀都是渲染超時的。比較的核心指標是滑動過程中,超出綠線frame占比。

    (注:流暢的標準是1s 60幀 一幀的渲染時間大約是16ms)
    
  • 測試設備小米5s

  • 數據綁定複雜度模擬:
    為了檢測預取功能在不同的頁麵渲染複雜度情況下的實際效果。在RecyclerView數據綁定函數:onBindViewHolder函數中,使用

    Thread.sleep(time);
    

    來模擬頁麵渲染的複雜度。複雜度的大小,通過time時間的長短來體現。時間越長,複雜度越高。

原始demo流暢度對比
打開prefetch 關閉prefetch

在原始demo中,由於複雜度有限,無論是打開或者關閉prefetch功能,滑動過程中無明顯差異。

延時8ms 流暢度對比
打開prefetch 關閉prefetch

通過延時8ms,整個數據綁定的複雜度急劇上升。在關閉prefetch功能的情況下,在16ms 內無法完成的渲染的幀數明顯增多。相比之下打開prefetch以後,頁麵依然非常流暢。

延時12ms 流暢度對比
打開prefetch 關閉prefetch

通過延時12ms在關閉prefetch功能的情況下,在16ms 內無法完成的渲染的幀數進一步增多,頁麵卡頓愈加明顯。但是打開prefetch以後,頁麵依然非常流暢。

延時17ms 流暢度對比
打開prefetch 關閉prefetch

通過延時17ms,在16ms時間內完成渲染已經無法做到。滑動過程中,滑動卡頓可以明顯被感知。在關閉prefetch功能的情況下,在16ms內無法完成的渲染的幀數進一步增多,並且延時也更長。打開prefetch以後,同樣不可避免的會出現16ms無法渲染幀,但是頁麵對比下還是更加流暢。

結論:基於上述實驗,可以得出以下幾個結論:
  1. 使用prefetch以後,recyclerView的流暢度對頁麵複雜度的敏感性降低。

  2. 在高複雜度(recyclerView的子項view的創建和綁定更加耗時)情況下,prefetch功能確實能顯著提高頁麵流暢度。

3.Prefetch原理:

android 5.0以後,android係統為了提高UI渲染的效率引入了RenderThread。通過這樣的設計,將主線程從UI
渲染的繁重工作中解脫出來。在UI渲染過程中,主線程可以更加專注於跟用戶進行交互。這樣可以大大提高頁麵流暢度。
通過Systrace工具跟蹤一個頁麵,獲取頁麵渲染詳細數據。

  注意:如果使用DDMS來抓取。一定要記得在Enable Application Traces from 中選擇APP所在的進程。否則你可能無法觀察到Prefetch的渲染。如下圖所示:

TB18ZINQFXXXXaBXpXXXXXXXXXX-525-619.png

一個常規頁麵的渲染流程如下:

通過上麵的展示,UI線程準備頁麵數據並且在ready以後交由Render線程渲染。幀與幀之間通過幀同步信號sync來同步。如果頁麵渲染無法在規定時間內完成,就會出現丟幀情況渲染的時序會變成如下情況:

不難發現在正常渲染過程中,有一個非常的明顯的特點:在UI線程將頁麵數據交由Render
線程渲染以後,會出現大量的空閑時間。如下圖所示:

這些空閑等待時間就被浪費了。Prefetch的核心思想就是利用這部分空閑時間來預先處理item的創建和數據綁定[2]。

對比一下使用了Prefetch以後的渲染時序圖如下:

時空上的複用,會大大提高頁麵渲染的效率,提高頁麵流暢度。

4. 收益

在實際開發的過程中,任何的升級都需要考量投入產出的比例。Prefetch優化影響的頁麵包括:

  • (1)Native頁麵中使用RecyclerView的頁麵
  • (2)weex頁麵中使用list的頁麵

    注意: 隻有Android 5.0以後版本的手機才能獲得這個優化。

    通過簡單的升級(可能需要解決一些兼容問題),就可以使幾乎所有的RecyclerView頁麵性能從中受益。並且在sourceCode層麵不需要做額外的定製(自定義和嵌套除外)。投入產出比還是非常可觀的,所以

    建議大家盡快升級吧!

5. 引用

最後更新:2017-04-28 23:21:44

  上一篇:go 麵對SDN,我們該怎麼辦?
  下一篇:go RedAlert簡介