Google Play应用获READ_CONTACTS权限后,联系人无法加载至自定义ListView的解决方法
Hey there! Let's work through this issue together. You've already added the manifest permission, so we need to focus on two key areas: properly handling the permission request result and correctly querying/binding contact data to your ListView. Here's a step-by-step breakdown:
1. Ensure You're Handling Dynamic Permission Requests Correctly (Android 6.0+)
Even with the manifest permission, Android requires dynamic permission requests for dangerous permissions like READ_CONTACTS on API 23+. Make sure you're requesting the permission on first launch, and triggering the contact load only after the user grants it.
Modern Approach (Using ActivityResultContracts)
The old onRequestPermissionsResult method is deprecated, so use the Activity Result API instead:
// In your Activity/Fragment private val requestContactPermission = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> if (isGranted) { // Permission granted—load contacts now loadContacts() } else { // Handle denial (show explanation, redirect to settings, etc.) Toast.makeText(this, "Permission needed to load contacts", Toast.LENGTH_SHORT).show() } } // Call this on first launch (e.g., in onCreate) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { // Request permission requestContactPermission.launch(Manifest.permission.READ_CONTACTS) } else { // Permission already granted—load contacts immediately loadContacts() } }
2. Correctly Query Contact Data After Permission Grant
Once permission is granted, use ContentResolver to query the contacts database. Always do this off the main thread to avoid ANRs (Application Not Responding) errors.
Example: Load Contacts with Coroutines (Kotlin)
private fun loadContacts() { lifecycleScope.launch(Dispatchers.IO) { val contactList = mutableListOf<Contact>() val projection = arrayOf( ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER ) contentResolver.query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, null, null, "${ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME} ASC" )?.use { cursor -> val nameIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME) val numberIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER) while (cursor.moveToNext()) { val name = cursor.getString(nameIndex) val number = cursor.getString(numberIndex) contactList.add(Contact(name, number)) } } // Switch back to main thread to update ListView withContext(Dispatchers.Main) { val listView = findViewById<ListView>(R.id.custom_list_view) listView.adapter = ContactAdapter(this@MainActivity, contactList) } } } // Data class to hold contact info data class Contact(val name: String, val number: String)
Java Equivalent (Using AsyncTask)
If you're using Java, use an AsyncTask or ExecutorService to run the query in the background:
private void loadContacts() { new AsyncTask<Void, Void, List<Contact>>() { @Override protected List<Contact> doInBackground(Void... voids) { List<Contact> contactList = new ArrayList<>(); String[] projection = { ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER }; Cursor cursor = getContentResolver().query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, null, null, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC" ); if (cursor != null) { int nameIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME); int numberIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER); while (cursor.moveToNext()) { String name = cursor.getString(nameIndex); String number = cursor.getString(numberIndex); contactList.add(new Contact(name, number)); } cursor.close(); } return contactList; } @Override protected void onPostExecute(List<Contact> contacts) { ListView listView = findViewById(R.id.custom_list_view); listView.setAdapter(new ContactAdapter(MainActivity.this, contacts)); } }.execute(); } // Contact model class public class Contact { private String name; private String number; public Contact(String name, String number) { this.name = name; this.number = number; } // Getters for name and number public String getName() { return name; } public String getNumber() { return number; } }
3. Create a Custom Adapter for Your ListView
You need a custom adapter to bind the contact data to your ListView's item layout.
Example ContactAdapter (Kotlin)
class ContactAdapter(context: Context, private val contacts: List<Contact>) : BaseAdapter() { private val inflater = LayoutInflater.from(context) override fun getCount(): Int = contacts.size override fun getItem(position: Int): Contact = contacts[position] override fun getItemId(position: Int): Long = position.toLong() override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { val view = convertView ?: inflater.inflate(R.layout.list_item_contact, parent, false) val contact = getItem(position) view.findViewById<TextView>(R.id.tv_contact_name).text = contact.name view.findViewById<TextView>(R.id.tv_contact_number).text = contact.number return view } }
Corresponding List Item Layout (list_item_contact.xml)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="16dp" android:orientation="vertical"> <TextView android:id="@+id/tv_contact_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="18sp" android:textStyle="bold"/> <TextView android:id="@+id/tv_contact_number" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="14sp" android:textColor="@android:color/darker_gray"/> </LinearLayout>
4. Common Pitfalls to Check
- Did you trigger
loadContacts()only after permission is granted? If you're trying to load contacts before the user accepts, the query will fail silently. - Is your ListView ID correct in the Activity layout? Double-check that you're referencing the right ListView when setting the adapter.
- Are you querying the correct URI? Use
ContactsContract.CommonDataKinds.Phone.CONTENT_URIto get phone contacts (not the baseContactsContract.Contacts.CONTENT_URI, which doesn't include phone numbers by default). - Did you handle empty cursors? If there are no contacts, your ListView will show empty—add an empty state view if needed.
内容的提问来源于stack exchange,提问作者Hatay Berkay Işıkoğlu

