Android拨打电话技术文档

目录

  • [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    }}

实现要点:

  1. 格式化电话号码
  2. 验证号码有效性
  3. 使用 ACTION_DIAL Intent
  4. 设置 FLAG_ACTIVITY_NEW_TASK 标志
  5. 使用 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    }}

实现要点:

  1. 显式检查 CALL_PHONE 权限
  2. 使用 ACTION_CALL Intent
  3. 捕获 SecurityException 处理权限拒绝
  4. 捕获其他异常处理失败情况
智能拨号(推荐方式)
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
维护: 持续更新中...

相关推荐
tongyiixiaohuang17 小时前
WMS已发货状态反写金蝶云星空:单策略实战教程
android
好的佩奇18 小时前
Dart 之类修饰符
android·flutter·dart
alexhilton20 小时前
用Konsist与Lint强制执行设计系统契约
android·kotlin·android jetpack
唐青枫1 天前
Kotlin Delegation 委托详解
kotlin
北京自在科技1 天前
谷歌安卓 Gemini 迎来界面改版,Find Hub 接入 Gemini 支持物品记忆查找
android·安卓
YB13751 天前
Jetpack Compose 动画Modifier.animateItem()
android
2401_885885041 天前
巴林语音通知接口如何对接?系统告警语音API接入教程
android·前端·网络·python·语音识别
收纳过往1 天前
数据库加索引的时机:慢查询日志说话
android·开发语言·数据库·python·低代码·rxjava
狂热开发者1 天前
车载中控要给鸿蒙用户,能不能和 Android 共用一个二维码?
android·华为·harmonyos
.Bryce.1 天前
T2-LLM评测不等于Agent评测
android·java·人工智能·后端·gpt·agent·1024程序员节