Android分批加载联系人并实时展示时Adapter通知异常求助
Hey there! Let's break down what's causing that error and fix it so you can show contacts in batches smoothly.
Why You're Seeing That Error
The core issue here is that you're modifying your ListAdapter's data or calling its notification methods (like notifyDataSetChanged()) from a background thread. Android strictly enforces that all UI-related operations (including updating adapters) must happen on the main thread—that's exactly what the error message is yelling at you about.
Fix 1: Use AsyncTask Correctly (Legacy but Works)
AsyncTask has built-in support for updating the UI mid-task via publishProgress() and onProgressUpdate(). The latter runs on the main thread, so it's safe to update your adapter here.
Here's a concrete example:
private class FetchContactsTask extends AsyncTask<Void, List<Contact>, Void> { @Override protected Void doInBackground(Void... voids) { List<Contact> batch = new ArrayList<>(); int contactCount = 0; // Your contact-fetching logic (e.g., using ContentResolver) Cursor cursor = getContentResolver().query( ContactsContract.Contacts.CONTENT_URI, null, null, null, null ); if (cursor != null && cursor.moveToFirst()) { do { // Parse a single contact from the cursor Contact contact = parseContactFromCursor(cursor); batch.add(contact); contactCount++; // Every 50 contacts, send the batch to the UI thread if (contactCount % 50 == 0) { publishProgress(batch); batch.clear(); // Reset for next batch } } while (cursor.moveToNext()); // Don't forget any remaining contacts after the last full batch if (!batch.isEmpty()) { publishProgress(batch); } cursor.close(); } return null; } // This runs on the MAIN thread—safe to update the adapter here! @Override protected void onProgressUpdate(List<Contact>... values) { List<Contact> newContacts = values[0]; yourContactAdapter.addAll(newContacts); // Assume your adapter has an addAll() method yourContactAdapter.notifyDataSetChanged(); } }
Fix 2: Use Thread + runOnUiThread() (For Custom Threads)
If you're sticking with a plain Thread, wrap all adapter updates in runOnUiThread() to switch back to the main thread:
new Thread(() -> { List<Contact> batch = new ArrayList<>(); int contactCount = 0; // Your contact-fetching logic here... Cursor cursor = getContentResolver().query( ContactsContract.Contacts.CONTENT_URI, null, null, null, null ); if (cursor != null && cursor.moveToFirst()) { do { Contact contact = parseContactFromCursor(cursor); batch.add(contact); contactCount++; if (contactCount % 50 == 0) { // Switch to main thread to update adapter runOnUiThread(() -> { yourContactAdapter.addAll(batch); yourContactAdapter.notifyDataSetChanged(); batch.clear(); }); } } while (cursor.moveToNext()); // Handle remaining contacts if (!batch.isEmpty()) { runOnUiThread(() -> { yourContactAdapter.addAll(batch); yourContactAdapter.notifyDataSetChanged(); }); } cursor.close(); } }).start();
Fix 3: Use Kotlin Coroutines (Modern Approach)
If you're using Kotlin, coroutines make this way cleaner with Dispatchers.IO for background work and Dispatchers.Main for UI updates:
lifecycleScope.launch(Dispatchers.IO) { val batch = mutableListOf<Contact>() var contactCount = 0 contentResolver.query( ContactsContract.Contacts.CONTENT_URI, null, null, null, null )?.use { cursor -> while (cursor.moveToNext()) { val contact = parseContactFromCursor(cursor) batch.add(contact) contactCount++ if (contactCount % 50 == 0) { // Switch to main thread safely withContext(Dispatchers.Main) { yourContactAdapter.addAll(batch) yourContactAdapter.notifyDataSetChanged() batch.clear() } } } // Process leftover contacts if (batch.isNotEmpty()) { withContext(Dispatchers.Main) { yourContactAdapter.addAll(batch) yourContactAdapter.notifyDataSetChanged() } } } }
Key Takeaways to Avoid This Error Again
- Never modify your adapter's data list (add/remove/clear) from a background thread.
- Never call
notifyDataSetChanged()or similar adapter notification methods off the main thread. - Always route UI-related updates through the main thread—whether via AsyncTask's
onProgressUpdate(),runOnUiThread(), or coroutines'Dispatchers.Main.
内容的提问来源于stack exchange,提问作者Bhanu Sharma

