recyclerview优化:DiffUtil使用过程中遇到的问题总结-kotlin中==未比较equals的问题分析

https://blog.youkuaiyun.com/fitaotao/article/details/84314043

使用DiffUtil:DiffUtil是Android Support Library中的一个工具类,可以帮助计算新旧数据集的差异并高效更新RecyclerView的数据。通过使用DiffUtil,可以避免不必要的数据刷新和界面重绘,提高列表更新的效率。

fun <T> RecyclerView.Adapter<*>.autoNotify(old: List<T>, new: List<T>, compare: (T, T) -> Boolean) {
    val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() {

        override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
            return compare(old[oldItemPosition], new[newItemPosition])
        }

        override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
            return old[oldItemPosition] == new[newItemPosition]
        }

        override fun getOldListSize() = old.size

        override fun getNewListSize() = new.size
    })

    diff.dispatchUpdatesTo(this)
}
    fun setData(list: List<MessageInfo>) {
        autoNotify(messageList, list) { old, new ->
            old.id == new.id
        }
        messageList.clear()
        messageList.addAll(list)
//        notifyItemRangeChanged(0, messageList.size)
    }

在使用的过程中发现areContentsTheSame 中的old[oldItemPosition] == new[newItemPosition]这个比较,当其中某个字段发生变化后,比较结果仍为true。期望肯定是字段如发生变化,则需要返回false,更新这个Item。

分析出现上述问题的原因:
问题1:项目代码中,在adapter使用的list和外部list是同一个数据源,这就导致在比较之前,old和new的值就已经相同了。
问题2:使用==比较。在Kotlin中,这种比较方式默认调用的是equals方法进行比较,理想状态是对每个字段进行逐一比较,但是,实际情况并非如此,具体造成这个问题的原因在下文分解。

分析问题:
问题1:
这个问题其实按照上面的描述,其实就是自己在跟自己比较,所以恒为true。所以在adapter中,给adapter中的List进行赋值的时候,要注意使用不同的对象。利用bean的clone,给list进行一次重新赋值。
代码如下:

public static class MessageInfo implements Serializable , Cloneable{
	      @Override
        public MessageInfo clone() {
            MessageInfo o = null;
            try{
                o = (MessageInfo)super.clone();
            }catch(CloneNotSupportedException e){
                e.printStackTrace();
            }
            return o;
        }
}

问题2:
这个问题就比较奇怪了,根据对kotlin的理解,==比较就是调用equals进行比较,并且写过测试代码,当字段变化时,两个对象进行比较返回的是false。

    data class Personal(val id:String, val name:String)
    @Test
    fun testEquals(){
        val personal1 = Personal("1", "dy")
        val personal2 = Personal("1", "dy")
        println("$personal1 , $personal2")
        val personal3 = Personal("1", "dy1")
        val personal4 = personal2

        assertEquals(true,  personal1 == personal2 )
        assertEquals(false,  personal1 === personal2 )
        assertEquals(false,  personal1 === personal3 )
        assertEquals(false,  personal1 == personal3 )
        assertEquals(true,  personal4 == personal1 )
        assertEquals(false,  personal4 === personal1)

    }

上述用例中,针对 kotlin 中的== === 进行了测试比较

其实这个问题的原因是项目中,虽然在调用处使用的是kotlin,但是bean文件是用Java创建的,这其实就涉及到一个java和kotlin混合使用的问题。那么问题究竟是什么呢?
我发现项目中的bean文件是java文件,而我的测试用例中,使用的是kotlin的data。
进一步验证问题,查看测试用例编译后生成的java代码:

public final class Personal {
   @NotNull
   private final String id;
   @NotNull
   private final String name;

   @NotNull
   public final String getId() {
      return this.id;
   }

   @NotNull
   public final String getName() {
      return this.name;
   }

   public Personal(@NotNull String id, @NotNull String name) {
      Intrinsics.checkNotNullParameter(id, "id");
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      this.id = id;
      this.name = name;
   }

   @NotNull
   public final String component1() {
      return this.id;
   }

   @NotNull
   public final String component2() {
      return this.name;
   }

   @NotNull
   public final Personal copy(@NotNull String id, @NotNull String name) {
      Intrinsics.checkNotNullParameter(id, "id");
      Intrinsics.checkNotNullParameter(name, "name");
      return new Personal(id, name);
   }

   // $FF: synthetic method
   public static Personal copy$default(Personal var0, String var1, String var2, int var3, Object var4) {
      if ((var3 & 1) != 0) {
         var1 = var0.id;
      }

      if ((var3 & 2) != 0) {
         var2 = var0.name;
      }

      return var0.copy(var1, var2);
   }

   @NotNull
   public String toString() {
      return "Personal(id=" + this.id + ", name=" + this.name + ")";
   }

