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

加载联系人至RecyclerView时启动缓慢及黑屏问题求助

问题分析与优化方案

你的代码导致启动慢、黑屏的核心原因是在主线程执行大量耗时的数据库IO操作,同时存在多处效率低下的写法,具体问题和优化如下:

核心问题点

  • 主线程执行耗时操作:ContentResolver.query()是耗时的IO操作,直接在主线程调用会阻塞UI线程,导致界面卡顿甚至黑屏。
  • 循环内重复查询电话数据:每个联系人都单独发起一次电话信息查询,多次数据库交互会大幅增加耗时。
  • 频繁刷新RecyclerView:循环内每次添加数据都调用notifyDataSetChanged(),会导致RecyclerView频繁重绘,浪费性能。
  • 不必要的字段查询:query()时传入null会获取联系人表的所有字段,增加内存占用和查询时间。
  • 非空断言风险:!!强制非空会忽略cursor为空的情况,可能引发崩溃,且未正确处理异常。

优化后的代码实现

1. 用协程将耗时操作移至后台线程

使用Kotlin协程把联系人加载逻辑放到IO线程,避免阻塞主线程:

private fun getContactList() {
    lifecycleScope.launch(Dispatchers.IO) {
        val contactList = mutableListOf<ContactModel>()
        // 只查询需要的字段,避免获取冗余数据
        val projection = arrayOf(
            ContactsContract.Contacts._ID,
            ContactsContract.Contacts.DISPLAY_NAME
        )
        val sort = "${ContactsContract.Contacts.DISPLAY_NAME} ASC"
        
        requireActivity().contentResolver.query(
            ContactsContract.Contacts.CONTENT_URI,
            projection,
            null,
            null,
            sort
        )?.use { cursor ->
            val idColumn = cursor.getColumnIndexOrThrow(ContactsContract.Contacts._ID)
            val nameColumn = cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME)
            
            while (cursor.moveToNext()) {
                val id = cursor.getString(idColumn)
                val name = cursor.getString(nameColumn)
                val imgUri = getPhotoUriFromID(id).toString()
                
                // 一次性查询当前联系人的电话信息
                val phoneProjection = arrayOf(
                    ContactsContract.CommonDataKinds.Phone.NUMBER
                )
                val selection = "${ContactsContract.CommonDataKinds.Phone.CONTACT_ID} = ?"
                requireActivity().contentResolver.query(
                    ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                    phoneProjection,
                    selection,
                    arrayOf(id),
                    null
                )?.use { phoneCursor ->
                    if (phoneCursor.moveToNext()) {
                        val number = phoneCursor.getString(
                            phoneCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER)
                        )
                        contactList.add(ContactModel(name, number, imgUri))
                    }
                }
            }
        }
        
        // 回到主线程统一更新UI
        withContext(Dispatchers.Main) {
            contactArr.clear()
            contactArr.addAll(contactList)
            groupListAdapter.notifyDataSetChanged()
        }
    }
}

2. 进一步优化:一次性关联查询联系人与电话信息

通过ContactsContract.Data表做关联查询,避免两次循环查询,效率更高:

private fun getContactList() {
    lifecycleScope.launch(Dispatchers.IO) {
        val contactList = mutableListOf<ContactModel>()
        // 关联查询联系人基本信息和电话
        val uri = ContactsContract.Data.CONTENT_URI
        val projection = arrayOf(
            ContactsContract.Data.CONTACT_ID,
            ContactsContract.Data.DISPLAY_NAME,
            ContactsContract.CommonDataKinds.Phone.NUMBER,
            ContactsContract.Data.PHOTO_URI
        )
        val selection = "${ContactsContract.Data.MIMETYPE} = ?"
        val selectionArgs = arrayOf(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
        val sort = "${ContactsContract.Data.DISPLAY_NAME} ASC"
        
        requireActivity().contentResolver.query(
            uri,
            projection,
            selection,
            selectionArgs,
            sort
        )?.use { cursor ->
            val contactIdColumn = cursor.getColumnIndexOrThrow(ContactsContract.Data.CONTACT_ID)
            val nameColumn = cursor.getColumnIndexOrThrow(ContactsContract.Data.DISPLAY_NAME)
            val numberColumn = cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER)
            val photoUriColumn = cursor.getColumnIndexOrThrow(ContactsContract.Data.PHOTO_URI)
            
            val processedContactIds = mutableSetOf<String>() // 避免重复添加同一联系人
            while (cursor.moveToNext()) {
                val contactId = cursor.getString(contactIdColumn)
                if (processedContactIds.contains(contactId)) continue
                
                val name = cursor.getString(nameColumn)
                val number = cursor.getString(numberColumn)
                val photoUri = cursor.getString(photoUriColumn) ?: ""
                contactList.add(ContactModel(name, number, photoUri))
                processedContactIds.add(contactId)
            }
        }
        
        withContext(Dispatchers.Main) {
            contactArr.clear()
            contactArr.addAll(contactList)
            groupListAdapter.notifyDataSetChanged()
        }
    }
}

额外优化建议

  • 使用ListAdapter替代普通RecyclerView.Adapter,它会自动计算数据差异,调用submitList()即可高效刷新,无需手动调用notifyDataSetChanged()。
  • 联系人头像加载推荐用Glide或Coil等图片加载库,异步加载并缓存,避免主线程处理图片IO。
  • 增加权限检查和异常捕获,确保在没有联系人权限时不会崩溃。

内容的提问来源于stack exchange,提问作者Mahmoud Nabil

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 00:30:31