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
维护: 持续更新中...

相关推荐
a2591748032-随心所记2 小时前
android studio gradle快速编译配置
android·android studio
一块小土坷垃2 小时前
# 《电影猎手》观影伴侣:一款支持iOS/安卓/电视盒子的全平台影视工具“电影猎手”(附自用评价)
android·ios·电视盒子
敲代码的鱼哇4 小时前
发送短信/拨打电话/获取联系人能力 UTS 插件(cz-sms)
android·前端·ios·uni-app·安卓·harmonyos·鸿蒙
用户5052372099155 小时前
Android 13/14 通知权限与前台服务适配指南
android
用户5052372099155 小时前
Android 12 适配指南:SplashScreen API 与 PendingIntent 变更
android
用户5052372099155 小时前
一张表看懂 Android 8-15 所有适配要点
android
_祝你今天愉快5 小时前
Android 12 (AOSP) 添加自定义系统服务
android
程序员陆业聪7 小时前
AI编码提效实战:Skill、Rule与上下文工程
android
程序员陆业聪8 小时前
AI驱动需求梳理与Spec编写:让PRD自动变成技术方案
android