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

主线程任务过重导致无法完整获取联系人问题咨询

解决主线程获取联系人导致帧丢失&无法获取全部联系人的问题

看起来你在获取手机联系人的时候碰到了两个麻烦:一是拿不全所有联系人,二是收到了I/Choreographer: Skipped 213 frames!的警告对吧?我来帮你拆解下问题,再给你靠谱的解决方案。

问题根源分析

这个帧丢失的警告,核心原因是你把所有联系人查询、循环处理的逻辑都放在了主线程(UI线程)里。主线程本来是负责UI渲染和响应用户操作的,一旦被耗时的IO操作(比如查询联系人数据库)长时间占用,系统就会因为来不及刷新UI而抛出这个警告,严重的话还会触发ANR(应用无响应)。而且因为主线程被阻塞,你的联系人循环可能还没跑完就被中断,自然拿不全所有联系人。

另外看你的代码还有个小bug:progressDialog.dismiss()放在了内部的电话号码查询循环里,这会导致只要处理完第一个带电话的联系人,弹窗就被关掉了,后续联系人处理的时候用户看不到加载状态,逻辑上也不对。

解决方案:用异步任务把耗时操作移到后台

我们需要把联系人查询的耗时逻辑放到后台线程执行,这里我用Android经典的AsyncTask来适配你的Java代码,同时修正弹窗的时机问题:

修正后的完整代码

private void getContactList() {
    new AsyncTask<Void, Void, String[]>() {
        ProgressDialog progressDialog;

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            // 在主线程显示加载弹窗,让用户知道正在处理
            progressDialog = ProgressDialog.show(YourActivity.this, "加载中", "正在获取联系人...");
        }

        @Override
        protected String[] doInBackground(Void... voids) {
            // 所有耗时操作都在这里执行,后台线程不会阻塞UI
            ContentResolver cr = getContentResolver();
            // 用StringBuilder替代字符串拼接,性能提升很多,尤其是联系人多的时候
            StringBuilder nameBuilder = new StringBuilder();
            StringBuilder numberBuilder = new StringBuilder();

            Cursor cur = cr.query(ContactsContract.Contacts.CONTENT_URI,
                    null, null, null, null);

            if ((cur != null ? cur.getCount() : 0) > 0) {
                while (cur != null && cur.moveToNext()) {
                    String id = cur.getString(cur.getColumnIndex(ContactsContract.Contacts._ID));
                    String name = cur.getString(cur.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));

                    if (cur.getInt(cur.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)) > 0) {
                        Cursor pCur = cr.query(
                                ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                                null,
                                ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
                                new String[]{id},
                                null);

                        while (pCur.moveToNext()) {
                            String phoneNo = pCur.getString(pCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                            nameBuilder.append(name).append(",");
                            numberBuilder.append(phoneNo).append(",");
                        }
                        pCur.close(); // 记得关闭Cursor,避免内存泄漏
                    }
                }
            }
            if (cur != null) {
                cur.close();
            }
            // 返回处理好的姓名和号码数组
            return new String[]{nameBuilder.toString(), numberBuilder.toString()};
        }

        @Override
        protected void onPostExecute(String[] result) {
            super.onPostExecute(result);
            // 回到主线程,关闭弹窗并处理结果
            if (progressDialog.isShowing()) {
                progressDialog.dismiss();
            }
            String myname = result[0];
            String mynumber = result[1];
            Log.i("tag", "Name: " + myname + ", Number: " + mynumber);
            // 这里可以把字符串转成你需要的String数组,或者做其他UI操作
        }
    }.execute();
}

几个关键的优化点

  • 异步线程隔离:把联系人查询、循环处理都放到doInBackground方法,这个方法运行在后台线程,完全不会阻塞主线程,帧丢失的问题就解决了,联系人也能全部处理完。
  • StringBuilder优化:原来的myname += name + ","是低效的字符串拼接,每次都会创建新的String对象,改用StringBuilder能大幅提升性能,尤其是联系人数量多的时候。
  • 弹窗逻辑修正:在onPreExecute(主线程)显示弹窗,onPostExecute(主线程)关闭弹窗,确保整个联系人获取过程中用户都能看到加载状态,逻辑更合理。
  • 资源释放:确保所有Cursor都被关闭,避免内存泄漏问题。

额外的现代方案(可选)

如果你的项目用了AndroidX,更推荐用Coroutines(协程)来处理异步,代码会更简洁易读,这里给个Kotlin的示例参考:

private fun getContactList() {
    lifecycleScope.launch {
        val progressDialog = ProgressDialog.show(this@YourActivity, "加载中", "正在获取联系人...")
        // 切换到IO线程处理耗时操作
        val (names, numbers) = withContext(Dispatchers.IO) {
            val cr = contentResolver
            val nameBuilder = StringBuilder()
            val numberBuilder = StringBuilder()
            
            cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null)?.use { cursor ->
                while (cursor.moveToNext()) {
                    val id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID))
                    val name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))
                    
                    if (cursor.getInt(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)) > 0) {
                        cr.query(
                            ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                            null,
                            "${ContactsContract.CommonDataKinds.Phone.CONTACT_ID} = ?",
                            arrayOf(id),
                            null
                        )?.use { phoneCursor ->
                            while (phoneCursor.moveToNext()) {
                                val phoneNo = phoneCursor.getString(phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
                                nameBuilder.append(name).append(",")
                                numberBuilder.append(phoneNo).append(",")
                            }
                        }
                    }
                }
            }
            Pair(nameBuilder.toString(), numberBuilder.toString())
        }
        progressDialog.dismiss()
        Log.i("tag", "Name: $names, Number: $numbers")
    }
}

内容的提问来源于stack exchange,提问作者Chintan Parmar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:26:30