使用AsyncLayoutInflater預加載,加快列表渲染

引子

列表卡頓是一個很常見的問題,通常的理解是滑動過程中卡頓,有很多常規的處理方法,從最簡單的複用到降低佈局複雜度到部分佈局動態加載。本文介紹下列表初次填充數據時卡頓的處理方法。

AsyncLayoutInflater

在查找頁面fps過低的問題中,我發現在列表首次填充數據進行顯示時,由於列表中將要顯示在屏幕中的ViewHolder都要被創建,進而這些ViewHolder的View都要被Inflate出來,因此會佔用UI線程大段時間,普通情況下有(100-500ms)的耗時。這段耗時按常規的處理方法處理,只能減少,不能避免。
一次看源碼中無意遇到AsyncLayoutInflater類,心想難道inflate()方法可以異步執行?百度了AsyncLayoutInflater類的介紹,看了下源碼,發現還真的可以異步加載View,正好可以處理這個問題。
處理方法也比較簡單,在Adapter創建時,就使用AsyncLayoutInflater來異步加載若干個ViewHolder的View,放入ViewType和ArrayList組成的SparseArray中,具體預加載的類型和數目根據業務的特點進行適配,避免預加載過少導致效果不大,避免預加載過多一直用不上浪費內存;在Adapter的onCreateViewHolder()回調中,從SparseArray中按ViewType取出ArrayList,並remove()出View,進行使用;如果沒有,則同步inflate進行創建。
經過這樣的處理,列表首次加載出來,立刻開始快速滾動,都很流暢,耗時基本上都在findViewById()和bindViewHolder()上。
在這個過程中,需要注意一些問題:
1.使用AsyncLayoutInflater創建的View,由於是在異步線程中進行Inflate操作,所以在它們和它們的children的構造函數中,Looper.myHandler()返回的爲null,如果想使用綁定到主線程Looper的Handler,需要通過new Handler(Looper.getMainLooper())來創建;另外,如果一些強制在UI線程中執行的操作,需要使用Handler切換到主線程中;
2.所有AsyncLayoutInflater公用一個異步線程進行inflate處理,未來得及處理的任務會被緩存起來;最大允許緩存的任務數爲10;超過10個,則會導致添加任務的線程阻塞。所以不要可着勁添加任務,如果想添加很多任務,可以在一些任務的回調中添加其他任務;
這個地方插一句,爲什麼會出現阻塞的情況呢,由於異步Inflate線程使用的是普通的線程,所以當沒有任務時,線程需要進入到阻塞狀態,因而任務緩存隊列使用的是ArrayBlockingQueue這種數據結構,那麼插入任務時,如果超過容量,就會導致插入任務線程阻塞,直到有容量可以存儲。
3.AsyncLayoutInflater異步inflate任務失敗,會回退到UI線程中執行,所以要仔細留意Logcat中是否有"Failed to inflate resource in the background! Retrying on the UI thread"日誌;
4.AsyncLayoutInflater每執行完一個任務,就會向UI線程中回調一次,如果出現多個回調接近於同時先後進行回調,那一定是由於主線程中進行耗時操作阻塞了,可以進一步優化。

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