   public int hashCode() {
      String var10000 = this.id;
      int var1 = (var10000 != null ? var10000.hashCode() : 0) * 31;
      String var10001 = this.name;
      return var1 + (var10001 != null ? var10001.hashCode() : 0);
   }

   public boolean equals(@Nullable Object var1) {
      if (this != var1) {
         if (var1 instanceof Personal) {
            Personal var2 = (Personal)var1;
            if (Intrinsics.areEqual(this.id, var2.id) && Intrinsics.areEqual(this.name, var2.name)) {
               return true;
            }
         }

         return false;
      } else {
         return true;
      }
   }
}

通过上述代码发现了核心问题,在转为java代码的时候,data会自动生成equals比较方法。而我项目中自定义的java bean没有实现equals方法,这就导致在判断调用的时候,调用的是any默认的equals方法,仅会对引用进行比较,而不会对字段进行逐一的比较。。所以解决问的方案也一目了然,自己重写一下equals方法。另外要注意需要同时重写hashcode方法。

在Java中,当我们重写 equals() 方法时,通常也需要重写 hashCode() 方法。这是因为Java的 Object 类规定,如果两个对象相等(即 equals() 方法返回 true ),那么它们的 hashCode() 方法必须返回相同的值。如果你只重写了 equals() 方法而没有重写 hashCode() 方法,那么可能会违反这个规定,导致哈希表等数据结构的行为异常。

简单省事的解决方法就是直接用kotlin的bean, 并注意使用data class

滑动冲突:
http://wed.xjx100.cn/news/231642.html
https://blog.youkuaiyun.com/xiaokangss/article/details/128002513

混淆:
http://www.taodudu.cc/news/show-1258028.html?action=onClick
https://www.ngui.cc/el/1833760.html?action=onClick

### Android RecyclerView 的替代方案或升级版本 尽管 `RecyclerView` 是目前 Android 开发中最常用的列表视图组件之一,但在某些特定场景下可能需要更高效或者功能更强的解决方案。以下是几种可以作为 `RecyclerView` 替代品或其升级方向的技术: #### 1. **Compose LazyColumn 和 LazyRow** Google 推出了 Jetpack Compose,这是一种现代化的 UI 工具包,用于构建原生界面。Jetpack Compose 提供了 `LazyColumn` 和 `LazyRow` 组件来代替传统的 `RecyclerView`。 这些组件通过声明式编程模型简化了布局逻辑,并提供了更高的性能和更好的开发体验[^3]。 ```kotlin import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.runtime.Composable @Composable fun MyLazyList(items: List<String>) { LazyColumn { items.forEach { item -> item { Text(text = item) } } } } ``` #### 2. **Epoxy by Airbnb** Airbnb 开源了一个名为 Epoxy 的库,它是一个基于 `RecyclerView` 构建的强大工具,旨在解决复杂数据绑定和动态列表的需求。Epoxy 可以显著减少样板代码并提高可维护性[^4]。 ```java // 使用 Epoxy 创建 Model 并自动更新 RecyclerView new SampleModel_() .id("unique_id") .title("Sample Title") .addTo(controller); ``` #### 3. **DiffUtil 改进版:AsyncDifferConfig** 虽然这不是完全意义上的替代方案,但可以通过改进 `RecyclerView.Adapter` 中的数据集管理方式提升效率。`AsyncDifferConfig.Builder` 结合 `ListAdapter` 能够异步计算差异,从而进一步优化用户体验[^5]。 ```java public class MyAdapter extends ListAdapter<MyItem, MyViewHolder> { public MyAdapter() { super(new AsyncDifferConfig.Builder<>(new DiffCallback()).build()); } private static final DiffUtil.ItemCallback<MyItem> DIFF_CALLBACK = new DiffUtil.ItemCallback<MyItem>() { @Override public boolean areItemsTheSame(@NonNull MyItem oldItem, @NonNull MyItem newItem) { return oldItem.getId().equals(newItem.getId()); } @Override public boolean areContentsTheSame(@NonNull MyItem oldItem, @NonNull MyItem newItem) { return oldItem.equals(newItem); } }; } ``` #### 4. **StaggeredGridLayoutManager 扩展** 如果项目需求涉及复杂的网格布局,则可以直接扩展现有的 `StaggeredGridLayoutManager` 来满足自定义需求。这种方式并不算严格意义下的替换,但它确实增强了原有功能[^6]。 --- ### 总结 对于大多数应用而言,`RecyclerView` 已经非常强大且灵活;然而,在追求更高生产力或特殊业务场景时,可以选择上述提到的一些高级框架和技术栈。无论是采用全新的技术路线还是深入挖掘现有 API 的潜力,都可以有效改善应用程序的表现力与运行效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值