如何加快Android联系人读取速度?附现有实现代码
嘿,我仔细看了你的代码,读取450个联系人耗时60秒确实有点慢,问题主要集中在多次重复的数据库查询和一些不必要的开销上。系统默认应用之所以快,是因为它用了更高效的查询策略,下面给你几个具体可落地的优化点:
1. 用一次查询获取所有数据,避免多次单联系人查询
你现在的逻辑是先查所有联系人的基础信息,然后对每个联系人单独查电话、邮箱、生日,450个联系人就会发起450*3=1350次查询,这是最大的性能瓶颈。Android的ContactsContract.Data表是所有联系人数据的统一存储地,我们可以一次性把所有需要的数据查出来,再在内存中按联系人ID分组:
// 定义我们需要的所有列,只查有用的! String[] projection = { ContactsContract.Data.CONTACT_ID, ContactsContract.Data.DISPLAY_NAME_PRIMARY, ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Email.DATA, ContactsContract.CommonDataKinds.Event.START_DATE, ContactsContract.CommonDataKinds.Event.TYPE }; // 过滤出我们关心的数据类型:电话、邮箱、生日 String selection = ContactsContract.Data.MIMETYPE + " IN (?, ?, ?)"; String[] selectionArgs = { ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE }; // 按CONTACT_ID排序,方便后续分组处理 Cursor dataCursor = getContentResolver().query( ContactsContract.Data.CONTENT_URI, projection, selection, selectionArgs, ContactsContract.Data.CONTACT_ID ); if (dataCursor != null) { HashMap<Long, Contact> contactMap = new HashMap<>(); while (dataCursor.moveToNext()) { long contactId = dataCursor.getLong(dataCursor.getColumnIndex(ContactsContract.Data.CONTACT_ID)); String name = dataCursor.getString(dataCursor.getColumnIndex(ContactsContract.Data.DISPLAY_NAME_PRIMARY)); String mimeType = dataCursor.getString(dataCursor.getColumnIndex(ContactsContract.Data.MIMETYPE)); // 获取或创建当前联系人对象 Contact contact = contactMap.get(contactId); if (contact == null) { contact = new Contact(); contact.name = name; contactMap.put(contactId, contact); } // 根据数据类型填充对应字段 switch (mimeType) { case ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE: String phone = dataCursor.getString(dataCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)); contact.PhoneNumber.add(phone); contact.NumberOfPhones = contact.PhoneNumber.size(); break; case ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE: String email = dataCursor.getString(dataCursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA)); contact.Email.add(email); contact.NumberOfMails = contact.Email.size(); break; case ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE: int eventType = dataCursor.getInt(dataCursor.getColumnIndex(ContactsContract.CommonDataKinds.Event.TYPE)); if (eventType == ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY) { String birthday = dataCursor.getString(dataCursor.getColumnIndex(ContactsContract.CommonDataKinds.Event.START_DATE)); // 建议给Contact类加个birthday字段来存储 } break; } } dataCursor.close(); // 把HashMap转成最终的List ListContact.addAll(contactMap.values()); }
这一步能把查询次数从1350次降到1次,直接解决最大的性能问题。
2. 不要查询不需要的列,避免SELECT *
你原来的代码里用了contentResolver.query(CONTENT_URI, null, ...),这会查询联系人表的所有列,但很多列你根本用不上。指定明确的projection数组,能减少数据库的数据传输量和内存占用,提升处理速度。
3. 减少UI线程的频繁更新
你现在每次循环都通过updateBarHandler.post更新ProgressDialog的消息,450次循环就会触发450次UI线程调度,这会带来不必要的开销。可以改成每10个联系人更新一次进度,或者直接用AsyncTask的onProgressUpdate方法,更高效:
// 示例:每10次循环更新一次进度 counter++; if (counter % 10 == 0) { updateBarHandler.post(() -> pDialog.setMessage("Reading contacts : " + counter + "/" + totalContacts)); }
4. 优化对象复用,减少GC压力
你的代码里每次循环都创建StringBuffer output(如果这个output只是用来打日志,完全可以直接删掉),另外临时集合TempPhoneNumber和TempEmail虽然用了clear(),但可以考虑在循环外初始化一次重复使用,减少对象创建次数,降低垃圾回收的频率。
5. 修复生日查询的SQL拼接问题
原来的生日查询条件是直接拼接字符串,容易出现潜在问题(虽然是本地数据库,但这是不好的编码习惯),而且效率低。如果一定要单独查询生日(建议合并到统一查询里),应该用selectionArgs传递参数:
String where = ContactsContract.CommonDataKinds.Event.TYPE + "=? AND " + ContactsContract.CommonDataKinds.Event.MIMETYPE + "=? AND " + ContactsContract.Data.CONTACT_ID + "=?"; String[] selectionArgs = { String.valueOf(ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY), ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE, contact_id }; Cursor birthdayCur = contentResolver.query( ContactsContract.Data.CONTENT_URI, columns, where, selectionArgs, sortOrder );
6. 考虑使用CursorLoader(可选)
Android推荐用CursorLoader来异步加载联系人数据,它会自动处理生命周期,避免内存泄漏,而且内部做了查询优化。不过如果你已经用了线程,前面的优化点已经足够解决问题,这个可以作为后续的代码优化方向。
按照这些方法调整后,读取450个联系人的速度应该能大幅提升,接近系统默认应用的水平。核心思路就是减少数据库查询次数和避免不必要的开销。
内容的提问来源于stack exchange,提问作者hadadrefael

