Android 10无需自定义拨号器获取呼出电话号码的技术问询
Android 10+ 非默认拨号应用获取呼出号码的替代方案
Android 10(API 29)开始,PROCESS_OUTGOING_CALLS权限被弃用,系统不再允许非默认拨号应用通过ACTION_NEW_OUTGOING_CALL广播或PhoneStateListener的onCallStateChanged直接获取呼出电话号码——这就是你当前代码中EXTRA_PHONE_NUMBER和回调参数为空的核心原因。以下是两种可行的替代方案:
方案一:读取通话记录(CallLog)
通过读取系统通话记录,在呼出电话结束后获取最近的呼出号码,无需用户设置默认拨号应用,但只能在通话完成后触发deeplink。
步骤:
- 添加权限:在Manifest中新增
READ_CALL_LOG权限 - 监听通话状态:在通话从接通(OFFHOOK)变为空闲(IDLE)时,查询最近的呼出通话记录
- 匹配号码并跳转:获取号码后判断是否为目标号码,触发deeplink
代码修改:
Manifest权限补充:
<uses-permission android:name="android.permission.READ_CALL_LOG" />
修改OutgoingCallService中的PhoneStateListener:
@SuppressLint("MissingPermission") private fun createPhoneStateListener(): PhoneStateListener { return object : PhoneStateListener() { private var lastCallState = TelephonyManager.CALL_STATE_IDLE override fun onCallStateChanged(state: Int, phoneNumber: String?) { super.onCallStateChanged(state, phoneNumber) when (state) { TelephonyManager.CALL_STATE_OFFHOOK -> { lastCallState = state } TelephonyManager.CALL_STATE_IDLE -> { // 仅在通话结束时触发查询 if (lastCallState == TelephonyManager.CALL_STATE_OFFHOOK) { getRecentOutgoingNumber()?.let { number -> if (number == "SPECIFIC PHONE NUMBER") { redirectToApp() } } } lastCallState = state } } } private fun getRecentOutgoingNumber(): String? { val projection = arrayOf(CallLog.Calls.NUMBER, CallLog.Calls.TYPE) val selection = "${CallLog.Calls.TYPE} = ?" val selectionArgs = arrayOf(CallLog.Calls.OUTGOING_TYPE.toString()) // 按时间倒序取最近一条记录 val sortOrder = "${CallLog.Calls.DATE} DESC" contentResolver.query( CallLog.Calls.CONTENT_URI, projection, selection, selectionArgs, sortOrder )?.use { cursor -> if (cursor.moveToFirst()) { val numberIndex = cursor.getColumnIndex(CallLog.Calls.NUMBER) return cursor.getString(numberIndex) } } return null } } }
方案二:使用AccessibilityService辅助功能
通过辅助功能监听系统拨号界面的文本输入,实时获取用户输入的号码,可在拨号时触发跳转,但需要用户手动开启辅助功能(属于敏感权限,需向用户明确说明用途)。
步骤:
- 创建AccessibilityService:监听拨号应用的文本变化事件
- 注册服务:在Manifest中配置服务及权限
- 获取并存储号码:提取拨号界面输入的号码,供后续服务使用
- 监听通话状态触发跳转:结合
PhoneStateListener,在通话接通时匹配号码并跳转
代码示例:
创建CallAccessibilityService:
class CallAccessibilityService : AccessibilityService() { override fun onAccessibilityEvent(event: AccessibilityEvent) { // 仅监听文本变化事件,且过滤系统拨号应用包名 if (event.eventType == AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED && event.packageName == "com.android.dialer") { val inputNumber = event.text.joinToString("") // 将号码存储到SharedPreferences,供后续使用 getSharedPreferences("CallData", MODE_PRIVATE).edit() .putString("pending_outgoing_number", inputNumber) .apply() } } override fun onInterrupt() {} }
Manifest注册服务:
<service android:name=".CallAccessibilityService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_service_config" /> </service>
辅助功能配置文件(res/xml/accessibility_service_config.xml):
<?xml version="1.0" encoding="utf-8"?> <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:accessibilityEventTypes="typeViewTextChanged" android:accessibilityFeedbackType="feedbackGeneric" android:accessibilityFlags="flagDefault" android:canRetrieveWindowContent="true" android:description="@string/accessibility_desc" android:packageNames="com.android.dialer" />
修改OutgoingCallService获取存储的号码:
@SuppressLint("MissingPermission") private fun createPhoneStateListener(): PhoneStateListener { return object : PhoneStateListener() { override fun onCallStateChanged(state: Int, phoneNumber: String?) { super.onCallStateChanged(state, phoneNumber) if (state == TelephonyManager.CALL_STATE_OFFHOOK) { val prefs = getSharedPreferences("CallData", MODE_PRIVATE) val targetNumber = prefs.getString("pending_outgoing_number", "") if (targetNumber == "SPECIFIC PHONE NUMBER") { redirectToApp() } } } } }
方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| 读取通话记录 | 无需用户额外开启复杂权限 | 只能在通话结束后获取号码,无法实时触发 |
| 辅助功能 | 实时获取号码,可提前触发跳转 | 需要用户手动开启辅助功能,敏感权限需说明 |
内容的提问来源于stack exchange,提问作者frederyk
相关产品推荐
相关产品推荐

