DiffUtil:高效处理Android列表数据变化



/   今日科技快讯   /

近日,在“一带一路全球行”国际段出发典礼上,高德地图公布了旗下“世界地图”产物正式上线:应用斗极卫星导航系统(以下简称“斗极系统”)的全球定位能力,实现境外路线规划和导航办事;还能够基于行程定位记录,点亮用户沿路过过的国度和城市。

/   作者简介   /

本篇文章转自麦客奥德彪的博客,文章首要分享了若何使用Android中的DiffUtil对象类,相信会对人人有所匡助!

原文地址:
https://juejin.cn/post/7206391499264426041

/   媒介   /

DiffUtil 是 Android 顶用于较量两个列表之间差别的实用对象类。它能够优化 RecyclerView 的刷新把持,仅刷新需要更新的部门,从而提高机能并削减不需要的把持。

本篇博客将从简洁到高级,介绍使用 DiffUtil 的根基流程以及一些高级用法,匡助斥地者更好地使用 DiffUtil。

/   正文   /

什么是 DiffUtil?

DiffUtil 是一个用于较量两个列表之间差别的实用对象类。它经由对照两个列表的元素,找出它们之间的差别,并生成更新把持的列表,以便进行最小化的更新把持。

为什么需要 DiffUtil?

这两天在做IM模块,写IM会话列表需求时发现了一个需要的优化点。

聊天列表页面用于显露所有的聊天记录。在这个页面中,每一条聊天记录都包罗对方的头像、昵称、新闻内容和时间戳等信息。为了提高用户体验,我们进展聊天列表能够实现以下功能:

  • 支撑实时刷新:当有新的聊天记录达到时,列表能够立刻进行更新,而不需要手动刷新页面;
  • 支撑快速滑动:用户能够快速滑动列表,查察更多的聊天记录;
  • 支撑搜刮:用户能够经由搜刮功能查找特定的聊天记录。

实现以上功能,需要聊天列表可以快速响应数据集的转变,而且可以高效地更新界面。而在大多数情形下,聊天列表的数据集很或者是动态转变的,是以我们需要一种高效的算法来对照旧数据集和新数据集之间的差别,而且只更新发生了转变的列表项。这时,DiffUtil 就变得非常主要了。

在聊天列表的需求场景中,若是我们不使用 DiffUtil,每次数据集发生转变时,都需要挪用 RecyclerView.Adapter 中的 notifyDataSetChanged() 方式来刷新整个列表。如许做固然简洁,然则会导致列表的滑动位置和状况悉数被重置,用户体验非常不友好。而若是使用 DiffUtil,我们能够仅仅更新数据集中发生转变的那些列表项,如许能够极大地提高 RecyclerView 的机能,而且连结列表的滑动位置和状况不变,从而提拔用户体验。

当然这只是一个需求场景,我们能够懂得为:

在使用 RecyclerView 时,若是数据集发生转变,我们平日需要挪用 notifyDataSetChanged() 或许 notifyItemRangeChanged() 等方式进行刷新把持。然则如许做会导致整个列表都被刷新,即使只有一部门数据发生了转变,也会从新绘制整个列表,造成机能虚耗。DiffUtil 能够匡助我们只更新发生转变的部门,从而削减不需要的刷新把持,提高机能。当我们面临这个问题时能够考虑DiffUtil。

DiffUtil 的用法

DiffUtil 的使用步伐如下:

  • 建立两个列表,离别透露旧数据集和新数据集。
  • 建立一个 DiffUtil.Callback 对象,实现个中的方式,用于对照旧数据集和新数据集的元素,并较量它们之间的差别。
  • 挪用 DiffUtil.calculateDiff() 方式,传入上一步建立的 DiffUtil.Callback 对象,较量差别,并返回更新把持的列表。
  • 使用返回的更新把持列表,更新 RecyclerView 的数据集。

建立 DiffUtil.Callback

