加载联系人至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
相关产品推荐
相关产品推荐

