目录
- [1. 概述](#1. 概述)
- [2. Android电话API](#2. Android电话API)
- [3. 核心功能实现](#3. 核心功能实现)
- [4. 使用示例](#4. 使用示例)
- [5. 技术要点](#5. 技术要点)
- [6. 完整源码](#6. 完整源码)
1. 概述
1.1 功能特性
- ✅ 拨打电话(打开拨号盘、直接拨打、智能拨号)
- ✅ 电话号码格式化和验证
- ✅ 通话状态监听
- ✅ 运营商信息获取
- ✅ 设备电话功能检测
- ✅ 紧急号码识别
1.2 技术栈
- 语言: Kotlin
- SDK: Android API 24+ (Android 7.0+)
- 核心API: TelephonyManager, Intent
1.3 权限要求
xml
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.WRITE_CALL_LOG" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
2. Android电话API
2.1 核心类说明
TelephonyManager
Android电话服务的核心管理类,提供电话状态和运营商信息。
常用方法:
kotlin
// 获取系统服务
val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
// 运营商信息
telephonyManager.networkOperatorName // 网络运营商名称
telephonyManager.simOperatorName // SIM卡运营商名称
telephonyManager.networkCountryIso // 网络国家代码
telephonyManager.simCountryIso // SIM卡国家代码
telephonyManager.line1Number // 本机号码(部分运营商支持)
PackageManager
用于检测设备功能支持情况。
常用常量:
kotlin
// 电话功能检测
PackageManager.FEATURE_TELEPHONY // 检查是否支持电话通信
Intent - 拨打电话
kotlin
// Intent动作
Intent.ACTION_DIAL // 打开拨号盘,无需权限
Intent.ACTION_CALL // 直接拨打电话,需要CALL_PHONE权限
// URI格式
Uri.parse("tel:138****0000") // 电话号码URI
2.2 拨打方式对比
| 方式 | Intent | 权限 | 说明 |
|---|---|---|---|
| 拨号盘 | ACTION_DIAL | 无需权限 | 打开系统拨号盘,用户手动拨打 |
| 直接拨打 | ACTION_CALL | CALL_PHONE | 直接拨打,无需用户确认 |
| 智能拨号 | 动态选择 | 根据权限自动选择 | 优先直接拨打,无权限则打开拨号盘 |
2.3 通话状态
状态值(TelephonyManager常量):
kotlin
TelephonyManager.CALL_STATE_IDLE = 0 // 空闲
TelephonyManager.CALL_STATE_RINGING = 1 // 来电
TelephonyManager.CALL_STATE_OFFHOOK = 2 // 通话中
监听方式:
方式1:PhoneStateListener(Android 11及以下)
kotlin
class MyPhoneStateListener : PhoneStateListener() {
override fun onCallStateChanged(state: Int, phoneNumber: String?) {
when (state) {
TelephonyManager.CALL_STATE_IDLE -> {
// 通话结束或无通话
}
TelephonyManager.CALL_STATE_RINGING -> {
// 来电:phoneNumber 为来电号码
}
TelephonyManager.CALL_STATE_OFFHOOK -> {
// 通话中(去电或接听)
}
}
}
}
// 注册监听
telephonyManager.listen(
phoneStateListener,
PhoneStateListener.LISTEN_CALL_STATE
)
// 取消监听
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE)
方式2:TelephonyCallback(Android 12+)
kotlin
class MyTelephonyCallback : TelephonyCallback(), TelephonyCallback.CallStateListener {
override fun onCallStateChanged(state: Int) {
when (state) {
TelephonyManager.CALL_STATE_IDLE -> {
// 空闲
}
TelephonyManager.CALL_STATE_RINGING -> {
// 来电
}
TelephonyManager.CALL_STATE_OFFHOOK -> {
// 通话中
}
}
}
}
// 注册监听
telephonyManager.registerTelephonyCallback(
context.mainExecutor,
telephonyCallback
)
// 取消监听
telephonyManager.unregisterTelephonyCallback(telephonyCallback)
3. 核心功能实现
3.1 设备检测
检查电话功能支持
kotlin
fun isPhoneSupported(context: Context): Boolean {
return try {
val packageManager = context.packageManager
// 检查是否支持电话通信功能
packageManager.hasSystemFeature(
android.content.pm.PackageManager.FEATURE_TELEPHONY
)
} catch (e: Exception) {
Log.e(TAG, "检查电话支持失败: ${e.message}")
false
}
}
技术要点:
- 使用
PackageManager检查系统功能 FEATURE_TELEPHONY是唯一正确的检测常量- 包装在 try-catch 中处理异常
检查权限状态
kotlin
// 拨打电话权限
fun hasCallPermission(context: Context): Boolean {
return ContextCompat.checkSelfPermission(
context,
Manifest.permission.CALL_PHONE
) == PackageManager.PERMISSION_GRANTED
}
// 读取通话记录权限
fun hasCallLogPermission(context: Context): Boolean {
return ContextCompat.checkSelfPermission(
context,
Manifest.permission.READ_CALL_LOG
) == PackageManager.PERMISSION_GRANTED
}
3.2 电话号码处理
格式化电话号码
kotlin
fun formatPhoneNumber(phoneNumber: String): String {
// 移除所有非数字字符(保留+号)
return phoneNumber.replace(Regex("[^0-9+]"), "")
}
处理规则:
- 保留数字:0-9
- 保留国际区号:+
- 移除其他字符:空格、横线、括号等
示例:
138-0013-8000 → 13800138000
(010) 12345678 → 01012345678
+86 138 0013 8000 → +8613800138000
验证电话号码
kotlin
fun isValidPhoneNumber(phoneNumber: String): Boolean {
val formatted = formatPhoneNumber(phoneNumber)
// 中国手机号:1开头的11位数字
if (formatted.matches(Regex("^1[3-9]\\d{9}$"))) {
return true
}
// 固定电话:3-4位区号+7-8位号码
if (formatted.matches(Regex("^0\\d{2,3}\\d{7,8}$"))) {
return true
}
// 400/800等服务电话
if (formatted.matches(Regex("^[48]00\\d{7}$"))) {
return true
}
// 国际号码:+开头,至少12位数字(含+号)
if (formatted.startsWith("+") && formatted.length >= 13) {
return true
}
return false
}
验证规则:
- 手机号:1开头 + 10位数字 = 11位
- 固话:0开头 + 2-3位区号 + 7-8位号码
- 服务电话:400或800开头 + 7位数字
- 国际号码:+开头 + 至少12位数字
紧急号码识别
kotlin
fun isEmergencyNumber(phoneNumber: String): Boolean { val formatted = formatPhoneNumber(phoneNumber) val emergencyNumbers = listOf( "110", "119", "120", "122", // 中国 "911", "112" // 国际 ) return emergencyNumbers.contains(formatted)}
3.3 拨打电话实现
打开拨号盘(无需权限)
kotlin
fun dialPhoneNumber(phoneNumber: String, showToast: Boolean = true): Boolean { return try { val formatted = formatPhoneNumber(phoneNumber) // 验证号码 if (formatted.isEmpty()) { if (showToast) showToast("电话号码不能为空") return false } if (!isValidPhoneNumber(formatted) && !isEmergencyNumber(formatted)) { if (showToast) showToast("电话号码格式无效") return false } // 创建拨号Intent val intent = Intent(Intent.ACTION_DIAL).apply { data = Uri.parse("tel:$formatted") flags = Intent.FLAG_ACTIVITY_NEW_TASK } context.startActivity(intent) if (showToast) showToast("正在拨打: $formatted") true } catch (e: Exception) { Log.e(TAG, "拨打电话失败: ${e.message}") if (showToast) showToast("拨打电话失败") false }}
实现要点:
- 格式化电话号码
- 验证号码有效性
- 使用
ACTION_DIALIntent - 设置
FLAG_ACTIVITY_NEW_TASK标志 - 使用
tel:URI scheme
直接拨打电话(需要权限)
kotlin
fun callPhoneNumber(phoneNumber: String, showToast: Boolean = true): Boolean { return try { // 检查权限 if (!hasCallPermission(context)) { if (showToast) showToast("需要拨打电话权限") return false } val formatted = formatPhoneNumber(phoneNumber) // 验证号码 if (formatted.isEmpty()) { if (showToast) showToast("电话号码不能为空") return false } if (!isValidPhoneNumber(formatted) && !isEmergencyNumber(formatted)) { if (showToast) showToast("电话号码格式无效") return false } // 创建拨打电话Intent val intent = Intent(Intent.ACTION_CALL).apply { data = Uri.parse("tel:$formatted") flags = Intent.FLAG_ACTIVITY_NEW_TASK } context.startActivity(intent) if (showToast) showToast("正在拨打: $formatted") true } catch (e: SecurityException) { Log.e(TAG, "没有拨打电话权限: ${e.message}") if (showToast) showToast("需要拨打电话权限") false } catch (e: Exception) { Log.e(TAG, "拨打电话失败: ${e.message}") if (showToast) showToast("拨打电话失败") false }}
实现要点:
- 显式检查
CALL_PHONE权限 - 使用
ACTION_CALLIntent - 捕获
SecurityException处理权限拒绝 - 捕获其他异常处理失败情况
智能拨号(推荐方式)
kotlin
fun callPhoneSmart(phoneNumber: String, showToast: Boolean = true): Boolean { return if (hasCallPermission(context)) { // 有权限则直接拨打 callPhoneNumber(phoneNumber, showToast) } else { // 无权限则打开拨号盘 dialPhoneNumber(phoneNumber, showToast) }}
优势:
- 自动适配权限状态
- 有权限时体验更好(直接拨打)
- 无权限时降级为拨号盘
- 无需额外权限检查
3.4 通话状态监听
通话状态枚举
kotlin
enum class CallState { IDLE, // 空闲 RINGING, // 来电 OFFHOOK, // 通话中 DIALING // 拨号中}
监听器接口
kotlin
interface CallStateListener { fun onCallStateChanged(state: CallState, phoneNumber: String?)}
Android 11及以下实现
kotlin
@SuppressLint("MissingPermission")fun startCallStateListener(listener: CallStateListener) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { callStateListener = listener phoneStateListener = object : PhoneStateListener() { override fun onCallStateChanged(state: Int, phoneNumber: String?) { val callState = when (state) { TelephonyManager.CALL_STATE_IDLE -> CallState.IDLE TelephonyManager.CALL_STATE_RINGING -> CallState.RINGING TelephonyManager.CALL_STATE_OFFHOOK -> { // 区分来电还是去电 if (phoneNumber != null) { CallState.RINGING } else { CallState.OFFHOOK } } else -> CallState.IDLE } listener.onCallStateChanged(callState, phoneNumber) } } telephonyManager.listen( phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE ) } else { Log.w(TAG, "Android 12+ 请使用 registerTelephonyCallback") }}
Android 12+实现
kotlin
@SuppressLint("NewApi")fun registerTelephonyCallback(listener: CallStateListener) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { callStateListener = listener val callback = object : TelephonyCallback(), TelephonyCallback.CallStateListener { override fun onCallStateChanged(state: Int) { val callState = when (state) { TelephonyManager.CALL_STATE_IDLE -> CallState.IDLE TelephonyManager.CALL_STATE_RINGING -> CallState.RINGING TelephonyManager.CALL_STATE_OFFHOOK -> CallState.OFFHOOK else -> CallState.IDLE } listener.onCallStateChanged(callState, null) } } telephonyManager.registerTelephonyCallback( context.mainExecutor, callback ) } else { Log.w(TAG, "Android 11及以下 请使用 startCallStateListener") }}
停止监听
kotlin
// Android 11及以下fun stopCallStateListener() { phoneStateListener?.let { telephonyManager.listen(it, PhoneStateListener.LISTEN_NONE) } phoneStateListener = null callStateListener = null}// Android 12+@SuppressLint("NewApi")fun unregisterTelephonyCallback(callback: TelephonyCallback) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { telephonyManager.unregisterTelephonyCallback(callback) } callStateListener = null}
重要注意事项:
- 必须在
onPause()或onDestroy()中取消监听 - Android 12+ 必须使用新API
- 使用
@SuppressLint("MissingPermission")抑制警告(实际需要权限)
3.5 运营商信息获取
获取网络运营商
kotlin
@SuppressLint("MissingPermission")fun getNetworkOperatorName(): String { return try { if (ContextCompat.checkSelfPermission( context, Manifest.permission.READ_PHONE_STATE ) != PackageManager.PERMISSION_GRANTED ) { return "未知" } telephonyManager.networkOperatorName ?: "未知" } catch (e: Exception) { Log.e(TAG, "获取运营商名称失败: ${e.message}") "未知" }}
获取SIM卡运营商
kotlin
@SuppressLint("MissingPermission")fun getSimOperatorName(): String { return try { if (ContextCompat.checkSelfPermission( context, Manifest.permission.READ_PHONE_STATE ) != PackageManager.PERMISSION_GRANTED ) { return "未知" } telephonyManager.simOperatorName ?: "未知" } catch (e: Exception) { Log.e(TAG, "获取SIM运营商名称失败: ${e.message}") "未知" }}
获取国家代码
kotlin
@SuppressLint("MissingPermission")fun getNetworkCountryIso(): String { return try { if (ContextCompat.checkSelfPermission( context, Manifest.permission.READ_PHONE_STATE ) != PackageManager.PERMISSION_GRANTED ) { return "未知" } telephonyManager.networkCountryIso ?: "未知" } catch (e: Exception) { Log.e(TAG, "获取国家代码失败: ${e.message}") "未知" }}
获取SIM卡国家代码
kotlin
@SuppressLint("MissingPermission")fun getSimCountryIso(): String { return try { if (ContextCompat.checkSelfPermission( context, Manifest.permission.READ_PHONE_STATE ) != PackageManager.PERMISSION_GRANTED ) { return "未知" } telephonyManager.simCountryIso ?: "未知" } catch (e: Exception) { Log.e(TAG, "获取SIM国家代码失败: ${e.message}") "未知" }}
获取本机号码
kotlin
@SuppressLint("MissingPermission")fun getPhoneNumber(): String? { return try { if (ContextCompat.checkSelfPermission( context, Manifest.permission.READ_PHONE_STATE ) != PackageManager.PERMISSION_GRANTED ) { return null } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // Android 6.0+ 需要特殊处理 // 注意:大多数运营商不会返回真实号码 null } else { telephonyManager.line1Number } } catch (e: Exception) { Log.e(TAG, "获取手机号码失败: ${e.message}") null }}
重要说明:
- Android 6.0+ 出于隐私考虑,限制了获取本机号码
- 大多数运营商不会返回真实号码
- 建议使用短信验证等方式获取用户手机号
4. 使用示例
4.1 基本拨打电话
kotlin
// 创建工具实例
val callUtils = CallUtils(context)
// 方式1:打开拨号盘(无需权限)
callUtils.dialPhoneNumber("13800138000")
// 方式2:直接拨打电话(需要权限)
callUtils.callPhoneNumber("13800138000")
// 方式3:智能拨号(推荐)
callUtils.callPhoneSmart("13800138000")
4.2 号码处理
kotlin
// 格式化号码
val formatted = CallUtils.formatPhoneNumber("138-0013-8000")
// 结果: "13800138000"
// 验证号码
val isValid = CallUtils.isValidPhoneNumber("13800138000")
// 结果: true
// 检查紧急号码
val isEmergency = CallUtils.isEmergencyNumber("110")
// 结果: true
4.3 监听通话状态
kotlin
val callUtils = CallUtils(context)// 开始监听callUtils.startCallStateListener(object : CallUtils.CallStateListener { override fun onCallStateChanged(state: CallUtils.CallState, phoneNumber: String?) { val stateText = when (state) { CallUtils.CallState.IDLE -> "空闲" CallUtils.CallState.RINGING -> "来电: $phoneNumber" CallUtils.CallState.OFFHOOK -> "通话中" CallUtils.CallState.DIALING -> "拨号中" } Log.i("CallState", stateText) Toast.makeText(context, stateText, Toast.LENGTH_SHORT).show() }})// 在Activity的onPause()或onDestroy()中停止监听override fun onDestroy() { super.onDestroy() callUtils.stopCallStateListener()}
4.4 获取运营商信息
kotlin
val callUtils = CallUtils(context)// 获取网络运营商val networkOperator = callUtils.getNetworkOperatorName()Log.i("Operator", "网络运营商: $networkOperator")// 获取SIM卡运营商val simOperator = callUtils.getSimOperatorName()Log.i("Operator", "SIM运营商: $simOperator")// 获取国家代码val countryIso = callUtils.getNetworkCountryIso()Log.i("Operator", "国家代码: $countryIso")// 获取本机号码val phoneNumber = callUtils.getPhoneNumber()if (phoneNumber != null) { Log.i("Operator", "本机号码: $phoneNumber")} else { Log.i("Operator", "无法获取本机号码")}
4.5 权限处理
kotlin
// 检查权限
val hasPermission = CallUtils.hasCallPermission(context)
if (hasPermission) {
// 有权限,直接拨打
callUtils.callPhoneNumber("13800138000")
} else {
// 无权限,请求权限
ActivityCompat.requestPermissions(
activity,
arrayOf(Manifest.permission.CALL_PHONE),
REQUEST_CALL_PERMISSION
)
}
// 处理权限结果
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
if (requestCode == REQUEST_CALL_PERMISSION) {
if (grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限已授予
callUtils.callPhoneNumber("13800138000")
} else {
// 权限被拒绝
Toast.makeText(this, "需要拨打电话权限", Toast.LENGTH_SHORT).show()
}
}
}
4.6 实际应用示例
从联系人拨打电话
kotlin
fun callContact(contact: Contact) { if (contact.phoneNumbers.isNotEmpty()) { val phoneNumber = contact.phoneNumbers[0].number if (CallUtils.isValidPhoneNumber(phoneNumber)) { // 显示确认对话框 AlertDialog.Builder(context) .setTitle("拨打电话") .setMessage("是否拨打 ${contact.displayName} ($phoneNumber)?") .setPositiveButton("拨打") { _, _ -> callUtils.callPhoneSmart(phoneNumber) } .setNegativeButton("取消", null) .show() } else { Toast.makeText(context, "号码格式无效", Toast.LENGTH_SHORT).show() } } else { Toast.makeText(context, "该联系人没有电话号码", Toast.LENGTH_SHORT).show() }}
批量拨打(测试用)
kotlin
fun batchCallTest(numbers: List<String>) { numbers.forEachIndexed { index, number -> Log.i("BatchCall", "准备拨打 ${index + 1}/${numbers.size}: $number") // 注意:实际应用中不应自动连续拨打 // 这里只是示例 // callUtils.dialPhoneNumber(number) }}
5. 技术要点
5.1 Intent使用注意事项
ACTION_DIAL vs ACTION_CALL
| 特性 | ACTION_DIAL | ACTION_CALL |
|---|---|---|
| 权限 | 无需权限 | 需要CALL_PHONE |
| 用户确认 | 用户手动拨打 | 直接拨打 |
| 适用场景 | 安全、低频 | 高频、自动化 |
| 兼容性 | 所有设备 | 可能受限 |
Intent Flags
kotlin
Intent.ACTION_DIAL.apply {
data = Uri.parse("tel:13800138000")
flags = Intent.FLAG_ACTIVITY_NEW_TASK // 重要:从非Activity启动时需要
}
5.2 权限处理最佳实践
运行时权限请求
kotlin
// 检查权限if (!CallUtils.hasCallPermission(context)) { // 说明为什么需要权限 if (ActivityCompat.shouldShowRequestPermissionRationale( activity, Manifest.permission.CALL_PHONE )) { // 显示解释对话框 AlertDialog.Builder(context) .setTitle("需要拨打电话权限") .setMessage("应用需要拨打电话权限来提供直接拨打功能") .setPositiveButton("确定") { _, _ -> ActivityCompat.requestPermissions( activity, arrayOf(Manifest.permission.CALL_PHONE), REQUEST_CODE ) } .show() } else { // 直接请求权限 ActivityCompat.requestPermissions( activity, arrayOf(Manifest.permission.CALL_PHONE), REQUEST_CODE ) }}
5.3 通话状态监听注意事项
生命周期管理
kotlin
class MainActivity : AppCompatActivity() { private lateinit var callUtils: CallUtils override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) callUtils = CallUtils(this) } override fun onStart() { super.onStart() // 开始监听通话状态 callUtils.startCallStateListener(callStateListener) } override fun onPause() { super.onPause() // 停止监听,避免内存泄漏 callUtils.stopCallStateListener() } private val callStateListener = object : CallUtils.CallStateListener { override fun onCallStateChanged(state: CallUtils.CallState, phoneNumber: String?) { // 处理状态变化 } }}
Android版本兼容
kotlin
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Android 12+ callUtils.registerTelephonyCallback(callStateListener)} else { // Android 11及以下 callUtils.startCallStateListener(callStateListener)}
5.4 错误处理
完善的异常处理
kotlin
fun safeCall(phoneNumber: String): Boolean { return try { // 1. 检查设备支持 if (!CallUtils.isPhoneSupported(context)) { showToast("设备不支持电话功能") return false } // 2. 验证号码 if (!CallUtils.isValidPhoneNumber(phoneNumber)) { showToast("电话号码格式无效") return false } // 3. 检查权限 if (!CallUtils.hasCallPermission(context)) { showToast("需要拨打电话权限") return false } // 4. 执行拨号 callUtils.callPhoneNumber(phoneNumber) } catch (e: SecurityException) { Log.e("Call", "权限异常: ${e.message}") showToast("需要拨打电话权限") false } catch (e: Exception) { Log.e("Call", "未知异常: ${e.message}") showToast("拨打电话失败") false }}
5.5 性能优化
避免重复创建实例
kotlin
// 好的做法 - 单例或保持引用
class MainActivity : AppCompatActivity() {
private val callUtils: CallUtils by lazy { CallUtils(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 使用 callUtils
callUtils.callPhoneSmart("13800138000")
}
}
// 不好的做法 - 每次都创建新实例
class MainActivity : AppCompatActivity() {
fun makeCall(phoneNumber: String) {
val callUtils = CallUtils(this) // 每次都创建
callUtils.callPhoneSmart(phoneNumber)
}
}
5.6 安全考虑
紧急号码特殊处理
kotlin
fun callEmergency(number: String) { if (CallUtils.isEmergencyNumber(number)) { // 紧急号码直接拨打,不需要确认 callUtils.callPhoneNumber(number) } else { // 普通号码显示确认对话框 showCallConfirmation(number) }}
防止误拨
kotlin
fun callWithConfirmation(phoneNumber: String) { // 格式化并验证号码 val formatted = CallUtils.formatPhoneNumber(phoneNumber) if (!CallUtils.isValidPhoneNumber(formatted)) { Toast.makeText(context, "号码格式无效", Toast.LENGTH_SHORT).show() return } // 显示确认对话框 AlertDialog.Builder(context) .setTitle("确认拨打电话") .setMessage("即将拨打 $formatted\n\n是否确认?") .setPositiveButton("拨打") { _, _ -> callUtils.callPhoneSmart(formatted) } .setNegativeButton("取消", null) .setCancelable(true) .show()}
6. 完整源码
6.1 CallUtils.kt
参见项目文件:app/src/main/java/com/zh/systemtest/utils/CallUtils.kt
6.2 CallUtilsExample.kt
参见项目文件:app/src/main/java/com/zh/systemtest/utils/CallUtilsExample.kt
6.3 AndroidManifest.xml
xml
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 电话相关权限 --> <uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.READ_CALL_LOG" /> <uses-permission android:name="android.permission.WRITE_CALL_LOG" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/Theme.SystemTest"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application></manifest>
附录
A. 常见问题
Q1: 为什么获取不到本机号码?
A: Android 6.0+ 出于隐私考虑,限制了获取本机号码的功能。大多数运营商不会返回真实号码。建议使用短信验证等方式获取用户手机号。
Q2: ACTION_DIAL 和 ACTION_CALL 有什么区别?
A: ACTION_DIAL 打开拨号盘,需要用户手动点击拨打,无需权限;ACTION_CALL 直接拨打电话,需要 CALL_PHONE 权限。
Q3: 通话状态监听不工作?
A: 确保已申请 READ_PHONE_STATE 权限,并且根据 Android 版本使用正确的 API(PhoneStateListener 或 TelephonyCallback)。
Q4: 如何区分来电和去电?
A: PhoneStateListener 的 onCallStateChanged 方法中,来电时 phoneNumber 参数不为 null;去电时 phoneNumber 为 null。
Q5: 设备不支持电话功能怎么办?
A: 使用 CallUtils.isPhoneSupported() 检查,如果返回 false,应该隐藏拨打电话相关功能或提示用户。
B. 权限说明
| 权限 | 用途 | 必需性 |
|---|---|---|
| CALL_PHONE | 直接拨打电话 | 直接拨打时必需 |
| READ_CALL_LOG | 读取通话记录 | 可选 |
| WRITE_CALL_LOG | 写入通话记录 | 可选 |
| READ_PHONE_STATE | 读取电话状态、运营商信息 | 获取运营商信息时必需 |
C. 运营商代码参考
中国移动:
- MCC: 460
- MNC: 00, 02, 07
- 运营商名:中国移动
中国联通:
- MCC: 460
- MNC: 01, 06, 09
- 运营商名:中国联通
中国电信:
- MCC: 460
- MNC: 03, 05, 11
- 运营商名:中国电信
文档版本 : 1.0
最后更新 : 2026-04-18
作者 : zh
维护: 持续更新中...