DiffUtil.Callback 是 DiffUtil 的焦点类,用于对照旧数据集和新数据集的元素,并较量它们之间的差别。它包含四个抽象方式,需要我们自行实现。

  • public abstract int getOldListSize():获取旧数据集的巨细。
  • public abstract int getNewListSize():获取新数据集的巨细。
  • public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition):判断旧数据集中的某个元素和新数据集中的某个元素是否代表统一个实体。
  • public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition):判断旧数据集中的某个元素和新数据集中的某个元素的内容是否沟通。

除此之外,DiffUtil.Callback 还有两个可选的方式,能够用于进一步优化较量过程:

  • public Object getChangePayload(int oldItemPosition, int newItemPosition)`:获取旧数据集中的某个元素和新数据集中的某个元素之间的差别信息。若是这两个元素沟通,然则内容发生改变,能够经由这个方式获取它们之间的差别信息,从而只更新需要改变的部门,削减不需要的更新把持。
  • public boolean getMoveDetectionFlag():设置是否开启移动把持的检测。若是设置为 true,DiffUtil 会检测数据集中元素的移动把持,并生成移动把持的更新列表。然则,开启移动把持的检测会增加较量量,或者会影响机能。

使用 DiffUtil

DiffUtil 的另一个优点就是和Adapter高度解耦,在原油的Adapter不动的情形下也能完成需求,下面是一个完整的例子,注重纷歧定要动Adapter的代码啊。

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder{

    private List<String> mOldList;
    private List<String> mNewList;

    // 组织方式,初始化数据集
    public MyAdapter(List<String> oldList, List<String> newList) {
        mOldList = oldList;
        mNewList = newList;
    }

    // ViewHolder,用于缓存列表项的视图
    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView mTextView;

        public ViewHolder(View itemView) {
            super(itemView);
            mTextView = itemView.findViewById(R.id.text_view);
        }
    }

    // 建立 ViewHolder
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false);
        return new ViewHolder(view);
    }

    // 绑定 ViewHolder
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        String text = mNewList.get(position);
        holder.mTextView.setText(text);
    }

    // 获取数据集巨细
    @Override
    public int getItemCount() {
        return mNewList.size();
    }

    // 更新数据集
    public void updateList(List<String> newList) {
        // 较量差别
        DiffUtil.Callback callback = new MyCallback(mOldList, newList);
        DiffUtil.DiffResult result = DiffUtil.calculateDiff(callback);

        // 更新数据集
        mOldList = mNewList;
        mNewList = newList;
        result.dispatchUpdatesTo(this);
    }

    // 自界说的 DiffUtil.Callback
    private static class MyCallback extends DiffUtil.Callback {
        private List<String> mOldList;
        private List<String> mNewList;

        public MyCallback(List<String> oldList, List<String> newList) {
            mOldList = oldList;
            mNewList = newList;
        }

        @Override
        public int getOldListSize() {
            return mOldList.size();
        }

        @Override
        public int getNewListSize() {
            return mNewList.size();
        }

        @Override
        public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
            return mOldList.get(oldItemPosition).equals(mNewList.get(newItemPosition));
        }

        @Override
        public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
            return mOldList.get(oldItemPosition).equals(mNewList.get(newItemPosition));
        }
    }
}

在上述示例中,我们建立了一个自界说的 Adapter,并在个中实现了 updateList() 方式,用于更新数据集。在 updateList() 方式中,我们先使用自界说的 DiffUtil.Callback 对旧数据集和新数据集进行差别较量,然后将新数据集赋值给成员变量 mNewList,并将较量获得的差别信息经由 result.dispatchUpdatesTo(this) 方式更新到 RecyclerView 中。

在 MyCallback 中,我们需要实现 DiffUtil.Callback 中的四个方式,个中 areItemsTheSame() 和 areContentsTheSame() 方式离别用于判断两个列表项是否代表统一个对象,以及这两个对象的内容是否沟通。若是这两个方式都返回 true,那么 DiffUtil 认为这两个列表项沟通,不需要进行更新把持;若是个中随意一个方式返回 false,那么 DiffUtil 认为这两个列表项分歧,需要进行更新把持。

在这个例子中,我们使用了字符串列表作为数据集,是以能够直接使用 equals() 方式对照两个字符串是否相等。若是我们使用的是自界说对象,那么需要凭据具体情形实现 areItemsTheSame() 和 areContentsTheSame() 方式。

除了以上示例中的方式,DiffUtil 还供应了一些其他的方式和类,用于加倍高级的差别较量。例如:

public static DiffUtil.DiffResult calculateDiff(DiffUtil.Callback callback, boolean detectMoves):同 calculateDiff() 方式,但能够指定是否开启移动把持的检测。
public static  List diff(List oldList, List newList, ItemCallback callback):一个加倍天真的差别较量方式,能够自界说对象的对照体式,而且支撑异步较量。
public static class AsyncDiffResult extends AsyncTask<Void, Void, DiffUtil.DiffResult>:异步较量差别的对象类,能够在子线程中进行差别较量,并在主线程中更新 UI。

支撑异步较量

在处理大量数据时,DiffUtil 的差别较量或者会对照耗时,从而影响应用的响应速度。为了避免这种情形,我们能够将差别较量放在一个异步义务中进行。DiffUtil 供应了 AsyncDiffResult 类来支撑异步较量,具体使用方式如下:

class ChatListAdapter : RecyclerView.Adapter<ChatListAdapter.ViewHolder>() {
    private var messageList: List<ChatMessage> = emptyList()

    fun submitListAsync(newList: List<ChatMessage>) {
        val callback = ChatMessageDiffCallback(messageList, newList)
        val asyncTask = object : AsyncTask<VoidVoid, DiffUtil.DiffResult>() {
            override fun doInBackground(vararg voids: Void): DiffUtil.DiffResult {
                return DiffUtil.calculateDiff(callback)
            }

            override fun onPostExecute(diffResult: DiffUtil.DiffResult) {
                messageList = newList
                diffResult.dispatchUpdatesTo(this@ChatListAdapter)
            }
        }
        asyncTask.execute()
    }

    // ...
}

使用 AsyncTask 来异步较量差别,并在较量完成后更新数据集。若是我们的数据集对照大,能够使用这种方式来避免壅塞主线程。注重,在使用异步较量时,我们不克直接挪用 notifyDataSetChanged() 方式来刷新列表,而是需要经由 DiffUtil.DiffResult 的 dispatchUpdatesTo() 方式来更新列表。

DiffUtil 虽好可不要贪酒哦

尽量使用弗成变数据对象。DiffUtil 是经由对照两个数据对象的引用来判断它们是否沟通的,是以若是我们在列表中使用可变数据对象,那么很轻易显现引用沟通但内容分歧的情形,从而导致列表的更新显现问题。所以,在使用 DiffUtil 时,最好使用弗成变数据对象,或许在可变数据对象中包管引用的独一性。

注重数据对象的 equals() 方式的实现。若是我们使用的是自界说的数据对象,那么需要确保它的 equals() 方式的实现是准确的,不然会导致 DiffUtil 较量的禁绝确。具体来说,equals() 方式应该准确地对照数据对象的所有字段。

尽量避免在列表中使用动态数据。DiffUtil 的差别较量是基于静态数据的,若是我们在列表中使用了动态数据,好比时间戳、随机数等,那么或者会导致每次较量的究竟分歧,从而影响列表的更新结果。所以,若是需要在列表中使用动态数据,能够将其较量究竟缓存下来,以避免影响列表的更新。

尽量避免对数据进行频仍的更新。固然 DiffUtil 能够非常高效地较量出数据集的差别,然则若是我们对数据进行频仍的更新,那么就会导致 DiffUtil 络续地进行较量,从而影响应用的机能。所以,在使用 DiffUtil 时,尽量避免对数据进行频仍的更新,能够将数据集的更新批量处理,或许使用合适的更新策略,如增量更新、局部更新等。

注重数据集的顺序。DiffUtil 的差别较量是基于数据集的顺序的,若是数据集的顺序发生了转变,那么就需要从新较量差别。所以,在使用 DiffUtil 时,需要注重数据集的顺序,尽量避免频仍地对数据集进行排序等把持。

避免在 UI 线程中进行差别较量。DiffUtil 的差别较量或者会对照耗时,若是在 UI 线程中进行较量,就会导致应用的卡顿,影响用户体验。所以,在使用 DiffUtil 时,最好将差别较量放在一个异步义务中进行,或许使用其他体式来避免壅塞 UI 线程。

总结

DiffUtil 是一个用于较量两个数据集之间差别的对象类,能够匡助我们削减不需要的更新把持,提高 RecyclerView 的机能。使用 DiffUtil 需要实现 DiffUtil.Callback 接口,并凭据具体情形实现个中的方式。除了根基的差别较量方式,DiffUtil 还供应了很多其他的方式和类,能够凭据实际需求选择使用。

介绍阅读:
我的新书,《第一行代码 第3版》已出书!
原创:写给初学者的Jetpack Compose教程,Modifier
原创:写给初学者的Jetpack Compose教程,根蒂控件和结构

迎接存眷我的公家号
进修手艺或投稿


长按上图,识别图中..即可存眷

标签:
guolin_blog
郭霖 微信号:guolin_blog 扫描二维码关注公众号
优质自媒体

小编推荐

  1. 1 发财树叶子蔫了怎么办(发财树叶子发黄干枯怎么处理)

    大家好,小丽今天来为大家解答发财树叶子蔫了怎么办以下问题,发财树叶子发黄干枯怎么处理很多人还不知道,现在让我们一起来看看吧!1、一、

  2. 2 癌症体质的人,通常有4个“特性”,希望你一个也没有

    千 百 万 环 保 超 市 会 员 共 同 关 注 !癌症,这个字眼总能让人心生惧怕,它如同沉寂潜行的死神,或者在任何人群中默默显现。然而,癌症并

  3. 3 乔布莱恩特总冠军(我想看乔布莱恩特)

    大家好,小美今天来为大家解答乔布莱恩特总冠军以下问题,我想看乔布莱恩特很多人还不知道,现在让我们一起来看看吧!1、迈克汤普森以状元的

  4. 4 夫君们笑一个男主(夫君们,笑一个)

    大家好,小伟今天来为大家解答夫君们笑一个男主以下问题,夫君们,笑一个很多人还不知道,现在让我们一起来看看吧!1、封城九宫主岚颜,从小心

  5. 5 可怜绣户侯门女独卧青灯古佛旁(可怜绣户侯门女独卧青灯古佛旁写的是谁)

    大家好,小美今天来为大家解答可怜绣户侯门女独卧青灯古佛旁以下问题,可怜绣户侯门女独卧青灯古佛旁写的是谁很多人还不知道,现在让我们一

  6. 6 人有三急是指哪三急呀(人有三急的三急指的是哪三急)

    大家好,小美今天来为大家解答人有三急是指哪三急呀以下问题,人有三急的三急指的是哪三急很多人还不知道,现在让我们一起来看看吧!1、通常

  7. 7 小学部|综合实践活动、劳动、地方课程教研|深耕细研不负春,“研”途花开溢芳菲——三学科本学期第二次联合研训

    深耕细研不负春“研”途花开溢芳菲三学科本学期第二次结合研训春之美,在于生机与勃发;教之美,在于钻研与提拔。4月18日上午,滨江区小学综

  8. 8 亚洲最帅男明星是谁(亚洲十大最帅男星)

    大家好,小美今天来为大家解答亚洲最帅男明星是谁以下问题,亚洲十大最帅男星很多人还不知道,现在让我们一起来看看吧!1、肖战能够成为亚洲

Copyright 2024 优质自媒体,让大家了解更多图文资讯!