主线程任务过重导致无法完整获取联系人问题咨询
解决主线程获取联系人导致帧丢失&无法获取全部联系人的问题
看起来你在获取手机联系人的时候碰到了两个麻烦:一是拿不全所有联系人,二是收到了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
相关产品推荐
相关产品推荐

