🇮🇷苏莱曼尼遇刺,竟是由于这个....

最近伊朗的少将苏莱曼尼遇刺,被全网称为2020年第一只黑天鹅事件。人们为美国没有夺走其余无辜生命状况下,精准打击目标的能力感到震惊。毕竟上一个有记载能作到“在百万军中取上将首级如探囊取物”的人是我们的张飞大哥。java

我也闲来无事,打开万年不开的微博,看到一条微博内容:android

忽然感受虎躯一震,在现在的科技时代,掌握科技的制高点和核心研发能力是多么的重要。不过,若是对于这号人物真经过IMEI就能简单的定位的话,或许他们须要一台装了xposed的安卓手机。好像发现了商机?git

在震惊之余,我拿出恰好这周复习的安卓设备ID信息和Sim卡信息的笔记,认真复习了一番。github

下面是Demo的运行结果:api

IMEI和MEID

IMEI和MEID都是移动设备的身份识别码。相似于设备的身份证。他们的区别以下:markdown

  • IMEI:国际移动设备识别码International Mobile Equipment IdentityIMEI),即一般所说的手机“串号”,用于在移动电话网络中识别每一部独立的手机等移动通讯设备。序列号共有15位数字。
  • MEID:移动设备识别码(Mobile Equipment Identifier)是CDMA手机的身份识别码。序列号共有14位数字。

在安卓8.0如下手机,咱们经过APITelephonyManager.getDeviceId()获取插有电信运营商SIM卡的卡槽时,默认返回MEID号;移动和联通运营商SIM卡的卡槽返回IMEI号。网络

获取IMEI或MEID

在Android 8.0以上的系统,TelephonyManager废弃了getDeviceId()方法,提供了两个独立的API来准确的获取IMEI和MEID:getImei()getMeid()。两个API均可以支持传入slotIndex来获取对应位置的IMEI和MEID。jsp

在Android8.0如下的系统,TelephonyManager经过getDeviceId()来获取IMEI或MEID;且在Android 6.0系统及以上系统,getDeviceId(slotIndex)支持经过传入slotIndex来获取对应位置的IMEI和MEID,来适应一机双卡的状况。当存在电信SIM卡时,getDeviceId()方法优先返回MEID,当移动和联通SIM卡时,返回IMEI。ide

卡1 卡2 getDeviceId(0) getDeviceId(1)
无/非电信卡 无/非电信卡 IMEI IMEI
电信卡 无/非电信卡 MEID IMEI
无/非电信卡 电信卡 IMEI MEID
/** * 获取IMEI或MEID */
    fun getIMEIORMEID(){
        //检查是否有READ_PHONE_STATE权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
            == PackageManager.PERMISSION_GRANTED){
            //获取IMEI和MEID
            val tm = getSystemService<TelephonyManager>()
            //getDeviceId()从API1就已经存在,默认返回卡槽一的IMEI或MEID
            var imei: String? = tm?.getDeviceId()
            //getDeviceId(solotIndex)在API23加入能够经过指定卡槽位置获取IMEI或MEID
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                //solotIndex:0 -> 卡槽1 IMEI或MEID
                tvIMEI1.text = "卡槽一 IMEI或MEID:${tm?.getDeviceId(0)}"
                //solotIndex:1 -> 卡槽2 IMEI或MEID
                tvIMEI2.text = "卡槽二 IMEI或MEID:${tm?.getDeviceId(1)}"
            }
        }
    }

    /** * 获取MEID */
    fun getMEID(){
        //检查是否有READ_PHONE_STATE权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
            == PackageManager.PERMISSION_GRANTED){
            //获取IMEI和MEID
            val tm = getSystemService<TelephonyManager>()
            //获取MEID在API26加入
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                //返回默承认用卡槽的MEID
                var meid:String? = tm?.meid
                //能够经过指定卡槽获取MEID
                tvMEID.text = "MEID:${tm?.getMeid(0)}"
            }
        }
    }
复制代码

MAC地址

MAC地址(Media Access Control Address),它是一个用来确认网络设备位置的地址。MAC地址用于在网络中惟一标示一个网卡,一台设备若一个或多个网卡,则每一个网卡都存在一个惟一的MAC地址。工具

通常会将IMEI或MEID结合MAC地址做为设备的惟一标识。

MAC地址在Android中不一样版本的获取方式也不太同样。你们能够自行Google。这里就再也不赘述。后续的Demo中也会给出代码。

