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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 06:44:13