Android 系統未提供直接的「未接來電」廣播,需組合 PhoneStateListener 監聽通話狀態變化 (RINGING → IDLE) 判斷未接來電。本文提供完整實作:狀態監聽器、廣播接收器啟動監聽、Manifest 註冊與權限聲明。
PhoneStateListener,覆寫 onCallStateChanged() 偵測 CALL_STATE_RINGING → CALL_STATE_IDLE 轉換BroadcastReceiver,接收 PHONE_STATE 廣播,向 TelephonyManager 註冊 CallListenerREAD_PHONE_STATE 權限、註冊 CallReceiver (高優先級)public class CallListener extends PhoneStateListener {
private static final String TAG = "MissedCallDetector";
private static int lastState = TelephonyManager.CALL_STATE_IDLE;
private final Context context;
public CallListener(Context context) {
this.context = context;
}
@Override
public void onCallStateChanged(int state, String incomingNumber) {
Log.d(TAG, "State changed: " + state + " number: " + incomingNumber);
// 關鍵判斷:上一狀態響鈴,當前狀態閒置 = 未接來電
if (lastState == TelephonyManager.CALL_STATE_RINGING &&
state == TelephonyManager.CALL_STATE_IDLE) {
handleMissedCall(incomingNumber);
}
lastState = state;
}
private void handleMissedCall(String number) {
Log.i(TAG, "Missed call from: " + number);
// TODO: 發送通知、記錄日誌、呼叫 API、啟動 Service 等業務邏輯
// 注意:這裡在主執行緒,耗時操作請用 Handler/Thread/Service
}
}
public class CallReceiver extends BroadcastReceiver {
private static final String TAG = "MissedCallDetector";
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "PHONE_STATE broadcast received");
String action = intent.getAction();
if (!TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) return;
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
CallListener listener = new CallListener(context);
// 僅監聽通話狀態,避免不必要的回調
tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
// 可選:從意圖取得來電號碼 (部分 ROM 需此補充)
Bundle extras = intent.getExtras();
if (extras != null) {
String number = extras.getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
Log.d(TAG, "Incoming number from broadcast: " + number);
}
}
}
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
package="com.example.missedcall">
<!-- 必須權限:讀取電話狀態 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<application ...>
<!-- 高優先級確保優先接收 -->
<receiver
android:name=".CallReceiver"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="1000">
<action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
</receiver>
</application>
</manifest>
| 版本 | 影響 | 應對 |
|---|---|---|
| Android 6.0 (API 23)+ | 執行時權限 | 需動態請求 READ_PHONE_STATE,用戶拒絕則無法監聽 |
| Android 9.0 (API 28)+ | 限制後台接收隱式廣播 | PHONE_STATE 為系統豁免廣播,靜態註冊仍生效 |
| Android 10 (API 29)+ | 限制讀取裝置識別碼/電話號碼 | 來電號碼可能為空,需 READ_CALL_LOG 權限配合 CallLog 查詢 |
| Android 11 (API 30)+ | 前台服務類型限制 | 若需長期監聽,建議改用 Foreground Service + FOREGROUND_SERVICE_PHONE_CALL |
BroadcastReceiver 僅在來電時被喚醒,若 App 進程被殺死可能漏接。生產級方案:
// Manifest
<service android:name=".CallMonitorService"
android:foregroundServiceType="phoneCall"
android:exported="false" />
// CallMonitorService.java
public class CallMonitorService extends Service {
private TelephonyManager tm;
private CallListener listener;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
createNotificationChannel();
startForeground(1, buildNotification());
tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
listener = new CallListener(this);
tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
return START_STICKY;
}
@Override public void onDestroy() {
if (tm != null && listener != null) tm.listen(listener, PhoneStateListener.LISTEN_NONE);
super.onDestroy();
}
// ... Notification 建立略
}
READ_CALL_LOG 權限查詢 CallLog.Calls;雙卡設備可能需 SubscriptionManagerlastState 為 static 變數,進程重啟會重置,可持久化至 SharedPreferencesTelephonyManager.createForSubscriptionId(subId) 區分卡槽原文標題錯誤提示:原標題為「struts2單個文件上傳的兩種完成方法」,但實際內容為 Android 未接來電監聽。本文已依實際內容重寫。