IMSI和ICCID

IMSI

国际移动用户识别码(International Mobile Subscriber Identity),是用于区分蜂窝网络中不一样用户的、在全部蜂窝网络中不重复的识别码。

获取IMSI

咱们能够经过TelephonyManager.getSubscriberId()获取SIM卡的IMSI,该API在SDK API 1就已经存在,但若是在双卡双待的手机上有多个卡槽的状况下,咱们想获取对应卡槽的IMSI,则须要调用TelephonyManager.getSubscriberId(int subId) 方法,传入对应卡槽的subId,0表明卡槽1,1表明卡槽2,该方法在SDK API 21加入,但在SDK API 29及以上则没法再调用,但咱们目前能够经过反射的方式去调用。

在这里安利一个工具:对于一个API,在各个Android系统的源码,咱们能够在http://androidxref.com/ 这个网站进行查看。

咱们经过这个网站搜索查看,还会发如今SDK API 21加入时,TelephonyManager.getSubscriberId(long subId)它的入参是Long类型,在以后的版本都是int类型。因此在版本兼容的时候,咱们若是是反射调用时,可能须要注意参数类型。

SDK API getSubscriberId()
1~20 getSubscriberId()
21 getSubscriberId()或getSubscriberId(long subId)
22~28 getSubscriberId()或getSubscriberId(int subId)
29及以上 暴露getSubscriberId(),但getSubscriberId(int subId)方法再也不暴露
/** * 经过反射调用 */
    fun getIMSI(){
        //检查是否有READ_PHONE_STATE权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
            == PackageManager.PERMISSION_GRANTED){
            //获取IMSI
            val tm = getSystemService<TelephonyManager>()
            //在SDKAPI 1就存在getSubscriberId()方法,返回默认卡槽IMSI
            var imsi = tm?.subscriberId
            //在SDKAPI 21加入getSubscriberId(subId)来指定返回卡槽位置IMSI
            //在SDKAPI 29以上,指定卡槽位置的方法再也不暴露,但依旧能经过反射来获取
            tvIMSI1.text = "卡槽一 IMSI:${getReflectMethod(this,"getSubscriberId",0) as CharSequence?}"
            tvIMSI2.text = "卡槽二 IMSI:${getReflectMethod(this,"getSubscriberId",1) as CharSequence?}"

        }
    }


    fun getReflectMethod(context: Context, method: String, param: Int): Any? {
        val telephony = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        try {
            val telephonyClass = Class.forName(telephony.javaClass.name)
            val parameter = arrayOfNulls<Class<*>>(1)
            parameter[0] = Int::class.javaPrimitiveType
            val getSimState = telephonyClass.getMethod(method, *parameter)
            val ob_phone = getSimState.invoke(telephony, param)

            if (ob_phone != null) {
                return ob_phone
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }

        return null
    }
复制代码

上面的代码展现了如何经过反射获取IMSI。在获取到IMSI以后,咱们能够经过IMSI的开头五位数来区别移动用户所属的国家和运营商。

在中国IMSI的开头三位为460,以后的两位表明中国的运营商信息:

运营商 MNC号码
移动 00, 02,04, 07,08
联通 01, 06,09
电信 03, 05
电信4G 11
铁通 20

ICCID

SIM卡卡号,是卡的标识,不做接入网络的鉴权认证,可在SIM卡卡后查询到。格式:大多为19或20位0-9的数字,亦存在6位/12位的状况。

获取ICCID

获取ICCID有两种方式:

  • 经过TelephonyManager的getSimSerialNumber()方法来获取ICCID,在SDK API 21加入了getSubscriberId(subId)来指定获取对应卡槽位置的ICCID;可是和IMSI同样,在SDK API 29及以上,getSubscriberId(subId)的方法再也不暴露外部调用,但咱们能够经过反射来调用。
  • 经过SubscriptionManager的getActiveSubscriptionInfoList()方法来获取SubscriptionInfo列表,这个列表包含了有效的SIM卡的信息,若是有多个SIM卡,则会返回多个SubscriptionInfo对象。每一个SubscriptionInfo对象表明一个有效的SIM卡。经过循环遍历SubscriptionInfo列表,调用SubscriptionInfogetIccId()的方法,能够获取ICCID的值,可是SubscriptionManager在SDk API 22才被加到SDK中。
/** * 经过反射调用 */
    fun getReflectMethod(context: Context, method: String, param: Int): Any? {
        val telephony = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        try {
            val telephonyClass = Class.forName(telephony.javaClass.name)
            val parameter = arrayOfNulls<Class<*>>(1)
            parameter[0] = Int::class.javaPrimitiveType
            val getSimState = telephonyClass.getMethod(method, *parameter)
            val ob_phone = getSimState.invoke(telephony, param)

            if (ob_phone != null) {
                return ob_phone
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }

        return null
    }

    /** * 经过TelephonyManager获取CCID */
    fun getICCIDByTM(){
        //检查是否有READ_PHONE_STATE权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
            == PackageManager.PERMISSION_GRANTED){
            //获取IMSI
            val tm = getSystemService<TelephonyManager>()
            //在SDKAPI 1就存在getSubscriberId()方法,返回默认卡槽IMSI
            var iccid = tm?.simSerialNumber
            //在SDKAPI 21加入getSubscriberId(subId)来指定返回卡槽位置IMSI
            //在SDKAPI 29以上,指定卡槽位置的方法再也不暴露,但依旧能经过反射来获取
            tvICCID1.text = "卡槽一 ICCID:${getReflectMethod(this,"getSimSerialNumber",0) as CharSequence?}"
            tvICCID2.text = "卡槽二 ICCID:${getReflectMethod(this,"getSimSerialNumber",1) as CharSequence?}"

        }
    }

    /** * 经过SubscriptionManager获取CCID SDK API 22及以上使用 */
    fun getICCIDBySM(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
            val mSubscriptionManager = getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
            //检查是否有READ_PHONE_STATE权限
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
                == PackageManager.PERMISSION_GRANTED){
                //获取当前有效的SIM卡列表
                var subInfos = mSubscriptionManager.activeSubscriptionInfoList
                for ((index,item) in subInfos.withIndex()){
                    when(index){
                        0->tvICCID1.text = "卡槽一 ICCID:${item.iccId}"
                        1->tvICCID2.text = "卡槽二 ICCID:${item.iccId}"
                    }
                }
            }
        }
    }
