Android RecyclerView Adapter数据更新异常:onBindViewHolder偶发未执行,列表内容不显示
Android RecyclerView Adapter数据更新异常:onBindViewHolder偶发未执行,列表内容不显示
看了你的代码和描述,问题的核心其实出在数据加载的异步性上,咱们一步步拆解解决:
一、问题根源分析
你提到generateList()打印的数据是正常的,但onBindViewHolder偶发不执行,本质原因是:
异步数据加载导致Adapter拿到空列表:
你在generateList()里启动了IO协程查询数据库,但协程是异步执行的——函数会直接先返回空的mutableList,这时候Adapter初始化时拿到的是空数据,getItemCount()返回0,onBindViewHolder根本不会被调用。
等协程里数据库数据回来后,你只是把数据加到了list里,但没有通知Adapter数据发生了变化,所以RecyclerView完全不知道要刷新。之前误以为是
setImageResource的问题,其实是偶然巧合——刚好换图片设置方法时,协程的数据加载完成了,才让你误以为是图片API的锅。
二、具体解决方案
1. 修正数据加载逻辑,确保数据就绪后通知Adapter
首先把generateList()改成挂起函数,在协程内完成数据加载,加载完成后再更新Adapter:
// 把generateList改成挂起函数,让数据库查询在协程内同步执行 suspend fun generateList(): MutableList<FFItem>{ val list = mutableListOf<FFItem>() val db = HabitTrackerDatabase.getInstance(applicationContext).Dao() // 文件夹数据查询 for (i in 1 until db.getTotalCountFolder()+1) { val folder = db.getInstanceFolderById(i) if(folder.parentFolderId == null){ list.add(FFItem(folder.name, null, folder.id)) } } // 文件数据查询 for (i in 1 until db.getTotalCountFile()+1) { val file = db.getInstanceFileById(i) if (file.parentFolderId == null) { list.add(FFItem(file.name, file.id, null)) } } Log.i("TAG", "加载完成的数据:$list") return list }
然后在Activity的onCreate里,先初始化空Adapter,再通过协程加载数据并刷新:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_home) // 初始化RecyclerView基础配置 recyclerView = findViewById(R.id.recyclerView) recyclerView.layoutManager = LinearLayoutManager(this) recyclerView.setHasFixedSize(true) // 先给空列表初始化Adapter,避免空指针 val emptyList = mutableListOf<FFItem>() adapter = Adapter(this, emptyList) recyclerView.adapter = adapter // 主线程协程加载数据,加载完成后更新Adapter CoroutineScope(Dispatchers.Main).launch { val loadedList = generateList() // 替换Adapter的数据源 adapter.elements.clear() adapter.elements.addAll(loadedList) // 通知Adapter数据已更新,触发RecyclerView刷新 adapter.notifyDataSetChanged() } }
2. 优化Adapter的图片加载逻辑(规范写法)
你现在的图片加载可以用ContextCompat.getDrawable来兼容更多版本,代码更简洁:
override fun onBindViewHolder(holder: FFViewHolder, position: Int) { val currentItem = elements[position] Log.i("TAG", "currentItem: "+currentItem.toString()) holder.title.text = currentItem.title if(currentItem.folderId == null){ Log.i("TAG", "this is for a file") val fileDrawable = ContextCompat.getDrawable(context, R.drawable.binary_digits) holder.img.setImageDrawable(fileDrawable) holder.button.setOnClickListener { Log.i("TAG", "you have clicked on a button to test for file") } }else{ Log.i("TAG", "this is for a folder") val folderDrawable = ContextCompat.getDrawable(context, R.drawable.folder) holder.img.setImageDrawable(folderDrawable) holder.button.setOnClickListener { Log.i("TAG", "you have clicked on a button to test for folder") } } }
3. 额外优化建议
- 去掉
onCreate里的while(exampleList==null)循环:这会阻塞主线程,非常容易引发ANR,完全没必要,协程异步加载才是正确方式。 - 避免Adapter持有Context强引用(可选):如果担心内存泄漏,可以把Context换成弱引用,或者在
onCreateViewHolder里用parent.context获取上下文。 - 点击事件解耦(可选):可以给Adapter定义点击回调接口,把点击事件逻辑移到Activity里,避免Adapter和业务逻辑耦合:
// 在Adapter里定义接口 interface OnItemActionClickListener { fun onFileButtonClick(item: FFItem) fun onFolderButtonClick(item: FFItem) } // Adapter构造函数添加参数 class Adapter( val context: Context, val elements: MutableList<FFItem>, val clickListener: OnItemActionClickListener ): RecyclerView.Adapter<Adapter.FFViewHolder>(){ // ... override fun onBindViewHolder(holder: FFViewHolder, position: Int) { // ... if(currentItem.folderId == null){ // ... holder.button.setOnClickListener { clickListener.onFileButtonClick(currentItem) } }else{ // ... holder.button.setOnClickListener { clickListener.onFolderButtonClick(currentItem) } } } }
然后在Activity里实现这个接口:
class Home : AppCompatActivity(), Adapter.OnItemActionClickListener { // ... override fun onCreate(savedInstanceState: Bundle?) { // ... adapter = Adapter(this, emptyList, this) // ... } override fun onFileButtonClick(item: FFItem) { Log.i("TAG", "点击了文件按钮:${item.title}") } override fun onFolderButtonClick(item: FFItem) { Log.i("TAG", "点击了文件夹按钮:${item.title}") } }
三、总结
按照上面的步骤修改后,数据加载完成后会主动通知Adapter刷新,onBindViewHolder就会稳定执行,RecyclerView的内容也会正常显示。之前的偶发问题本质是数据加载的异步性没处理好,和图片设置API没有直接关系~





