【Android】Android设备唯一标识简介

一、概念

IMEI
IMEI(International Mobile Equipment Identity) 是国际移动设备身份码的缩写,国际移动装备辨识码,是由 15 位数字组成的 “电子串号”,它与每台手机一一对应,而且该码是全世界唯一的。每一部手机在组装完成后都将被赋予一个全球唯一的一组号码,这个号码从生产到交付使用都将被制造生产的厂商所记录。IMEI 码由 GSM(全球移动通信协会)统一分配,授权 BABT(英国通信认证管理委员会)审受。

IMEI 组成为:
1、前 6 位数(TAC,Type ApprovalCode) 是 “型号核准号码”,一般代表机型。
2、接着的 2
位数(FAC,Final Assembly Code) 是 “最后装配号”,一般代表产地。
3、之后的 6 位数(SNR) 是"串号",一般代表生产顺序号。 4、最后 1 位数(SP) 通常是 “0”,为检验码,备用。

一般在 Android 手机上可以在关于手机里面查看到

IMSI
国际移动用户识别码(IMSI:International Mobile Subscriber Identification Number)是区别移动用户的标志,储存在 SIM 卡中,可用于区别移动用户的有效信息。其总长度不超过 15 位,同样使用 0~9 的数字。

ICCID
ICCID:Integrate circuit card identity 集成电路卡识别码(固化在手机 SIM 卡中),简单来说就是 SIM 卡序列号,它拥有独一无二的特性,类似于手机的序列号,仅仅指向一张手机卡。共有 20 位数字组成,不同运营商编码格式不一样。并且前六位数字为运营商代码:比如中国移动的为:898600;中国联通的为:898601,中国电信的为:898603。

简而言之:IMEI / MEID 是和设备相关的,移动设备的唯一标志码;而 IMSI 和 ICCID 是和手机卡相关的,信息存储到手机卡上,没有安装手机卡的手机,上面获取这两个值为 null。

二、TelephonyManager

//注意代码使用需要在Manifest配置权限
// <uses-permission android: /> 
//在API 23(Android6.0 )及其以上版本phone组权限需要动态申请
TelephonyManager tm = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);  
String deviceid = tm.getDeviceId();   //取 IMEI或者MEID
String tel = tm.getLine1Number();     //取出用户手机号码,手机没有安装SIM卡,值为null
String imsi =tm.getSubscriberId();     //取出IMSI,手机没有安装SIM卡,值为null
String imei =tm.getSimSerialNumber();  //取出ICCID,手机没有安装SIM卡,值为null

其实上面的信息分开来看总共分为两部分,一部分是设备相关的信息,一部分是 SIM 卡相关的信息。关于 SIM 卡相关的信息,一般来说没有办法标志设备,因为手机可以任意替换 SIM 卡,而且现在手机都是双卡双待的,关于双卡手机获取和手机卡相关的信息参考这篇文章

我们这里主要关心设备 ID(IMEI 或者 MEID),解释下这两个东西:IMEI 是国际移动设备识别码的简称,而 MEID 是 动设备识别码的简称,一般 IMEI 是所有设备都有,而 MEID 一般只在只有支持 CDMA 制式的设备才有的。

OPPO 手机:进入手机设置 – 常规 – 关于手机 – 状态信息 --IMEI 即可看到。 小米手机: 设置 – 我的设备 – 全部参数 – 状态信息 —IMEI 信息 华为手机:设置 — 关于手机

注意:平板没有 imei 号

三、设备唯一 ID

1. IMEI 或者 MEID

Android 系统关于双卡的支持的知识需要知道一些,在 Android4.x 及其以下版本的时候,原生 Android 是不支持双卡的,在
5.x 左右开始支持,但是 api 是隐藏的,在 Android 6.0 才开始公开开放双卡的 API。

来看下我们获取 IMEI 或者 MEID 的基本方法

//注意动态权限申请
TelephonyManager telephonyManager = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);
String imei = telephonyManager.getDeviceId();
这个方法在 4.x 及其以下版本的时候,运行这个方法是没有问题的,因为 4.x 是不支持双卡的,也就是说 4.x 的手机要么是 GSM 要么是 CDMA 制式的。所以,getDeviceId的文档这样写道:

/**
     * Returns the unique device ID, for example, the IMEI for GSM and the MEID
     * or ESN for CDMA phones. Return null if device ID is not available.
     * 翻译过来就是:这个方法会返回唯一的设备id,
     * 比如在GSM的手机上返回的是IMEI,而在CDMA 手机上返回的是MEID或者ESN。
     * 如果设备id不可读取,那么返回null。
     */

但是这种情况到了 Android 6.0 (先不考虑 5.x 那个版本不稳定)上就不一样了,6.0 支持双卡,也就是说手机上不可以能只有一个 IMEI 或者 MEID,这个时候就需要根据手机卡槽获取了,这个卡槽里面装的是什么制式的卡,那么对于下标获取的就是 IMEI 或者 MEID:

public void onClick(View view) {
    
    

        TelephonyManager tm = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);

        Log.d("Q_M", "meid:" + tm.getDeviceId());
        Log.d("Q_M", "meid:" + tm.getDeviceId(0));
        Log.d("Q_M", "meid:" + tm.getDeviceId(1));

    }