复制代码

SN

SN码是Serial Number的缩写,是产品的序列号,主要为了验证"产品的合法性",主要用来保护用户的正版权益。通常是不可改变的。咱们能够经过adb devices命令来查看。

获取SN

咱们在SDK API 26如下,能够直接经过Build.SERIAL进行获取,在SDK API 26以上则须要READ_PHONE_STATE权限,调用Build.getSerial()方法进行获取。

/** * 获取SN */
    fun getSN(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
                == PackageManager.PERMISSION_GRANTED){
                // 须要READ_PHONE_STATE权限
                tvSN.text = "SN:${Build.getSerial()}"
            }
        }else{
            tvSN.text = "SN:${Build.SERIAL}"
        }
    }

复制代码

ANDROID ID

设备首次启动随机生成的ID,设备还原出厂设置后或系统升级后会从新生成 格式:长度为16位的字符串,但因为生产厂商定制系统的Bug,有的设备会生成相同的ID,或者返回null

获取ANDROID ID

经过Settings.Secure.getString(this.getContentResolver(), Settings.Secure.ANDROID_ID便可获取ANDROID ID

/** * 获取AndroidId */
    fun getAndroidId(){
        tvAndroidId.text = "Android ID:${Settings.Secure.getString(this.getContentResolver(), Settings.Secure.ANDROID_ID)}"
    }

复制代码

Android10 AndroidId

在Android10已经没法获取设备硬件相关的Id,例如IMEI、MAC地址等。若是代码中获取这方面的信息,则会给你抛出一个大写的SecurityException错误。

受影响的API为:

  • Build

    getSerial()

  • TelephonyManager

    getImei()

    getDeviceId()

    getMeid()

    getSimSerialNumber()

    getSubscriberId()

在Android10,除非系统应用或者特殊的应用商应用READ_PRIVILEGED_PHONE_STATE才能访问以上的API。这就意味这Android ID的许多获取方法在Android10基本GG。

Google为咱们提出了Android10获取软件范畴的ID的方法。具体参考文章:惟一标识符最佳作法

但这方案不能解决咱们不少场景:须要获取设备的惟一不可更改的ID,例如:风控、推送和用户画像等领域。但不用担忧,咱们伟大的祖国厂商系统,为咱们提供了统一的OAID(匿名设备标识符)来替代IMEI的做用。具体参考文档:OAID文档

SIM卡相关拓展

最后再补充几点与SIM卡相关,但和Android设备信息无关的内容。主要是整理了一下如何检测一台设备当前插入了几张SIM卡、当前手机最多支持几张Sim卡和获取当前使用蜂窝网络的SIM卡。

检测当前手机插入几张SIM卡

获取当前手机插入几张SIM卡也有两个途径,一个是经过TelephonyManager的getSimState()方法获取,另外一个是经过SubscriptionManager获取

  • TelephonyManager在SDK API 1就加入了``getSimState()方法获取默认的Sim卡状态;在SDK API 21加入了getSimState(int slotId)方法,获取对应卡槽的Sim卡状态,可是该方法倒是经过注解@hide`修饰的,因此没有对外暴露方法,外部没法调用,但能够经过反射进行调用;在SDK API 26才对外暴露。
SDK API getSubscriberId()
1~20 getSimState()
21~26 getSimState()或反射调用getSimState(int slotId)
26以上 getSimState()或getSimState(int slotId)

Sim卡状态表:

状态 状态码 解释
SIM_STATE_UNKNOWN 0 SIM卡状态:未知
SIM_STATE_ABSENT 1 SIM卡状态:设备中没有SIM卡
SIM_STATE_PIN_REQUIRED 2 SIM卡状态:锁定:要求用户的SIM卡PIN解锁
SIM_STATE_PUK_REQUIRED 3 SIM卡状态:锁定:要求用户的SIM PUK解锁
SIM_STATE_NETWORK_LOCKED 4 SIM卡状态:锁定:须要网络PIN才能解锁
SIM_STATE_READY 5 SIM卡状态:就绪
SIM_STATE_NOT_READY 6 SIM卡状态:SIM卡还没有就绪
SIM_STATE_PERM_DISABLED 7 SIM卡状态:SIM卡错误,已永久禁用
SIM_STATE_CARD_IO_ERROR 8 SIM卡状态:SIM卡错误,存在但有故障
SIM_STATE_CARD_RESTRICTED 9 SIM卡状态:SIM卡受限,存在,但因为运营商的限制而没法使用
  • SubscriptionManager在SDK API 22才被加入SDK API中。咱们在SDK API 22及以上能够经过SubscriptionManager.getActiveSubscriptionInfoCount()方法获取有效的Sim卡个数。但这个方法须要READ_PHONE_STATE用户权限。

所以结合TelephonyManager.getSimState()SubscriptionManager.getActiveSubscriptionInfoCount()方法,我的整理了一个获取当前手机SIM卡的方法。

/** * 获取Sim卡个数 * @param context * @return */
    fun checkSimCount(context: Context): Int {
        var simCount: Int
        try {
            //若是SDK Version >=22 && 拥有Manifest.permission.READ_PHONE_STATE 权限
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1 && ContextCompat.checkSelfPermission(
                    context,
                    Manifest.permission.READ_PHONE_STATE
                ) == PackageManager.PERMISSION_GRANTED
            ) {
                simCount = getSimCountBySubscriptionManager(context)
                if (simCount == -1) {
                    //若是SubscriptionManager获取失败,则TelephonyManager尝试获取
                    simCount = getSimCountByTelephonyManager(context)
                }
            } else {
                simCount = getSimCountByTelephonyManager(context)
            }

        } catch (e: Exception) {
            simCount = getSimCountByTelephonyManager(context)
        }

        return simCount
    }


    /** * 经过SubscriptionManager获取Sim卡张数,若是获取失败返回-1 * @param context * @return */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
    @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
    private fun getSimCountBySubscriptionManager(context: Context): Int {
        try {
            val subscriptionManager =
                context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
            return subscriptionManager?.activeSubscriptionInfoCount ?: -1
        } catch (e: Throwable) {
            return -1
        }

    }

    /** * 经过TelephonyManager反射获取Sim卡张数 * @param context * @return */
    private fun getSimCountByTelephonyManager(context: Context): Int {
        val slotOne = getSimStateBySlotIdx(context, 0)
        val slotTwo = getSimStateBySlotIdx(context, 1)
        if (slotOne && slotTwo) {
            return 2
        } else if (slotOne || slotTwo) {
            return 1
        } else {
            val telephony = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
            val simState = telephony.simState
            return if (simState != TelephonyManager.SIM_STATE_ABSENT && simState != TelephonyManager.SIM_STATE_UNKNOWN) {
                1
            } else 0
        }
    }


    /** * 经过反射去调用TelephonyManager.getSimState(int slotIdx)方法,获取sim卡状态 * SIM_STATE_UNKNOWN 0 SIM卡状态:未知 * SIM_STATE_ABSENT 1 SIM卡状态:设备中没有SIM卡 * SIM_STATE_PIN_REQUIRED 2 SIM卡状态:锁定:要求用户的SIM卡PIN解锁 * SIM_STATE_PUK_REQUIRED 3 SIM卡状态:锁定:要求用户的SIM PUK解锁 * SIM_STATE_NETWORK_LOCKED 4 SIM卡状态:锁定:须要网络PIN才能解锁 * SIM_STATE_READY 5 SIM卡状态:就绪 * SIM_STATE_NOT_READY 6 SIM卡状态:SIM卡还没有就绪 * SIM_STATE_PERM_DISABLED 7 SIM卡状态:SIM卡错误,已永久禁用 * SIM_STATE_CARD_IO_ERROR 8 SIM卡状态:SIM卡错误,存在但有故障 * SIM_STATE_CARD_RESTRICTED 9 SIM卡状态:SIM卡受限,存在,但因为运营商的限制而没法使用。 * @param context * @param slotIdx:0(sim1),1(sim2) * @return */
    fun getSimStateBySlotIdx(context: Context, slotIdx: Int): Boolean {
        var isReady = false
        try {
            val getSimState = getSimByMethod(context, "getSimState", slotIdx)
            if (getSimState != null) {
                val simState = Integer.parseInt(getSimState.toString())
                if (simState != TelephonyManager.SIM_STATE_ABSENT && simState != TelephonyManager.SIM_STATE_UNKNOWN) {
                    isReady = true
                }
            }
        } catch (e: Throwable) {
        }

        return isReady
    }


    /** * 经过反射获取Sim卡状态 * @param context * @param method * @param param * @return */
    private fun getSimByMethod(context: Context, method: String, param: Int): Any? {
        val telephony = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        try {
            val telephonyClass = Class.forName(telephony.javaClass.name)
            val parameter = arrayOfNulls<Class<*>>(1)
            parameter[0] = Int::class.javaPrimitiveType
            val getSimState = telephonyClass.getMethod(method, *parameter)
            val obParameter = arrayOfNulls<Any>(1)
            obParameter[0] = param
            val result = getSimState.invoke(telephony, *obParameter)

            if (result != null) {
                return result
            }
        } catch (e: Throwable) {
            e.printStackTrace()
        }

        return null
    }

复制代码

当前可支持最大Sim卡张数

SubscriptionManager咱们能够经过getActiveSubscriptionInfoCountMax()方法来获取当前手机可支持的Sim卡最大数量。

/** * 获取当前支持最多sim卡数量 */
    fun getMaxSimCount(context: Context){
        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
                val subscriptionManager = context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
                tvSimMaxCount.text =  "当前支持最多Sim卡数量:${subscriptionManager?.activeSubscriptionInfoCountMax}"
            }

        } catch (e: Throwable) {
        }
    }
复制代码

当前蜂窝网络Sim卡信息

咱们能够经过TelephonyManager的getSimOperator()方法获取当前网络的SIM卡的MCC+MNC(mobile country code + mobile network code)其实就是IMSI的前五位,这个API在SDK API 1就已经加入。而后根据返回的信息根据运营商的MNC去判断对应的运营商信息。

/** * 经过TelephonyManager当前蜂窝网络运营商信息 */
    fun getNetType(){
        //检查是否有READ_PHONE_STATE权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
            == PackageManager.PERMISSION_GRANTED){
            //获取TelephonyManager
            val tm = getSystemService<TelephonyManager>()
            //在SDK API 1 getSimOperator()方法
            when(tm?.simOperator){
                "46000","46002","46004","46007","46008"->tvSimNetType.text = "当前上网SIM卡运营商:移动"
                "46001","46006","46009"->tvSimNetType.text = "当前上网SIM卡运营商:联通"
                "46003","46005","46011"->tvSimNetType.text = "当前上网SIM卡运营商:电信"
                "46020"->tvSimNetType.text = "当前上网SIM卡运营商:铁通"
            }

        }
    }
复制代码

源码:github.com/DaMaiGit/An…

相关文章
相关标签/搜索