优雅地实现一个高效、异步数据实时刷新的列表
今日科技快訊
2月11日消息,據(jù)CNBC報(bào)道,當(dāng)特斯拉公司于2019年1月宣布第二輪裁員以控制成本時(shí),一個(gè)關(guān)鍵部門受到的打擊尤為沉重。兩名被裁汰的員工表示,負(fù)責(zé)向北美地區(qū)客戶交付Model 3電動(dòng)汽車的部門被裁員一半以上。
作者簡介
本篇來自?SilenceDut?的投稿文章,和大家分享了如何優(yōu)雅地實(shí)現(xiàn)一個(gè)高效、異步數(shù)據(jù)實(shí)時(shí)刷新的列表,希望對(duì)大家有所幫助!
SilenceDut?的博客地址:
http://www.silencedut.com/
前言
Android 的業(yè)務(wù)開發(fā)中。列表需求很常見也很重要的部分,列表承載的信息多,涉及的的協(xié)議多,布局也多,尤其一些復(fù)雜的列表,不管是用 ListView 還是 RecyclerView,使用不當(dāng)會(huì)帶來很多的性能問題和后期的維護(hù)問題,形成一套規(guī)范的,高性能的列表開發(fā)模式很有必要。
案例分析
用一些案例說明一下吧(只是用一些 App 里的截圖來做類比,并不知其協(xié)議類型和實(shí)現(xiàn)方式)
類似的列表不容易解決的主要在兩個(gè)方面:
先不管列表里每個(gè) Item 的具體 UI,首先列表是可通過下拉刷新和廣播通知變化,數(shù)據(jù)應(yīng)該也只能全量下發(fā),更新頻率可能特別高,列表的長度也可能很長比如幾百條(一些聊天列表或者在線用戶列表可能存在數(shù)據(jù)量更大的情況),如果過高頻率的刷新很容易造成頁面卡頓。
從 UI 上看有很多特征,昵稱、頭像、等級(jí)、各種特權(quán)等等,而且大部分情況一條協(xié)議是無法包含所有信息,可能是很多個(gè)版本需求的迭代,涉及到多個(gè)協(xié)議,比如我們項(xiàng)目中這種情況大部分是只返回一些 uid 列表,一般先將基本的數(shù)據(jù)設(shè)置到 adapter 里顯示,同時(shí)根據(jù) uid 去查詢相應(yīng)的各個(gè)對(duì)應(yīng)的協(xié)議,異步返回結(jié)果,然后更新對(duì)應(yīng)的Item,因?yàn)槎际钱惒降臄?shù)據(jù)很難先拼接好數(shù)據(jù)再更新列表。還有些情況是 Item 中的一些數(shù)據(jù)可能會(huì)根據(jù)通知出現(xiàn)變化,如下圖常見的聊天列表在線狀態(tài),最新的聊天內(nèi)容,時(shí)間,未讀消息數(shù)等都需要根據(jù)通知更新數(shù)據(jù),如果 item 中可變的數(shù)據(jù)太多,更新的代碼寫起來會(huì)很繁瑣。
列表的性能問題
使用過頁面卡頓工具?systrace?分析頁面卡頓或者超時(shí)的應(yīng)該有一定的經(jīng)驗(yàn)就是如果頁面存在比較復(fù)雜的列表,在一些低端機(jī),有的甚至配置較好的手機(jī)上會(huì)出現(xiàn)卡頓情況,及時(shí)感覺不到卡頓,用?systrace?應(yīng)該也能看到相對(duì)其他 View 比較多的掉幀(也可稱為 Jank),谷歌根據(jù)比較多的一些 app 的數(shù)據(jù)也有類似的結(jié)論,列表的使用不當(dāng)是很多卡頓的來源
systrace?地址:
https://developer.android.com/studio/command-line/systrace
列表容易造成卡頓主要原因是相對(duì)其他 View,列表承載的內(nèi)容多,更新又比較頻繁,而且還有holder的的重建復(fù)用以及無效刷新等帶來的很多的子 Item View 的 UI 刷新,列表變化頻繁(如刪除、移動(dòng)、新增等),動(dòng)畫會(huì)帶來很大的UI性能消耗,根據(jù)原因主要從下面幾個(gè)個(gè)方面來提高列表的性能:
即使調(diào)用再多次 notifyData,列表內(nèi)容不變化的時(shí)候不刷新 UI,內(nèi)容變化的時(shí)候只刷新需要 UI 更新的 Item
列表內(nèi)容相關(guān)的異步數(shù)據(jù)或者通知需要更新列表時(shí)高效更新
根據(jù)具體情形,可以禁用列表的動(dòng)畫。
不易用的DiffUtill
DiffUtil 是 support-v7:24.2.0 中的新工具類,它用來比較兩個(gè)數(shù)據(jù)集,尋找出舊數(shù)據(jù)集-》新數(shù)據(jù)集的最小變化量。并不是一個(gè)新的工具,這里如果只是介紹如何使用 DiffUtil也沒任何意義。DiffUtil 雖然提供很久,能高性能的刷新列表,但是其使用情況上來看,可能并不理想,主要原因是:非常不易使用
寫起來麻煩:?使用時(shí)需要實(shí)現(xiàn) DiffCallBack 抽象類,需要實(shí)現(xiàn)至少四個(gè)方法這樣即使一個(gè)很簡單的列表也要寫上很多的代碼,如果列表里有多種 Type 的 Holder,寫起來就更加的臃腫耦合,
????public?Callback()?{
????}
????public?abstract?int?getOldListSize();
????public?abstract?int?getNewListSize();
????public?abstract?boolean?areItemsTheSame(int?var1,?int?var2);
????public?abstract?boolean?areContentsTheSame(int?var1,?int?var2);
????
????public?Object?getChangePayload(int?oldItemPosition,?int?newItemPosition)?{
????????return?null;
????}
}
容易崩潰:?DiffCallBack計(jì)算數(shù)據(jù)差量時(shí)需要放到異步線程,稍有不慎容易崩潰,
java.lang.IndexOutOfBoundsException,Inconsistency?detected.?Invalid?view?holder?adapter?positionViewHolder{65752ee?position=2?id=-1,?oldPos=2,?pLpos:2?scrap?[attachedScrap],java.lang.IndexOutOfBoundsException?Inconsistency?detected.?Invalid?item?position?16(offset:16).state:64,?
java.lang.IllegalArgumentException:?Scrapped?or?attached?views?may?not?be?recycled.?isScrap:false?isAttached:true
類似上面的崩潰相信使用過 DiffUtil 應(yīng)該都不陌生,根本原因是列表的數(shù)據(jù)變化的時(shí)候沒有立刻調(diào)用 adapter 刷新列表,而 DiffUtil 的計(jì)算需要放在異步線程來處理,需要操作數(shù)據(jù)和展示數(shù)據(jù)的在不用的線程,同步比較難控制,尤其是在列表長度變化的時(shí)候又更新比較頻繁的時(shí)候。雖然提供 AsyncListDiffer 的幫助類,但并不能減少這些崩潰發(fā)生的概率,而且即使知道大概的原因,這些崩潰還是很難避免。
不易增、刪、更新的列表
以更新列表為例:
類似需要異步請(qǐng)求數(shù)據(jù)的
類似通知更新數(shù)據(jù)的:
實(shí)現(xiàn)更新的方式可能有很多種方式,但需要注意:
不要在 Holder 里監(jiān)聽數(shù)據(jù)變化,不管是類似 EventBus 的廣播還是 LiveData,雖然如果項(xiàng)目里用到LiveData,可能在holder里通過livedate.observer(context,Observer) 很方便監(jiān)聽回調(diào),但是因?yàn)?Holder 的沒有明顯的生命周期,可能會(huì)頻繁被復(fù)用以及 Holder 的回收不可見等狀態(tài)不可控,如果是使用 LiveData,導(dǎo)致被頻繁綁定 observer,或者出現(xiàn)內(nèi)存泄漏等各種難以定位的問題。下面是 Google 關(guān)于列表 View 的使用建議
* When the async code is done, you should update the data, not the views.
* After updating the data, tell the adapter that the data changed.
* The RecyclerView gets note of this and re-renders your view.
* When working with recycling views (ListView or RecyclerView),
* you cannot know what item a view is representing. In your case,
* that view gets recycled before the async work is done and
* ?is assigned to a different item of your data.
* So never modify the view. Always modify the data and notify the adapter.
* bindView should be the place where you treat these cases.
簡單來說就是異步數(shù)據(jù)結(jié)果回來不應(yīng)該在 Holder 里直接改變 view 的狀態(tài),而是應(yīng)該改變數(shù)據(jù),然后通過 adapter 來改變 View。?主要原因還是上面說的 Holder 創(chuàng)建與銷毀,可見不可見等狀態(tài)很難控制。
不在 Holder 里更新就只能在外部更新,但如果使用了 DiffUtil,外部更新數(shù)據(jù)不容易實(shí)現(xiàn)。首先異步數(shù)據(jù)獲取到后或者廣播通知列表中的數(shù)據(jù)需要變化時(shí),找到需要的變更項(xiàng)更改數(shù)據(jù),類型下面的實(shí)現(xiàn)
val?allDatas?=?...//廣播通知變化,或者異步請(qǐng)求得到新居后更新列表中的數(shù)據(jù)
fun?onDataChanged(data:T)?{
val?updateItemIdex?=?findIndex(object.dataFeture)
allDatas.set(updateItemIdex,data)
or?//在全部數(shù)據(jù)中找到需要變更的數(shù)據(jù),更改數(shù)據(jù)中的某些值。這種更常見
val?needChangeData?=?findData(object.dataFeture)
needChangeData.info?=?data.info
}
fun?findData(dataFeture?:?Long):T?{
return?allDatas.find...
}
//在全部數(shù)據(jù)中找到需要變更的數(shù)據(jù)位置Index,替換數(shù)據(jù)
fun?findIndex(dataFeture?:?Long):Index?{
return?allDatas.find...
}
把列表數(shù)據(jù)更新后,需要讓 UI 的 Item 也同步更新
一種是局部刷新:
直接刷新單個(gè) Item 很容易出現(xiàn)不同線程同時(shí)處理數(shù)據(jù)帶來的崩潰問題等,再具體點(diǎn)這種情況是此時(shí)有類似 mAdapter.setDatas(mDatas) 刷新全量列表的行為,而此時(shí)的新的列表的長度和原來的不同,就有可能出現(xiàn)上述的崩潰。全量和局部可能都是基于通知或異步數(shù)據(jù)的結(jié)果所以很難控制先后順序。
還有一種是調(diào)用全量更新:
DiffUtil.DiffResult?diffResult?=?DiffUtil.calculateDiff(new?DiffCallBack(mDatas,?newDatas),?true);diffResult.dispatchUpdatesTo(mAdapter);
mDatas?=?newDatas;
mAdapter.setDatas(mDatas);
使用了 DiffUtil 的原因,可能會(huì)覺得不會(huì)刷新所有的 UI,這樣性能會(huì)提高。但這樣使用會(huì)出現(xiàn)新的問題,這種方式有個(gè)很嚴(yán)重的問題就是每次都要進(jìn)行 DiffCallBack 的差分運(yùn)算,雖然可以異步線程里處理,但是數(shù)據(jù)量較大,異步數(shù)據(jù)較多,更新頻繁的時(shí)候會(huì)導(dǎo)致cpu被大量占用,從而帶來更嚴(yán)重的界面卡頓問題。
還有很麻煩的地方就是一個(gè)異步結(jié)果返回更改單個(gè) Item 里的數(shù)據(jù)時(shí),這時(shí)很有可能你看不到列表的更新。StackOverflow 有類似的問題:Update single item in RecyclerView with DiffUtil。因?yàn)槊看胃碌臅r(shí)候你需要 new 一個(gè)新的對(duì)象,然后將不需要改變的內(nèi)容復(fù)制,需要改變的進(jìn)行賦值。而不是像上面那樣找到原來的數(shù)據(jù)進(jìn)行更改局部,因?yàn)樵瓟?shù)據(jù)對(duì)象已經(jīng)在源數(shù)據(jù)列表里,雖然創(chuàng)建新的列表,但在更新單個(gè)對(duì)象的時(shí)候因?yàn)槭峭粋€(gè)對(duì)象所以舊的數(shù)據(jù)列表肯定同步更新,導(dǎo)致做差分對(duì)比的結(jié)果肯定是不需要更新 UI (因?yàn)槭峭粋€(gè)對(duì)象),所以只能創(chuàng)建新的對(duì)象,這對(duì)更新頻繁和每個(gè) Item 有很多異步返回?cái)?shù)據(jù)的列表來說是個(gè)很大的消耗,寫起來也會(huì)非常非常繁瑣。
同樣的,在一些頻繁插入、刪除、增加數(shù)據(jù)的列表項(xiàng)使用不當(dāng)也有容易出現(xiàn)各種各樣的問題。
diffAdapter:一種高效、高性能的方案
diffadapter就是根據(jù)實(shí)際項(xiàng)目中各種復(fù)雜的列表需求,同時(shí)為了解決 DiffUtil 使用不方便,容易出錯(cuò)而實(shí)現(xiàn)的一個(gè)高效,高性能的列表庫,侵入性低,方便接入,致力于將列表需求的開發(fā)精力用于具體的 Item Holder 上,而不用花時(shí)間在一些能通用的和業(yè)務(wù)無關(guān)的地方。使用 DiffUtil 來做最小更新,屏蔽外部調(diào)用 DiffUtil 的接口。只用實(shí)現(xiàn)簡單的數(shù)據(jù)接口和展示數(shù)據(jù)的 Holder,不用自己去實(shí)現(xiàn) Adapter 來管理數(shù)據(jù)和 Holder 之間的關(guān)系,不用考慮 DiffUtil 的實(shí)現(xiàn)細(xì)節(jié),就能快速的開發(fā)出一個(gè)高性能的復(fù)雜列表需求。
先看下 demo 的效果,圖像 url,名稱,價(jià)格都是異步或者通知變化的數(shù)據(jù)。
進(jìn)行隨機(jī)的全量數(shù)顯,局部刷新,插入,刪除等操作。
diffadapter 地址:
https://github.com/SilenceDut/diffadapter
Feature
無需自己實(shí)現(xiàn) Adapter,簡單配置就可實(shí)現(xiàn)沒有各種 if-else 判斷類型的多 Type 視圖列表
使用 DiffUtil 來找出最小需要更新的 Item 集合,使用者無需做任何 DiffUtil 的配置即可實(shí)現(xiàn)高效的列表
提供方便,穩(wěn)定的更新、刪、插入、查詢方法,適用于各種非常頻繁,復(fù)雜的場景(如因?yàn)楫惒交蛲ㄖ脑蛲瑫r(shí)出現(xiàn)插入,刪除,全量設(shè)置的情況)
更友好方便的異步數(shù)據(jù)更新方案
基本用法
Step1:繼承BaseMutableData,主要實(shí)現(xiàn)areUISame(newData: AnyViewData)?和?uniqueItemFeature()
????companion?object?{
?????????//數(shù)據(jù)展示的layout,也是和Holder一一對(duì)應(yīng)的唯一特征
?????????const?val?VIEW_ID?=?R.layout.holder_skins
????}
????override?fun?getItemViewId():?Int?{
????????return?VIEW_ID
????}
????override?fun?areUISame(newData:?AnyViewData):?Boolean?{
????????//?判斷新舊數(shù)據(jù)是否展示相同的UI,如果返回True,則表示UI不需要改變,不會(huì)updateItem
????????return?this.any?==?newData.any
????}
????override?fun?uniqueItemFeature():?Any?{
????????//?返回可以標(biāo)識(shí)這個(gè)Item的特征,比如uid,id等,用來做UI差分已經(jīng)可以動(dòng)態(tài)
????????return?this.id
????}
}
Step 2:繼承 BaseDiffViewHolder<T extends BaseMutableData>,泛型類型傳入上面定義的 AnyViewData
????override?fun?getItemViewId():?Int?{
????????return?AnyViewData.VIEW_ID
????}
????override?fun?updateItem(data:?AnyViewData,?position:?Int)?{
????????根據(jù)AnyViewData.VIEW_ID對(duì)應(yīng)的layout來更新Item
????????Log.d(TAG,"updateItem?$data")
????}
}
Step 3:注冊(cè),顯示到界面
val?diffAdapter?=?DiffAdapter(this)//注冊(cè)類型,不分先后順序
diffAdapter.registerHolder(AnyHolder::class.java,?AnyViewData.VIEW_ID)
????????diffAdapter.registerHolder(AnyHolder2::class.java,?AnyViewData2.VIEW_ID)
diffAdapter.registerHolder(AnyHolder3::class.java,?AnyViewData3.VIEW_ID)
val?linearLayoutManager?=?LinearLayoutManager(this)
recyclerView.layoutManager?=?linearLayoutManager
recyclerView.adapter?=?diffAdapter
//監(jiān)聽數(shù)據(jù)變化
fun?onDatached(datas?:?List<BaseMutableData<*>>)?{
????diffAdapter.datas?=?adapterListData
}
只需要上面幾步,就可以完成如類似下圖的多 type 列表,其中數(shù)據(jù)源里的每個(gè)BaseMutableData的getItemViewId() 決定著用哪個(gè) Holder 展示 UI。
(以上均用 kotlin 實(shí)現(xiàn),Java 使用不受任何限制)
增加、刪除、插入、更新
public?void?deleteData(BaseMutableData?data)
public?void?deleteData(int?startPosition,?int?size)
void?insertData(int?startPosition?,List<??extends?BaseMutableData>?datas)
public?void?updateData(BaseMutableData?newData)
上述接口在調(diào)用的時(shí)機(jī),頻率都很復(fù)雜的場景下也不會(huì)引起崩潰。
使用 updateData(BaseMutableData newData)時(shí),newData 可以是新 new 的對(duì)象,也可以是修改后的原對(duì)象,不會(huì)出現(xiàn)使用 DiffUtil 更新單個(gè)數(shù)據(jù)無效的問題。
高階用法
基本用法中Data和Holder綁定的模式并沒什么特殊之處,早在兩年前的項(xiàng)目KnowWeather?就已經(jīng)用上這種思想,現(xiàn)在只是結(jié)合 DiffUtil 以及其他的疑難問題解決方案將其開源,diffadapter 最核心的地方在于高性能和異步獲取數(shù)據(jù)或者通知數(shù)據(jù)變化時(shí)列表的更新上。
多數(shù)據(jù)源異步更新
以一個(gè)類似的 Item 為例,這里認(rèn)為服務(wù)器返回的數(shù)據(jù)列表只包含uid,也就是 List<Long> uids,個(gè)人資料,等級(jí),貴族等都屬于不同的協(xié)議。下面展示的是異步獲取個(gè)人資料展示的頭像和昵稱的情況,其他的可以類比。
Step 1:定義 ViewData
data?class?ItemViewData(var?uid:Long,?var?userInfo:?UserInfo?,?var?anyOtherData:?Any?...)?:?BaseMutableData<ItemViewData>()?{????companion?object?{
????????const?val?VIEW_ID?=?R.layout....
????}
????override?fun?getItemViewId():?Int?{
????????return?VIEW_ID
????}
????override?fun?areUISame(newData:?UserInfo):?Boolean?{
????????return?this.userInfo?.portrait?==?newData.userInfo?.portrait?&&?this.userInfo?.nickName?==?newData.userInfo?.nickName?&&?this.anyOtherData?==?newData.anyOtherData
????}
????override?fun?uniqueItemFeature():?Any?{
???????return?this.uid
????}
}
數(shù)據(jù)類 ItemViewData 包含所有需要顯示到 Item 上的信息,這里只處理和個(gè)人資料相關(guān)的數(shù)據(jù),anyOtherData: Any ...表示 Item 所需的其他數(shù)據(jù)內(nèi)容
BaseMutableData里有個(gè)默認(rèn)的方法allMatchFeatures(@NonNull Set<Object> allMatchFeatures),不需要顯示調(diào)用,這里當(dāng)外部有異步數(shù)據(jù)變化時(shí),提供當(dāng)前BaseMutableData 用來匹配變化的異步數(shù)據(jù)的對(duì)象
public?void?appendMatchFeature(@NonNull?Set<Object>?allMatchFeatures)?{allMatchFeatures.add(uniqueItemFeature());
}
默認(rèn)添加了 uniqueItemFeature(),allMatchFeatures 是個(gè) Set,可以重寫方法添加多個(gè)用來匹配的特征。
Step 2:定義 View Holder
同基本用法
Step 3:監(jiān)聽數(shù)據(jù)變化,更新列表
//用于監(jiān)聽請(qǐng)求的異步數(shù)據(jù),userInfoData變化時(shí)與此相關(guān)的數(shù)據(jù)private?val?userInfoData?=?MutableLiveData<UserInfo>()
//在adapter里監(jiān)聽數(shù)據(jù)變化
diffAdapter.addUpdateMediator(userInfoData,?object?:?UpdateFunction<UserInfo,?ItemViewData>?{
????override?fun?providerMatchFeature(input:?UserInfo):?Any?{
????????return?input.uid
????}
????override?fun?applyChange(input:?UserInfo,?originalData:?ItemViewData):?ItemViewData?{
???????return?originalData.userInfo?=?input
????}
})
//?任何通知數(shù)據(jù)獲取到的通知
fun?asyncDataFetch(userInfo?:?UserInfo)?{
????userInfoData.value?=?userInfo
}
這樣當(dāng) asyncDataFetch 接收到數(shù)據(jù)變化的通知的時(shí)候,改變 userInfoData 的值,adapter 里對(duì)應(yīng)的 Item 就會(huì)更新。其中找到 adapter 中需要更新的 Item 是關(guān)鍵部分,主要由實(shí)現(xiàn) UpdateFunction 來完成,實(shí)現(xiàn) UpdateFunction 也很簡單。
????/**
?????*?匹配所有數(shù)據(jù),及返回類型為R的所有數(shù)據(jù)
?????*/
????Object?MATCH_ALL?=?new?Object();
????/**
?????*?提供一個(gè)特征,用來查找列表數(shù)據(jù)中和此特征相同的數(shù)據(jù)
?????*?@param?input?用來提供查找數(shù)據(jù)和最終改變列表的數(shù)據(jù)
?????*?@return?用來查找列表中的數(shù)據(jù)的特征項(xiàng)
?????*/
????Object?providerMatchFeature(@NonNull?I?input);
????/**
?????*?匹配到對(duì)應(yīng)的數(shù)據(jù),如果符合條件的數(shù)據(jù)有很多個(gè),可能會(huì)被回調(diào)多次
?????*?@param?input?是數(shù)據(jù)改變的部分?jǐn)?shù)據(jù)源
?????*?@param?originalData?需要改變的數(shù)據(jù)項(xiàng)
?????*?@return?改變后的數(shù)據(jù)項(xiàng)
?????*/
????R?applyChange(@NonNull?I?input,@NonNull?R?originalData);
}
UpdateFunction 用來提供異步數(shù)據(jù)獲取到后數(shù)據(jù)用來和列表中的數(shù)據(jù)匹配的規(guī)則和根據(jù)規(guī)則找到需要更改的對(duì)象后如果改變?cè)瓕?duì)象,剩下的更新都由 diffadapter 來處理。如果符合條件的數(shù)據(jù)有很多個(gè),applyChange(@NonNull I input,@NonNull R originalData)會(huì)被回調(diào)多次。如下時(shí):
????return?UpdateFunction.MATCH_ALL
}
applyChange 回調(diào)的次數(shù)就和列表中的數(shù)據(jù)量一樣多。
如果同一種匹配規(guī)則providerMatchFeature對(duì)應(yīng)多種Holder類型,UpdateFunction<I,R>的返回?cái)?shù)據(jù)類型R就可以直接設(shè)為基類的 BaseMutableData,然后再 applyChange 里在具體根據(jù)類型來處理不同的 UI。
最高效的Item局部更新方式 —— payload
DiffUtil 能讓一個(gè)列表中只更新部分變化的 Item,payload 能讓同一個(gè) Item 只更新需要變化的 View,這種方式非常適合同一個(gè) Item 有多個(gè)異步數(shù)據(jù)源的,同時(shí)又對(duì)性能有更高要求的列表。
Step 1:重寫 BaseMutableData 的 appendDiffPayload
data?class?ItemViewData(var?uid:Long,?var?userInfo:?UserInfo?,?var?anyOtherData:?Any?...)?:?BaseMutableData<ItemViewData>()?{????companion?object?{
????????const?val?KEY_BASE_INFO?=?"KEY_BASE_INFO"
????????const?val?KEY_ANY?=?"KEY_ANY"
????}
????...
????/**
?????*?最高效的更新方式,如果不是頻繁更新的可以不實(shí)現(xiàn)這個(gè)方法
?????*/
????override?fun?appendDiffPayload(newData:?ItemViewData,?diffPayloadBundle:?Bundle)?{
????????super.appendDiffPayload(newData,?diffPayloadBundle)
????????if(this.userInfo!=?newData.userInfo)?{
????????????diffPayloadBundle.putString(KEY_BASE_INFO,?KEY_BASE_INFO)
????????}
????????if(this.anyData?!=?newData.anyData)?{
????????????diffPayloadBundle.putString(KEY_ANY,?KEY_ANY)
????????}
????????...
????}
}
默認(rèn)用 Bundle 存取變化,無需存具體的數(shù)據(jù),只需類似設(shè)置標(biāo)志位,表明 Item 的哪部分?jǐn)?shù)據(jù)發(fā)生了變化。
Step 2 :需要重寫 BaseDiffViewHolder 里的 updatePartWithPayload
class?ItemViewHolder(itemViewRoot:?View,?recyclerAdapter:?DiffAdapter):?BaseDiffViewHolder<ItemViewData>(?itemViewRoot,??recyclerAdapter){????override?fun?updatePartWithPayload(data:?ItemViewData,?payload:?Bundle,?position:?Int)?{
????if(payload.getString(ItemViewData.KEY_BASE_INFO)!=null)?{
????????updateBaseInfo(data)
????}
????if(payload.getString(ItemViewData.KEY_ANY)!=null)?{
????????updateAnyView(data)
????}
}
根據(jù)變化的標(biāo)志位,更新 Item 中需要變化部分的 View
總結(jié)
一些探討:
為什么沒有提供類似 onItemClickLisener 用來處理點(diǎn)擊事件的接口
不是因?yàn)椴缓脤?shí)現(xiàn),其實(shí)現(xiàn)實(shí)起來非常簡單。首先嘗試去理解為什么RecyclerView.Adapter 沒有提供像 listview 那樣的點(diǎn)擊事件的 listener,我的理解是大而全的公用點(diǎn)擊監(jiān)聽不是一個(gè)好的設(shè)計(jì)方式,尤其對(duì)于多類型的view來說,因?yàn)辄c(diǎn)擊的是不同的 holder,要在回調(diào)里根據(jù)類型來處理不同的邏輯,少不了各種 if-else 的代碼塊,不同 holder 相關(guān)的數(shù)據(jù),邏輯耦合到一塊,試想如果有四五種類型,處理統(tǒng)一點(diǎn)擊回調(diào)的地方是多大的一塊代碼,后期的維護(hù)又是一個(gè)問題。我認(rèn)為好的方式應(yīng)該是在各自的holder的構(gòu)造函數(shù)里來各自處理,每個(gè)holder 都有自己的數(shù)據(jù)和類型,很好的隔離開不同類型數(shù)據(jù)的耦合,每個(gè) holder 各司其職:顯示數(shù)據(jù),監(jiān)聽點(diǎn)擊,維護(hù)方便。
為什么沒有下拉刷新、加載更多、動(dòng)畫、分割線等更多的功能
首先 diffadapter 主要就是為了提供高性能刷新,異步數(shù)據(jù)更新,高效的配置多類型列表的功能,這也是絕大多數(shù)列表最常見的功能,像上面說的那些功能以及onItemClickLisener 都是一些額外的添加項(xiàng),不想做一個(gè)為了看起來更多功能但沒有任何難度,堆積代碼的開源庫,不想為了看起來大而全來吸引別人使用。就是職責(zé)很單一,目的很明確,diffadapter 侵入性很低,不影響任何其他功能的引入,包括不限于上面提到的那些。而且上面提到的那些都有很多很好的開源庫,你可以根據(jù)任何自己的需要來定制。
更詳細(xì),多樣的使用方式和細(xì)節(jié)見?diffadapter demo,有詳細(xì)的 demo 和使用說明,demo 用 kotlin 實(shí)現(xiàn),使用了 mvvm 和模塊化的框架方式。
這種方式也是目前能想到的比較好的異步數(shù)據(jù)更新列表的方式,非常歡迎一起探討更多的實(shí)現(xiàn)方式。
總結(jié)
以上是生活随笔為你收集整理的优雅地实现一个高效、异步数据实时刷新的列表的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ubuntu下的3D桌面.compiz
- 下一篇: 24微信小程序开发2