测试结果来自网络,我没有验证(我对不插卡的情况存在疑问):

我的两张联通卡分别获取的是imei1和imei2

  1、不插卡(或两张卡都是GSM卡)

  getDeviceId()  返回 imei1
  getDeviceId(0) 返回 imei1
  getDeviceId(1) 返回 imei2

  2、卡1插CDMA卡,卡2不插卡(或卡2插GSM卡)

  getDeviceId()   返回 meid
  getDeviceId(0) 返回 meid
  getDeviceId(1) 返回 imei2

  3、卡1不插卡(或卡1插GSM卡)卡2插CDMA卡

  getDeviceId()   返回 imei1
  getDeviceId(0) 返回 imei1
  getDeviceId(1) 返回 meid

后来到了 Android 8.0 ,方法控制更为精细了,所以这个 getDeviceId 方法就被废弃了,不再推荐使用。而推荐使用 getImei和getMeid 同时这两个方法支持传入卡槽的下标来确定要读取那个卡对应的值。在 8.0 及其以上的手机上可以这么严重:

public void onClick(View view) {
    
    

        TelephonyManager tm = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);

        //手机上存在两个imei值,分配给两个卡槽
        Log.d("Q_M", "imei:" + tm.getImei());
        Log.d("Q_M", "imei0:" + tm.getImei(0));
        Log.d("Q_M", "imei1:" + tm.getImei(1));

        //手机上只会存在一个meid,两个卡槽获取的一样
        Log.d("Q_M", "meid:" + tm.getMeid());
        Log.d("Q_M", "meid0:" + tm.getMeid(0));
        Log.d("Q_M", "meid1:" + tm.getMeid(1));
    }

最后来看一眼友盟的代码里面获取 Imei 的方式:

public static String getImeiNew(Context var0) {
    
    
        String var1 = null;

        try {
    
    
            if(var0 != null) {
    
    
                TelephonyManager var2 = (TelephonyManager)var0.getSystemService("phone");
                if(var2 != null && checkPermission(var0, "android.permission.READ_PHONE_STATE")) {
    
    
                    if(VERSION.SDK_INT >= 26) {
    
    
                        try {
    
    
                            Method var3 = var2.getClass().getMethod("getImei", new Class[0]);
                            var3.setAccessible(true);
                            var1 = (String)var3.invoke(var2, new Object[0]);
                        } catch (Exception var4) {
    
    
                            ;
                        }

                        if(TextUtils.isEmpty(var1)) {
    
    
                            var1 = var2.getDeviceId();
                        }
                    } else {
    
    
                        var1 = var2.getDeviceId();
                    }
                }
            }
        } catch (Exception var5) {
    
    
            if(AnalyticsConstants.UM_DEBUG) {
    
    
                MLog.w("No IMEI.", var5);
            }
        }

        return var1;
    }

2. ANDROIDID

在设备首次启动时,系统会随机生成一个 64 位的数字,并把这个数字以 16 进制字符串的形式保存下来,这个 16 进制的字符串就是 ANDROID_ID,当设备被 wipe 后该值会被重置。设备恢复出厂设置,这个值也会改变。如果设备被 root,这个值可以任意改变。

String androidID = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);

3. Serial Number (设备序列号)

这个东西理论上来说是来自硬件,出厂是就设置好了,但是有些设备厂商会随便写一个值

Build.SERIAL
在 api>=26的时候
可以这么获取 Build.getSerial();

4. Mac 地址

<!--访问WIFI的权限-->
<uses-permission android:/>

Mac 地址,在 6.0 以上不能按正常方式获取,7.0 以上很难获取,并且 Mac 地址也不一定唯一。

1.在 6.0 以下后去 mac 地址方式

WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
WifiInfo winfo = wifi.getConnectionInfo();
String mac =  winfo.getMacAddress();

2.在 6.0 及其以上 8.0 以下 上面的方法在 7.0 的设备获取的永远是02:00:00:00:00:00

/**
     * 通过网络接口取
     * 记得添加网络权限
     * <uses-permission android: />
     *
     * @return mac 地址字符串
     */
    private static String getNewMac() {
    
    
        try {
    
    
            List<NetworkInterface> all = Collections.list(NetworkInterface.getNetworkInterfaces());
            for (NetworkInterface nif : all) {
    
    
                if (!nif.getName().equalsIgnoreCase("wlan0")) continue;

                byte[] macBytes = nif.getHardwareAddress();
                if (macBytes == null) {
    
    
                    return null;
                }

                StringBuilder res1 = new StringBuilder();
                for (byte b : macBytes) {
    
    
                    res1.append(String.format("%02X:", b));
                }

                if (res1.length() > 0) {
    
    
                    res1.deleteCharAt(res1.length() - 1);
                }
                return res1.toString();
            }
        } catch (Exception ex) {
    
    
            ex.printStackTrace();
        }
        return null;
    }

注:通过 adb 命令行可以查看手机 mac 地址,不过需要 root 手机:

命令行

$ adb shell 
$ su 
$ cat /sys/class/net/wlan0/address

猜你喜欢

转载自blog.csdn.net/sinat_36955332/article/details/108702973
今日推荐