Android VoIP应用获取大量联系人耗时过长优化求助
优化大量联系人加载性能的方案
嘿,我太懂处理20000条联系人时13秒加载时间有多闹心了——咱们从几个核心维度来把这个速度提上去:
1. 只查询需要的列,砍掉冗余数据
你当前的query方法传了null作为投影参数,这会让ContentResolver返回所有列的数据,大数据量下这完全是没必要的性能浪费。明确指定你需要的列,能大幅减少Cursor的体积:
// 只定义业务需要的列 String[] projection = { ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.Contacts._ID }; // 替换原有的query调用 cursor = getActivity().getContentResolver().query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, // 只加载需要的字段 null, null, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME );
2. 把耗时操作扔到后台,别堵死UI线程
你现在的代码是在UI线程里跑查询和数据处理的,这必然导致界面卡顿甚至ANR。用AsyncTask(或者更现代的Coroutines/RxJava)把重活放到后台线程:
private class LoadContactsTask extends AsyncTask<Void, Void, List<Person>> { @Override protected List<Person> doInBackground(Void... voids) { List<Person> storeContacts = new ArrayList<>(); HashSet<String> contactIdSet = new HashSet<>(); // 用contactId去重比name靠谱 String[] projection = { ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.Contacts._ID }; Cursor cursor = getActivity().getContentResolver().query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, null, null, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME ); if (cursor != null) { // 提前获取列索引,避免循环内重复调用浪费时间 int nameIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME); int numberIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER); int idIndex = cursor.getColumnIndex(ContactsContract.Contacts._ID); while (cursor.moveToNext()) { String name = cursor.getString(nameIndex); String phoneNumber = cursor.getString(numberIndex); String contactId = cursor.getString(idIndex); // 用contactId去重,避免同一个联系人的多个号码重复添加 if (contactIdSet.add(contactId)) { Person per = new Person(); per.setName(name); per.setNumber(phoneNumber); storeContacts.add(per); } } cursor.close(); } return storeContacts; } @Override protected void onPostExecute(List<Person> contacts) { StoreContacts = contacts; adapter = new ContactListViewAdapter(getContext(), StoreContacts); contactsListView.setAdapter(adapter); Toast.makeText(getActivity(), "加载完成,共" + contacts.size() + "个联系人", Toast.LENGTH_LONG).show(); } } // 调用异步任务启动加载 new LoadContactsTask().execute();
3. 优化Cursor遍历的细节
- 提前获取列索引:把
getColumnIndex的调用挪到循环外面,避免每次循环都去查找列的位置,20000条数据下这点优化能省不少时间。 - 靠谱的去重逻辑:你原来注释掉的用
name去重很容易出错(重名的人太多),改用contactId作为去重依据,既能避免重复加载同一个联系人,又不会漏掉不同的重名用户。
4. 官方推荐:用LoaderManager管理查询
Android官方建议用LoaderManager来处理ContentProvider的查询,它会自动管理后台线程,还能监听联系人数据的变化,甚至在屏幕旋转这类配置变化时也不会重复查询:
// 实现LoaderCallbacks接口 public class YourFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> { private static final int CONTACTS_LOADER_ID = 1; private ContactListViewAdapter adapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getLoaderManager().initLoader(CONTACTS_LOADER_ID, null, this); } @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { String[] projection = { ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.Contacts._ID }; return new CursorLoader( getActivity(), ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, null, null, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME ); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { // 在这里处理Cursor数据并更新UI List<Person> contacts = new ArrayList<>(); HashSet<String> contactIdSet = new HashSet<>(); if (cursor != null) { int nameIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME); int numberIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER); int idIndex = cursor.getColumnIndex(ContactsContract.Contacts._ID); while (cursor.moveToNext()) { String name = cursor.getString(nameIndex); String phoneNumber = cursor.getString(numberIndex); String contactId = cursor.getString(idIndex); if (contactIdSet.add(contactId)) { Person per = new Person(); per.setName(name); per.setNumber(phoneNumber); contacts.add(per); } } } adapter = new ContactListViewAdapter(getContext(), contacts); contactsListView.setAdapter(adapter); } @Override public void onLoaderReset(Loader<Cursor> loader) { adapter.clear(); // 数据重置时清空列表 } }
5. 进阶优化:分页加载或改用RecyclerView
如果一次性加载20000条还是觉得慢,可以试试分页加载:先加载前200条,用户滚动到列表底部时再加载下一批。另外,把ListView换成RecyclerView也能提升滚动流畅度——RecyclerView是复用视图的,比ListView的性能好太多,尤其适合大数据量场景。
内容的提问来源于stack exchange,提问作者E-housma Mardini
相关产品推荐
相关产品推荐

