Android API29+真机无法通过Kotlin获取SIM手机号与IMSI求助
Android API 29+ 真机无法获取SIM手机号和IMSI的解决方案
核心原因
Android 10(API29)开始对隐私权限做了严格收紧:
- 普通第三方应用无法通过
READ_PHONE_STATE权限获取手机号(line1Number)和IMSI(subscriberId),这些属于高度敏感的用户隐私信息,系统直接限制访问。 READ_PRIVILEGED_PHONE_STATE是系统级权限,仅对系统应用、厂商预装应用开放,普通APP即使在Manifest声明也无法获取授权。
具体解决方案
1. 放弃通过系统获取手机号,改用用户手动输入
这是目前普通APP获取手机号唯一合规且可靠的方式。系统返回的line1Number在很多机型上本来就可能为空(取决于运营商是否写入SIM卡),API29+更是直接无法获取,别依赖这个接口。
2. IMSI的替代方案
API29+普通应用无法获取IMSI,若需标识用户/设备,可改用以下替代方案:
- Android ID:通过
Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)获取,恢复出厂设置会变更,同一设备不同应用的Android ID不同(API29+)。 - 广告ID(Advertising ID):通过Google Play服务获取,用户可手动重置,适合广告追踪场景。
3. 调整现有代码,适配API29+权限限制
修正你代码里的几个问题,并针对API版本做适配:
- 你把
subscriptionId当成IMSI是错误的,subscriptionId是SIM卡的订阅ID,不是IMSI。 - API29+下仅能获取非敏感SIM信息(运营商名称、SIM序列号、卡槽索引等)。
调整后的代码示例:
private var carrierName: String? = "" private var displayName: String? = "" private var slotIndex = 0 private var number: String? = "" private var countryIso: String? = "" private var imsi: String? = "" private var simId: String? = "" init { // 初始化默认值 carrierName = "" displayName = "" slotIndex = 0 number = "" countryIso = "" imsi = "" simId = "" when { Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> { // API29+ 仅能获取非敏感信息 if (subscriptionInfo != null) { carrierName = subscriptionInfo.carrierName.toString() displayName = subscriptionInfo.displayName.toString() slotIndex = subscriptionInfo.simSlotIndex simId = subscriptionInfo.iccId countryIso = if (subscriptionInfo.countryIso?.isNotEmpty() == true) { subscriptionInfo.countryIso } else { telephonyManager.simCountryIso } } else { carrierName = telephonyManager.simOperatorName displayName = telephonyManager.simOperatorName countryIso = telephonyManager.simCountryIso simId = telephonyManager.simSerialNumber } // API29+ 无法获取手机号和IMSI,此处留空或提示用户输入 number = null imsi = null } Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1 && subscriptionInfo != null -> { // API21-API28 逻辑保留,注意subscriptionId不是IMSI carrierName = subscriptionInfo.carrierName.toString() displayName = subscriptionInfo.displayName.toString() slotIndex = subscriptionInfo.simSlotIndex number = subscriptionInfo.number imsi = telephonyManager.subscriberId // 这里才是IMSI simId = subscriptionInfo.iccId countryIso = if (subscriptionInfo.countryIso?.isNotEmpty() == true) { subscriptionInfo.countryIso } else { telephonyManager.simCountryIso } } else -> { // API21以下逻辑保留 carrierName = telephonyManager.simOperatorName displayName = telephonyManager.simOperatorName countryIso = telephonyManager.simCountryIso imsi = telephonyManager.subscriberId simId = telephonyManager.simSerialNumber number = telephonyManager.line1Number.takeIf { it?.isNotEmpty() == true } } } }
4. 权限申请的正确操作
即使API29+拿不到敏感信息,仍需动态申请READ_PHONE_STATE权限才能获取非敏感SIM信息:
// 检查权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { // 动态申请权限 ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_PHONE_STATE), REQUEST_CODE_PHONE_STATE) }
注意事项
- 不要尝试绕过系统权限限制,比如使用反射等手段,这会违反隐私政策,导致APP被Google Play拒审,甚至触发系统安全机制。
- 若你的应用是系统应用/厂商预装应用,可联系厂商获取系统签名,配合
READ_PRIVILEGED_PHONE_STATE权限获取敏感信息,但普通第三方APP无法使用此方式。
内容的提问来源于stack exchange,提问作者php fox
相关产品推荐
相关产品推荐

