日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

优雅地实现一个高效、异步数据实时刷新的列表

發(fā)布時(shí)間:2024/3/13 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 优雅地实现一个高效、异步数据实时刷新的列表 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.



今日科技快訊


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?abstract?static?class?Callback?{
    ????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);
    ????@Nullable
    ????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 也同步更新

    • 一種是局部刷新:

    adapter.notifyItemChanged(updateItemIdex);

    直接刷新單個(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()

    class?AnyViewData(var?id?:?Long?,var?any?:?String)?:?BaseMutableData<AnyViewData>()?{
    ????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

    class?AnyHolder(itemView:?View,?recyclerAdapter:?DiffAdapter):?BaseDiffViewHolder<AnyViewData>(?itemView,??recyclerAdapter){

    ????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?<T?extends?BaseMutableData>?void?addData(T?data)?
    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 也很簡單。

    interface?UpdateFunction<I,R?extends?BaseMutableData>?{
    ????/**
    ?????*?匹配所有數(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í):

    Object?providerMatchFeature(@NonNull?I?input)?{
    ????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)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。