-
添加权限:
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
,6.0以上需要动态申请。如果是小米手机的无法申请,需要手动到设置中允许权限。 -
注册
"android.intent.action.SIM_STATE_CHANGED"
广播registerReceiver(receiver, IntentFilter("android.intent.action.SIM_STATE_CHANGED"))
-
获取intent中的ss字段
class SimCardReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { intent.extras?.keySet()?.forEach { key -> Timber.i("$key = ${intent.extras?.get(key)}") } val ss = intent.getStringExtra("ss") when (ss) { "LOADED" -> Timber.i("有sim卡") "ABSENT" -> Timber.i("没有sim卡") } } }
在我的小米手机上,插入的双卡,卡1电信,卡2联通,运行结果如下:
android.telephony.extra.SUBSCRIPTION_INDEX = 1
slot_id = 1
subscription_id = 1
phone_id = 1
android.telephony.extra.SLOT_INDEX = 1
phoneName = Phone
reason = null
rebroadcastOnUnlock = true
ss = LOADED
phone = 1
subscription = 1
有sim卡
这是一个粘性广播,一注册就会立马收到广播,返回的结果中SLOT_INDEX为1,索引是从0开始的,说明这是返回的卡2的信息。
此时我将卡拨出,两个卡都会被同时拨出,结果如下:
slot_id = 0
subscription_id = -1
phone_id = 0
android.telephony.extra.SLOT_INDEX = 0
phoneName = Phone
reason = null
ss = ABSENT
phone = 0
没有sim卡
slot_id = 1
subscription_id = -1
phone_id = 1
android.telephony.extra.SLOT_INDEX = 1
phoneName = Phone
reason = null
ss = ABSENT
phone = 1
没有sim卡
这里可以看到卡1卡2同时被拨出来了。
我再把两张卡同时插回去:
android.telephony.extra.SUBSCRIPTION_INDEX = 1
slot_id = 1
subscription_id = 1
phone_id = 1
android.telephony.extra.SLOT_INDEX = 1
phoneName = Phone
reason = null
ss = NOT_READY
phone = 1
subscription = 1
android.telephony.extra.SUBSCRIPTION_INDEX = 2
slot_id = 0
subscription_id = 2
phone_id = 0
android.telephony.extra.SLOT_INDEX = 0
phoneName = Phone
reason = null
ss = NOT_READY
phone = 0
subscription = 2
android.telephony.extra.SUBSCRIPTION_INDEX = 2
slot_id = 0
subscription_id = 2
phone_id = 0
android.telephony.extra.SLOT_INDEX = 0
phoneName = Phone
reason = null
ss = READY
phone = 0
subscription = 2
android.telephony.extra.SUBSCRIPTION_INDEX = 1
slot_id = 1
subscription_id = 1
phone_id = 1
android.telephony.extra.SLOT_INDEX = 1
phoneName = Phone
reason = null
ss = READY
phone = 1
subscription = 1
android.telephony.extra.SUBSCRIPTION_INDEX = 2
slot_id = 0
subscription_id = 2
phone_id = 0
android.telephony.extra.SLOT_INDEX = 0
phoneName = Phone
reason = null
ss = LOADED
phone = 0
subscription = 2
有sim卡
android.telephony.extra.SUBSCRIPTION_INDEX = 1
slot_id = 1
subscription_id = 1
phone_id = 1
android.telephony.extra.SLOT_INDEX = 1
phoneName = Phone
reason = null
ss = LOADED
phone = 1
subscription = 1
有sim卡
这里收到了6条广播,每张卡3条广播,每张卡的ss分别经历3个状态,如下:
NOT_READY
READY
LOADED
当把卡拨掉的时候它返回的是ABSENT
,所以可以认为如果是ABSENT
就是无卡,其它状态就是有卡。
两张卡已经插入的情况下,此时再注册广播只会收到卡2的一条广播,ss为LOADED
,为什么会这样我也搞不清楚。所以,这种方式适合判断单卡的设备是否插入了sim卡,如果是双卡设备,你就无法判断是否插入了双卡,因为当你注册的时候它只接收一条卡的广播信息,此时你拨卡再插卡的时候才会收到两张卡的广播信息。 所以它只适合判断单卡,只要你一注册你就能获得那个卡槽的信息,无论是否当前有卡还是无卡。
在双卡的情况下,插入了两张卡,拨卡的时候能收到两张卡的广播信息,但是,当我注册的时候,如果双卡之前就已经拨掉了,则此时我只收到一条广播,如下:
slot_id = 1
subscription_id = -1
phone_id = 1
android.telephony.extra.SLOT_INDEX = 1
phoneName = Phone
reason = null
ss = ABSENT
phone = 1
没有sim卡
可以看到,收到的是卡2的信息。
"android.intent.action.SIM_STATE_CHANGED"
实际上为Intent.ACTION_SIM_STATE_CHANGED
,但它是系统API,我们无法直接调用,所以只能直接使用它的值。查看该常量已经过期,推荐使用TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED
或TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED
,这两也是系统API,所以只能使用它们的值来注册。
首先看TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED
的源码,它的值为"android.telephony.action.SIM_CARD_STATE_CHANGED"
,收到广播后,它通过TelephonyManager.EXTRA_SIM_STATE
来获取状态,状态包括:
- SIM_STATE_ABSENT SIM card not found
- SIM_STATE_CARD_IO_ERROR SIM card IO error
- SIM_STATE_CARD_RESTRICTED SIM card is restricted
- SIM_STATE_PRESENT SIM card is present
Requires the READ_PRIVILEGED_PHONE_STATE permission. The current state can also be queried using getSimCardState(). This is a protected intent that can only be sent by the system.
通过阅读API文档可知,这要求系统App才能收到这个广播,因为只有系统APP才能申请READ_PRIVILEGED_PHONE_STATE
权限。且这个SIM卡的状态也可通过TelephonyManager.getSimCardState()
来获取。
对于TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED
,它获取的状态有:
- SIM_STATE_NOT_READY SIM card applications not ready
- SIM_STATE_PIN_REQUIRED SIM card PIN locked
- SIM_STATE_PUK_REQUIRED SIM card PUK locked
- SIM_STATE_NETWORK_LOCKED SIM card network locked
- SIM_STATE_PERM_DISABLED SIM card permanently disabled due to PUK failures
- SIM_STATE_LOADED SIM card data loaded
该广播同样需要系统级App才能接收。该状态可通过TelephonyManager.getSimApplicationState()
函数获取。
通过查看源码,发现TelephonyManager
中还有一个ACTION_SIM_SLOT_STATUS_CHANGED = "android.telephony.action.SIM_SLOT_STATUS_CHANGED"
广播,对应状态可使用getUiccSlotsInfo()
函数获取。
经过试验,我发现即使我的应用有系统签名,也无法接收TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED
和TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED
这两个广播。运行的是在Android7.1.1版本,不知道是否是系统版本的问题,有时间再Android11中再试一下。
现在能肯定的是第一种方式是OK,即便在我的小米11pro(Android13)中依旧可以监听到sim卡状态的变化,完整示例代码如下:
class SimCardReceiver : BroadcastReceiver() {
private lateinit var callback: (Boolean) -> Unit
override fun onReceive(context: Context, intent: Intent) {
val noCard = intent.getStringExtra("ss") == "ABSENT"
callback(noCard)
}
companion object {
private const val ACTION_SIM_STATE_CHANGED: String = "android.intent.action.SIM_STATE_CHANGED" // 等于:Intent.ACTION_SIM_STATE_CHANGED
private var isRegister = false
private val receiver: SimCardReceiver by lazy { SimCardReceiver() }
fun register(context: Context, action: String,callback: (Boolean) -> Unit) {
receiver.callback = callback
unregister(context)
context.registerReceiver(receiver, IntentFilter(ACTION_SIM_STATE_CHANGED))
isRegister = true
}
fun unregister(context: Context) {
if (isRegister) {
context.unregisterReceiver(receiver)
isRegister = false
}
}
}
}
调用:
SimCardReceiver.register(this) { noCard ->
Timber.i(if (noCard) "无卡" else "有卡")
}
SimCardReceiver.unregister(this)