Android RecyclerView Adapter notifyDataSetChanged失效原因及跨线程更新方案
RecyclerView notifyDataSetChanged无效的原因及跨线程更新方案
先贴出你提供的代码:
val recyclerView = findViewById<RecyclerView>(R.id.rv) val adapter = UserDetailsAdapter(users) recyclerView.adapter = adapter recyclerView.layoutManager = LinearLayoutManager(this) Thread{ userDao.insertAll(user,user2,user3) users= userDao.getAll() as ArrayList Log.d("Checking Service",users.size.toString()) recyclerView.post { Log.d("Checking Service","Data set Changed") adapter.notifyDataSetChanged() } }.start()
为什么notifyDataSetChanged无效?
核心问题是数据源引用不匹配:
- 初始化Adapter时,你把当前的
users列表传给了Adapter,此时Adapter内部持有这个列表的引用。 - 子线程里执行
users= userDao.getAll() as ArrayList时,只是让users变量指向了一个全新的ArrayList对象,但Adapter里保存的还是原来那个旧列表的引用。 - 所以调用
notifyDataSetChanged()时,Adapter读取的还是旧列表的数据,自然不会更新UI。哪怕用了runOnUIThread,本质问题没解决,照样无效。
解决方法
有三种常用修复方式,按需选择:
1. 复用原有列表对象,不重新赋值
不要给users变量重新赋值,而是清空旧列表再添加新数据:
Thread{ userDao.insertAll(user,user2,user3) val newUsers = userDao.getAll() as ArrayList Log.d("Checking Service",newUsers.size.toString()) recyclerView.post { users.clear() users.addAll(newUsers) adapter.notifyDataSetChanged() } }.start()
这样Adapter持有的还是原来的列表引用,数据更新后通知就能生效。
2. 给Adapter添加更新数据源的方法
在UserDetailsAdapter里加一个专门更新数据的方法,直接替换内部的数据源:
class UserDetailsAdapter(private var userList: List<User>) : RecyclerView.Adapter<...>() { // ... 其他原有代码 fun updateUsers(newUsers: List<User>) { this.userList = newUsers notifyDataSetChanged() } }
然后在UI线程调用这个方法:
Thread{ userDao.insertAll(user,user2,user3) val newUsers = userDao.getAll() as ArrayList Log.d("Checking Service",newUsers.size.toString()) recyclerView.post { adapter.updateUsers(newUsers) } }.start()
3. 用Room+LiveData自动处理(推荐)
Room支持直接返回LiveData<List<User>>,数据变化时会自动在UI线程通知你,不需要手动开线程和post:
- 先修改Dao的方法:
@Dao interface UserDao { // ... 其他方法 @Query("SELECT * FROM user") fun getAll(): LiveData<List<User>> }
- 然后在Activity/Fragment里观察这个LiveData:
val recyclerView = findViewById<RecyclerView>(R.id.rv) val adapter = UserDetailsAdapter(emptyList()) recyclerView.adapter = adapter recyclerView.layoutManager = LinearLayoutManager(this) // 观察数据变化,自动在UI线程回调 userDao.getAll().observe(this) { newUsers -> adapter.updateUsers(newUsers) // 用上面第二种方法里的updateUsers,或者复用列表 } // 插入数据的操作可放在子线程,或用Room的suspend方法配合协程 Thread{ userDao.insertAll(user,user2,user3) }.start()
这种方式符合Android架构规范,代码更简洁,也不容易出错。
跨线程更新RecyclerView Adapter的正确姿势
所有涉及Adapter数据源修改和UI通知的操作,必须在UI线程执行,常用方式有:
- 用
recyclerView.post(Runnable):把更新逻辑包装成Runnable,交给RecyclerView所在的UI线程执行。 - 用Activity/Fragment的
runOnUiThread(Runnable):直接指定在UI线程执行。 - 用LiveData:Room返回的LiveData会自动在UI线程触发回调,无需手动切换线程。
- 用协程:在
viewModelScope或lifecycleScope里执行数据操作,切换到UI线程更新Adapter。
内容的提问来源于stack exchange,提问作者Jithin Murali
相关产品推荐
相关产品推荐

