安卓易學,爬坑不易——騰訊老司機的RecyclerView區域性重新整理爬坑之路

安卓易學,爬坑不易——騰訊老司機的RecyclerView區域性重新整理爬坑之路

針對手遊的效能優化,騰訊WeTest平臺的Cube工具提供了基本所有相關指標的檢測,為手遊進行最高效和準確的測試服務,不斷改善玩家的體驗。目前功能還在免費開放中。
點選地址:http://wetest.qq.com/cube立即體驗!


作者:Hoolly,騰訊移動客戶端開發工程師。
商業轉載請聯絡騰訊WeTest獲得授權,非商業轉載請註明出處

WeTest導讀

安卓開發者都知道,RecyclerView比ListView要靈活的多,但不可否認的裡面的坑也同樣埋了不少人。下面讓我們看看騰訊開發工程師用例項講解自己踩坑時的解決方案和心路歷程。
話說有圖有真相,首先來對比一下區域性重新整理前後的效果:
優化之前的效果:
圖片描述

優化之後的效果:
圖片描述
可以看到,優化之後,列表中的這張大圖不在有一閃一閃亮晶晶的效果了!
那麼,這是如何做到的呢?這是本文的重點,本文的大綱主要包括:
分析為什麼會閃一下
對分析的可能造成閃動的問題進行解決
驗證是否解決

一、為什麼會閃一下?

我們的需求是大家已經看到了,點選打分,彈出一個對話方塊,點選一個分數,這時候,通過一些列複雜的轉換(當然不是本文的論述的重點),這時候到了要更新列表項了,如是很自然,我們會這麼做:
圖片描述
因為,操作的那個列表項你是知道他的position,所以你可以這麼做,(當然,我之前是直接notifyDataSetChanged的,這個會照成所以不不要的item也會重新整理)然而,閃動還是出現了,那麼我開始懷疑:
流傳甚為廣泛的一種說法,imageView的寬高不固定導致的(wrap_content)?
這個是RecyclerView自帶的更新動畫效果導致的?
這個是因為圖片載入框架(glide 的 animte)的動畫效果導致的?
getView中(RecyclerView中是onBindViewHolder)載入圖片的時候,設定一個tag,當發現這個imageView的tag和之前的tag一致時就不載入

二、帶著思考,就去嘗試吧!

1、對於第一種,我的做法是自己寫了一個自定義的imageView,重寫omMeasure方法,如下:
圖片描述
因為我們的這個列表項中的圖片是(高=寬)的,因此,我才這麼寫,這樣寫也有一個好處,不用在onBindViewHolder中去動態的計算出高度,然後在已layoutParm的方式設定給imageView,相信不少小夥伴都做過了吧!
然而,遺憾的是,他並沒有解決閃一下的問題!此時這個閃動的原因顯然不在這裡,但是這裡做的,可以保留下來。

2、對於第二種說法,我參考了這裡
http://stackoverflow.com/questions/29331075/recyclerview-blinking-after-notifydatasetchanged
的做法:
圖片描述
以及也嘗試了這種
圖片描述
然而,那種漸變的閃動消失了,但是,取而代之的是一種更加不可接受的閃動,這裡就不用gif展示了,因此原因也並不在此處。

3、對於對三種說法,我也去嘗試了一下將glide載入改為:
圖片描述
然而得到的依然是一個失望的結果,依然沒有解決閃動的問題,原因也不在此處。

4、那麼,就剩下最後一個猜測了,那麼會不會是它呢?那就試試吧,於是程式碼改為:
圖片描述
這裡的做法其實就是設定Tag,那麼是騾子是馬,拉出來溜溜吧,結果更加令人髮指,如圖:
圖片描述

好吧,此時已經有點崩潰了,顯然這個也不是我要的結果,那麼此時是否應該在靜下來想一想,自己對於可能的幾種原因做過的一些對策,是否有哪裡遺漏了。經過思考,發現並沒有!!那麼一定是還有其他的原因,沒有考慮到!
還是去翻一翻RecyclerView的api吧,我注意到了這個api:
圖片描述
圖片描述

可以看到這裡有一個payload的引數,use null to identify a “full” update這是說如果傳null就是全部更新,回過頭去看一看我們之前的呼叫方式:
圖片描述

看一下原始碼,發現
圖片描述
實際上,payload這個引數就是傳的null,那也就是說如果傳一個不為null的引數,就可以對列表項中的具體控制元件更新了?
http://stackoverflow.com/questions/33176336/need-an-example-about-recyclerview-adapter-notifyitemchangedint-position-objec
我瞭解到這個方法的使用方式是這樣的:
圖片描述

然來,onBindViewHolder有這麼一個過載方式,如是我也這麼做了,在下面這個過載中,去更新我想更新的控制元件:
圖片描述

然後,更新的方式變成了這種:
圖片描述
是騾子是馬,那就在遛一遛吧!
然而,依然是會閃一下!!!這這麼會!!!還是除錯一下吧,新過載onBindViewHolder方法有沒有被執行,一更程式碼,發現果然沒有被執行!
那麼,究竟是什麼鬼?去網上查了一下,有人給出了一個解決辦法:
http://stackoverflow.com/questions/32463136/recyclerview-adapter-notifyitemchanged-never-passes-payload-to-onbindviewholde
圖片描述
需要重寫這個動畫,讓永遠返回true,已達到newHolder和olderHolder是同一個,然而,這真的就是我的救命稻草嗎?

那麼,是騾子是馬,拉出來溜溜吧,然而,並不是馬!!進原始碼看一看
圖片描述
發現其實只要我們傳入的payload不為空,那麼返回的就是true?重寫有意義嗎?顯然,我過載的onBindViewHolder方法並沒有執行的原因顯然不是這個。
那麼,到底,到底問題出在何處?會不會是XrecyclerView的問題?根據呼叫棧,我看到第一個onBindViewHolder被執行了,往上面跟,發現XrecyclerView的實現果然存在問題!

圖片描述
如圖,作者僅僅只實現了,不帶payload的方法,最後adapter呼叫的只有不帶paylaod的方法!所以,重寫一個吧!

圖片描述
最後!終於達到了想要的效果了,經過這次爬坑,選擇一個開源的框架真滴是需要慎重再慎重。

總結

實際上RecyclerView做區域性重新整理是非常容易的,其實就是使用好帶payload引數的這個notifyItemRangeChanged方法,以及override帶payload的這個onBindViewHolder方法,在onBindViewHolder中去重新整理你想更新的控制元件即可,並非是網上傳聞的那些原因,當然此處爬坑時間之長,也可能更選用開源控制元件不當有關,所以,選擇開源控制元件,要謹慎再謹慎!


針對手遊的效能優化,騰訊WeTest平臺的Cube工具提供了基本所有相關指標的檢測,為手遊進行最高效和準確的測試服務,不斷改善玩家的體驗。目前功能還在免費開放中。
點選地址:http://wetest.qq.com/cube立即體驗!