You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android RecyclerView Adapter数据更新异常:onBindViewHolder偶发未执行,列表内容不显示

Android RecyclerView Adapter数据更新异常:onBindViewHolder偶发未执行,列表内容不显示

看了你的代码和描述,问题的核心其实出在数据加载的异步性上,咱们一步步拆解解决:

一、问题根源分析

你提到generateList()打印的数据是正常的,但onBindViewHolder偶发不执行,本质原因是:

  1. 异步数据加载导致Adapter拿到空列表
    你在generateList()里启动了IO协程查询数据库,但协程是异步执行的——函数会直接先返回空的mutableList,这时候Adapter初始化时拿到的是空数据,getItemCount()返回0,onBindViewHolder根本不会被调用。
    等协程里数据库数据回来后,你只是把数据加到了list里,但没有通知Adapter数据发生了变化,所以RecyclerView完全不知道要刷新。

  2. 之前误以为是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没有直接关系~

火山引擎 最新活动