如何在Android Studio中显示通话记录里的未知/未保存电话号码列表?
Got it, let's walk through how to build this feature step by step. I've tackled similar tasks before, so here's a practical, actionable breakdown:
1. 申请必要权限
First, your app needs two critical permissions to access call logs and compare against saved contacts. Add these to your AndroidManifest.xml:
<uses-permission android:name="android.permission.READ_CALL_LOG" /> <uses-permission android:name="android.permission.READ_CONTACTS" />
Since Android 6.0 (API 23), you have to request these permissions dynamically at runtime. Here's a quick snippet to handle that in your Activity:
private static final int PERMISSION_REQUEST_CODE = 100; private String[] REQUIRED_PERMISSIONS = {Manifest.permission.READ_CALL_LOG, Manifest.permission.READ_CONTACTS}; // Check and request permissions when the activity starts @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (!hasPermissions(this, REQUIRED_PERMISSIONS)) { ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, PERMISSION_REQUEST_CODE); } else { // Permissions granted, start processing call logs fetchAndFilterUnknownCalls(); } } // Helper method to check permissions private boolean hasPermissions(Context context, String... permissions) { for (String permission : permissions) { if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) { return false; } } return true; } // Handle permission request result @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == PERMISSION_REQUEST_CODE) { boolean allGranted = true; for (int result : grantResults) { if (result != PackageManager.PERMISSION_GRANTED) { allGranted = false; break; } } if (allGranted) { fetchAndFilterUnknownCalls(); } else { // Show a message to user explaining why permissions are needed Toast.makeText(this, "Permissions are required to show unknown call records", Toast.LENGTH_LONG).show(); } } }
2. 查询通话记录
Next, use ContentResolver to fetch all call logs. We'll pull key details like the phone number, call type, and timestamp:
private List<CallRecord> fetchCallLogs() { List<CallRecord> callRecords = new ArrayList<>(); ContentResolver contentResolver = getContentResolver(); // Define the columns we need from call logs String[] projection = { CallLog.Calls.NUMBER, CallLog.Calls.TYPE, CallLog.Calls.DATE }; Cursor cursor = contentResolver.query( CallLog.Calls.CONTENT_URI, projection, null, null, CallLog.Calls.DATE + " DESC" // Sort by most recent calls first ); if (cursor != null) { while (cursor.moveToNext()) { String number = cursor.getString(cursor.getColumnIndexOrThrow(CallLog.Calls.NUMBER)); int callType = cursor.getInt(cursor.getColumnIndexOrThrow(CallLog.Calls.TYPE)); long date = cursor.getLong(cursor.getColumnIndexOrThrow(CallLog.Calls.DATE)); // Normalize the number to avoid format mismatches (e.g., (123) 456-7890 vs 1234567890) String normalizedNumber = normalizePhoneNumber(number); callRecords.add(new CallRecord(normalizedNumber, callType, date)); } cursor.close(); } return callRecords; } // Helper to normalize phone numbers (remove non-digit characters except '+') private String normalizePhoneNumber(String number) { return number.replaceAll("[^\\d+]", ""); } // Simple data class to hold call record details public static class CallRecord { public String number; public int callType; public long date; public CallRecord(String number, int callType, long date) { this.number = number; this.callType = callType; this.date = date; } }
3. 筛选未知/未保存号码
Now we need to check which call numbers aren't in the user's saved contacts. To optimize performance, pre-fetch all contact numbers into a HashSet first (this avoids repeated database hits):
private HashSet<String> getAllContactNumbers() { HashSet<String> contactNumbers = new HashSet<>(); ContentResolver contentResolver = getContentResolver(); String[] projection = {ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER}; Cursor cursor = contentResolver.query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, null, null, null ); if (cursor != null) { while (cursor.moveToNext()) { String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER)); contactNumbers.add(number); } cursor.close(); } return contactNumbers; } // Filter call logs to get only unknown numbers private List<CallRecord> filterUnknownCalls(List<CallRecord> allCallRecords) { HashSet<String> contactNumbers = getAllContactNumbers(); List<CallRecord> unknownCalls = new ArrayList<>(); for (CallRecord record : allCallRecords) { if (!contactNumbers.contains(record.number)) { unknownCalls.add(record); } } return unknownCalls; }
4. 展示筛选后的列表
Finally, display the filtered unknown calls using a RecyclerView (the modern choice for lists in Android):
Step 4.1: Add RecyclerView to your layout (activity_main.xml)
<androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="8dp"/>
Step 4.2: Create a list item layout (item_call_record.xml)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp" android:background="?attr/selectableItemBackground"> <TextView android:id="@+id/tv_number" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="18sp" android:textStyle="bold"/> <TextView android:id="@+id/tv_call_details" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="14sp" android:textColor="@android:color/darker_gray" android:layout_marginTop="4dp"/> </LinearLayout>
Step 4.3: Implement the RecyclerView Adapter
public class CallRecordAdapter extends RecyclerView.Adapter<CallRecordAdapter.CallRecordViewHolder> { private List<CallRecord> callRecords; private Context context; public CallRecordAdapter(Context context, List<CallRecord> callRecords) { this.context = context; this.callRecords = callRecords; } @NonNull @Override public CallRecordViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(context).inflate(R.layout.item_call_record, parent, false); return new CallRecordViewHolder(view); } @Override public void onBindViewHolder(@NonNull CallRecordViewHolder holder, int position) { CallRecord record = callRecords.get(position); holder.tvNumber.setText(record.number); // Format call type and date String callType = getCallTypeString(record.callType); String dateString = new SimpleDateFormat("MM/dd/yyyy HH:mm", Locale.getDefault()).format(new Date(record.date)); holder.tvCallDetails.setText(String.format("%s • %s", callType, dateString)); } private String getCallTypeString(int callType) { switch (callType) { case CallLog.Calls.INCOMING_TYPE: return "Incoming"; case CallLog.Calls.OUTGOING_TYPE: return "Outgoing"; case CallLog.Calls.MISSED_TYPE: return "Missed"; default: return "Unknown"; } } @Override public int getItemCount() { return callRecords.size(); } public static class CallRecordViewHolder extends RecyclerView.ViewHolder { TextView tvNumber; TextView tvCallDetails; public CallRecordViewHolder(@NonNull View itemView) { super(itemView); tvNumber = itemView.findViewById(R.id.tv_number); tvCallDetails = itemView.findViewById(R.id.tv_call_details); } } }
Step 4.4: Set up RecyclerView in your Activity
Add this to your fetchAndFilterUnknownCalls() method (run on a background thread to avoid UI freezes):
private void fetchAndFilterUnknownCalls() { new AsyncTask<Void, Void, List<CallRecord>>() { @Override protected List<CallRecord> doInBackground(Void... voids) { List<CallRecord> allCalls = fetchCallLogs(); return filterUnknownCalls(allCalls); } @Override protected void onPostExecute(List<CallRecord> unknownCalls) { RecyclerView recyclerView = findViewById(R.id.recycler_view); recyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this)); recyclerView.setAdapter(new CallRecordAdapter(MainActivity.this, unknownCalls)); if (unknownCalls.isEmpty()) { Toast.makeText(MainActivity.this, "No unknown call records found", Toast.LENGTH_SHORT).show(); } } }.execute(); }
Important Notes
- Android 10+ Permissions: For API level 29+,
READ_CALL_LOGis a privileged permission on some devices. Handle cases where users deny it repeatedly with clear explanations. - Modern Alternatives:
AsyncTaskworks for simple cases, but consider using Kotlin Coroutines orWorkManagerfor better lifecycle management in newer apps. - Number Normalization: Always normalize numbers before comparing—different regions use different formats, so stripping non-digit characters (except '+') ensures accurate matches.
内容的提问来源于stack exchange,提问作者Rick

