View的異步Inflate+全局緩存:加速你的頁面

一、背景

Android 在 View 的使用中,過多的佈局文件 inflate 影響性能,尤其在一些滾動列表、樣式種類很豐富的場景下,inflate 次數相對較多,整體 inflate 耗時就會增加,導致滾動過程卡頓。

所以,需要 View 的異步 inflate,甚至 View 的全局緩存,通過這些方式,去減少 UI 線程 inflate 的耗時及次數,以便減少卡頓,提升性能。

二、現有的解決方案

要實現 View 的異步 Inflate,最簡單粗暴的方式是直接 new Thread 執行 inflate,或者使用 HandleThread+Handler 的方式,或者使用 android 官方 android.support.v4.view 包的 AsyncLayoutInflater 類。但都存在一個問題,就是如果 inflate view 實例的時候 view 的構造方法裏面直接有 new Handler()(原本想新建一個主線程 Handler 用於 UI 操作),將會達不到要求。

ListView、RecycleView 等控件,自身實現了“緩存複用”機制,這使得滑動過程性能得以提升,但更多的是控件間、或者頁面內的緩存複用,而對於頁面之間(activity 與 activity)緩存複用,沒一個有效的解決方案。當然,如果把 View 的緩存做成全局單例,是可以做到頁面之間的緩存複用,但 View 和 context 強綁定在一起,全局緩存可能會導致 activity 實例不能正常釋放,導致內存泄露問題;也可能在 context 使用上存在問題,如用 APPlication 實例傳遞給 View 的 context,代碼中又直接把 context 當成 activity 用,將導致一些問題。

三、異步 Infllate+全局緩存問題分析及解決方案

從現有方案中,提煉出兩個核心問題,一、異步 Inflater 的時候,View 中 new Handler 導致的安全問題;二、全局緩存,View 的 context 問題。

  1. 異步 Inflater 的時候,View 中 new Handler 導致的安全問題解決方案

對於直接 new Thread 執行 inflate,或者使用 HandleThread+Handler 的方式,或者使用 android 官方 android.support.v4.view 包的 AsyncLayoutInflater 類。

示例代碼

三種方式的對比:

第一種和第三種方式,run 方法中由於沒有使用 Looper.loop 機制,這使得 new Handler 即使調用 post 方法發消息,並不會正在執行,導致 UI 不能正常刷新。

第二種,雖然 new Handler 能正常工作,但如刷新 UI,很可能會 crash,比如如下的異常:

所以 Handler hander = new Handler(),往 handler 拋的消息需要拋到主線程,比如改寫成 Handler hander = new Handler(Looper.getMainLooper())。但是我們無法更改 View 的 Handler 構造代碼,下面方案通過了反射的方式,強制把後臺的線程的 Looper 設置爲 mainLooper,這樣後臺線程 new Handler()方式也能把消息拋到主線程消息隊列

使用示例:

  1. 全局緩存,View 的 context 問題解決方案

在全局緩存時,爲了解決創建 view 的 context 不一定是 activity 導致的問題,或者是 activity 導致的內存泄露問題,對 Context 做封裝:新建了 ViewContext 代理類:

inflate 的時候,將用 ViewContext 傳入 View,方式如下:

四、基本實施及使用

  1. 基本架構

  1. 實施思路

1)View 的緩存大小應控制,且可動態修改:在 View 的緩存方面,設計一個緩存大小機制,且允許動態的修改對應的緩存大小,這樣可以根據具體需要設置,從而更好的控制內存使用;

2)緩存 View 的狀態處理,方便管理。異步創建 View,放入緩存池並標記可用,每次從緩存池獲取 View 後,標記狀態不可用,待回收後在標記可用;

3)異步創建 View,可預加載。提供 View 的異步創建,並放入緩存中,結合預加載,能有效的減少實際創建 View 所需的耗時,提升性能;

4)內存管理策略–應用低內存自動釋放緩存。通過 context 取到 APPlication 註冊一個 ComponentCallbacks,監聽 APP 內存狀態,適當的釋放緩存;

5)緩存優先級策略–可設置緩存釋放優先級。提供設置緩存釋放優先級的能力,業務方可以更精準的利用緩存,更好的滿足業務所需;

6)View 創建方式。對於 View 的創建,可以設計一個 IViewCreeator,創建 View 的過程由使用方決定。如佈局文件中只有 TextView,可以傳入 layoutId 選擇 new TextView()的方式。

  1. 實施

基本使用如下,新建單例(如 ViewHelper):

使用 ViewHelper 如下:

使用前後的效果:

  1. 注意事項

1)使用異步 Inflate+全局緩存構建的 View,在使用時需要重新設置 LayoutParams,不然顯示上可能不是最終想要的結果;

2)使用異步 Inflate+全局緩存構建的 View,如果 View 的解析過程中,存在 Theme 相關的,可能會導致 View 構建失敗。如原生的 TabLayout,解析時會讀取 Theme 中的屬性,如果 context 傳入的是 APPlication 且沒有設置相關 Theme,就會報錯;

3)使用異步 Inflate+全局緩存構建的 View,需要及時的調用 refreshCurrentActivity 方法,這樣儘可能的保持 context 是當前的 activity 實例。在使用 context 的時候,避免直接把 context 強轉 activity,而是使用 ViewContext 的 getActivity 方法獲取。

五、總結

選擇異步 Inflate ,應根據需要,合理的選擇。如只需 activity 級別的,選擇原生 AsyncLayoutInflater 的方式,就能很好的滿足要求,並且沒有 context 使用問題。如想更早的準備或者跨頁面複用,View 的異步 Inflate+全局緩存是更好的選擇,但要注意 context 使用問題,因爲 inflate 所需的 context 不一定是 activity,也正是這點使得單例緩存的 View,不用擔心內存泄露問題,滿足多頁面的緩存複用。

目前,優酷在 AsyncView 項目已經實現 View 的異步 Inflate+全局緩存,該項目已經對公司內部開源,是 AIOSO 的子項目之一,也在進一步的落實對外開源。它是“低侵入式”的,沒有對 Android 原生 UI 進行改造,Android UI 框架開發的 APP 都可以方便接入。該項目已經在優酷 APP 上大量使用,反饋效果良好,主要體現在:

1)在幀率方面,整體帶來了 5%左右提升。尤其在低端機,體感上有明顯的提升;

2)在啓動方面,結合各業務端提前做一些預加載任務,整體帶來了 20%左右的提升。

作者 | 阿里文娛高級無線開發工程師 瑞源

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章