Android设备信息的那些事

Android开发过程中避免不了要获取一些与设备相关的信息,比如deviceId,判断网络类型等。有时候我们到手机设备信息界面看到一个陌生的代号也是一头雾水,虽然有一种似曾相识的感觉,但是确实不知道是干什么,本篇就为你揭开这层面纱。

科普几个概念

常规

  • IMEI
  • MEID
  • IMSI
  • ICCID
  • 基带版本
  • 内核版本

Android

  • DeviceId
  • AndroidId
  • 序列号

IMEI

国际移动设备识别码(International Mobile Equipment Identity,IMEI),即通常所说的手机通信序列号、手机「串号」,用于在行动电话网络中识别每一部独立的手机等行动通讯装置,共有15位数字。

手机号码盘输入“*#06#”可显示当前手机的IMEI信息。IMEI作为手机通讯序列号,在某些不包含通信功能设备上自然也就不存在这个值了。另外双卡双待手机可以拿到两个IMEI序列号。

MEID

移动设备识别码(Mobile Equipment Identifier)是CDMA手机的身份识别码,也是每台CDMA手机或通讯平板唯一的识别码。通过这个识别码,网络端可以对该手机进行跟踪和监管。用于CDMA制式的手机。MEID的数字范围是十六进制的,和IMEI的格式类似。

因为MEID是CDMA的身份标识,某些手机不支持CDMA(电信)制式,因此在系统信息界面也就不会显示这个值了,也可以作为全网通和移动联通手机的区分。

IMSI

国际移动用户识别码(International Mobile Subscriber Identification Number)是区别移动用户的标志,储存在SIM卡中,可用于区别移动用户的有效信息,长度不超过15位,实际使用的IMSI的长度绝大部分都是15位。

IMSI结构如下:MCC+MNC+MIN
其中:

  • MCC:Mobile Country Code,行动装置国家代码,共3位,中国为460;
  • MNC:Mobile Network Code,移动设备网络代码,2位(欧洲标准)或3位(北美标准),中国电信CDMA系统使用03;

IMSI

Android开发过程中区分网络运营商:

    public static String getSimOperator() {
        TelephonyManager tm = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
        String imsi = tm.getSubscriberId();
        String operator = "";

        if (!TextUtils.isEmpty(imsi)) {
            if (imsi.startsWith("46000") || imsi.startsWith("46002") || imsi.startsWith("46007")) {
                // 中国移动
                operator = "中国移动";
            } else if (imsi.startsWith("46001") || imsi.startsWith("46006")) {
                // 中国联通
                operator = "中国联通";
            } else if (imsi.startsWith("46003") || imsi.startsWith("46005")) {
                // 中国电信
                operator = "中国电信";
            }
        }
        
        return operator;
    }

当然也可以采用TelephonyManager中的getSimOperator()方法来获取:

public static String getSimOperator() {
        TelephonyManager tm = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
        String operatorNum = tm.getSimOperator();
        String operator = "";
        if (!TextUtils.isEmpty(operatorNum)) {
            if ("46000".equals(operatorNum) || "46002".equals(operatorNum) || "46007".equals(operatorNum)) {
                // 中国移动
                operator = "中国移动";
            } else if ("46001".equals(operatorNum) || "46006".equals(operatorNum)) {
                // 中国联通
                operator = "中国联通";
            } else if ("46003".equals(operatorNum) || "46005".equals(operatorNum)) {
                // 中国电信
                operator = "中国电信";
            }
        }

        return operator;
    }

ICCID

集成电路卡识别码(Integrate circuit card identity),固化在手机SIM卡中 ICCID为IC卡的唯一识别号码,共有20位数字组成, 其编码格式为:XXXXXX 0MFSS YYGXX XXXX。分别介绍如下: 前六位运营商代码:中国移动的为:898600;898602;898604;898607 ,中国联通的为:898601、898606、898609,中国电信898603。SIM卡的ICCID不正规途径可以进行伪造。

ICCID和IMSI区别:

  • 一张SIM卡,里面有ICCID,也有IMSI。 ICCID是卡的标识,IMSI是用户的标识。
  • ICCID只是用来区别SIM卡,不作接入网络的鉴权认证。而IMSI在接入网络的时候,会到运营商的服务器中进行验证。

基带版本

基带版本就是手机的调制解调器使用的驱动版本号,调制解调器主要目的负责着手机的通信功能(打电话,发短信,数据交换等)。

基带(Baseband)是手机中的一块电路,负责完成移动网络中无线信号的解调、解扰、解扩和解码工作,并将最终解码完成的数字信号传递给上层处理系统进行处理。

内核版本

内核版本就是手机的硬件(如主板,GPS,摄像头,WiFi,蓝牙等)驱动集合体的版本号。

DeviceId

设备唯一标识,针对Android设备,它根据不同的手机设备返回IMEI,MEID或者ESN码。手机制式为GSM时,返回手机的IMEI;手机制式为CDMA时,返回手机的MEID或ESN。

开发过程中一般用DeviceId来标识唯一设备,需要预先申请电话权限,获取deviceId方法如下:

    public static String getDeviceID() {
        TelephonyManager tm = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
        return tm.getDeviceId();
    }

但是某些情况下获取DeviceId可能为空,具体原因有:

  1. 只有拥有通讯打电话功能的android设备才有IMEI号,WIFI平板获取不到。
  2. Android6.0以上设备获取deviceId需要申请系统信息相关权限,用户不同意权限同样拿不到。
  3. 即使有打电话功能,国内部分厂商自己的系统可能会把deviceId值改变,从而得到的是空值、”null”或类似00…相同的值。

AndroidId

在设备首次启动时,系统会随机生成一个64位的数字,并把这个数字以16进制字符串的形式保存下来,这个16进制的字符串就是ANDROID_ID,来标识当前系统的唯一性,当设备被恢复出厂设置后该值会被重置。

如果想要采用AndroidId作为设备唯一ID使用,获取androidId方法如下:

    public static String getAndroidId() {
        return android.provider.Settings.Secure.getString(
                mContext.getContentResolver(),
                android.provider.Settings.Secure.ANDROID_ID);
    }

但需要考虑以下几种情况:

  • 在Android <=2.1 or Android >=2.3的版本是可靠、稳定的,但在2.2的版本并不是100%可靠的
  • 厂商定制系统的Bug:不同的设备可能会产生相同的ANDROID_ID,有些设备返回的值为null。
  • 设备差异:对于CDMA设备,ANDROID_ID和TelephonyManager.getDeviceId() 返回相同的值。
    并且,如果某个Andorid手机被Root过的话,这个ID也可以被改变。

序列号

Serial Number,或叫SN,即设备生产时确定的一串序列号,例如5JPDU17519001099。

设备序列号也可作为设备唯一标识,获取SN号方法如下:

    public static String getSerialNumber() {
        return android.os.Build.SERIAL;
    }

但有些设备获取SN也不准确(汗!!!):

  • 对CDMA设备,返回的是一个空值。
  • 在少数的一些设备上,会返回垃圾数据。对于没有通话功能的设备,它可能会返回一个固定的值。

Android获取唯一标识

某一天产品经理告诉我必须要有一个值来确定唯一设备,但是根据上边的deviceId、androidId、SN号,似乎都不能准备来确定唯一的值,无奈啊。你也可能会说,我也可以根绝wifi MAC地址,蓝牙MAC地址来确定,但是我也可以明确的告诉你,针对wifi MAC地址和蓝牙MAC地址也是受系统影响的,在某时候关闭wifi和蓝牙并不能获取到MAC地址,而且在Android6.0的时候google将wifi MAC地址封闭了,各个设备返回的都是“02:00:00:00:00:00”常量值。

google的官方文档明确指出wifi地址问题:

Most notably, Local WiFi and Bluetooth MAC addresses are no longer available. The getMacAddress() method of a WifiInfo object and the BluetoothAdapter.getDefaultAdapter().getAddress() method will both return 02:00:00:00:00:00 from now on.

这种情况下我们应该怎么办呢?我也只能说“如果拿不到只能自己造了”,以下是我给出的一种方案,似乎也能满天过海吧。

思想:

  1. App启动闪屏页或登录页首先检测电话管理权限和读写权限(为后边获取存储唯一ID做准备);
  2. 分别判断SD卡和Sharepreference是否保存有之前保存的设备唯一ID:
  3. SP和SD都有,判断是否一致,不一致,复制SP到SD,取SP值。
  4. SP和SD有且只有其中一个有,相互复制,然后取值。
  5. SP和SD都没有:
    • 获取deviceId,若为空,向下取;
    • 获取SN,若为空,向下取;
    • UUID生成一个,分别保存到SD和SP。

注意:SD卡上保存的时候,路径隐蔽一些,防止被删除。

以上方法,只要不刷机,获取到的值都是唯一的。

    public static String getDeviceId0() {
        final String DEVICE_ID_SP = "DEVICE_ID";
        //文件名前边加个“.”可隐藏此文件。
        final String DEVICE_ID_SD_SDR = "Android/data/.device";

        String spDeviceId = (String) SPUtil.get(SPUtil.SP_GLOBAL, DEVICE_ID_SP);
        String sdDeviceId = FileUtil.readFileFromSD(DEVICE_ID_SD_SDR);
        String deviceId;
        boolean isSaveSP = false;
        boolean isSaveSD = false;

        //获取deviceId;
        if (!IsEmpty.string(spDeviceId) && !IsEmpty.string(sdDeviceId)) {
            deviceId = spDeviceId;
            if (!spDeviceId.equals(sdDeviceId)) {
                isSaveSD = true;
            }
        } else if (!IsEmpty.string(spDeviceId) && IsEmpty.string(sdDeviceId)) {
            deviceId = spDeviceId;
            isSaveSD = true;
        } else if (IsEmpty.string(spDeviceId) && !IsEmpty.string(sdDeviceId)) {
            deviceId = sdDeviceId;
            isSaveSP = true;
        } else {
            deviceId = getDeviceID();
            if (IsEmpty.string(deviceId)) {
                deviceId = "sn" + getSerialNumber();
                if (IsEmpty.string(deviceId)) {
                    UUID id = UUID.randomUUID();
                    String[] idd = id.toString().split("-");
                    deviceId = "uu" + idd[0] + idd[1] + idd[2];
                }
            }
            isSaveSP = true;
            isSaveSD = true;
        }

        //存储deviceId;
        if (isSaveSP) {
            SPUtil.put(SPUtil.SP_GLOBAL, DEVICE_ID_SP, deviceId);
        }
        if (isSaveSD) {
            FileUtil.writeFileToSD(DEVICE_ID_SD_SDR, deviceId);
        }

        return deviceId;
    }

Tip

  • 这对手机制式GSM、CDMA、WCDMA等本篇不做过多阐述,可自行研究了解。
  • Android开发过程中根据TelephonyManager的getLine1Number有些时候并不一定能拿到电话号码,此种情况只能够读取写入到SIM卡的手机号,有些电话卡上的电话号码只存在运营商服务器上(例如移动)。

转载请注明出处,我是toperc.

发布了47 篇原创文章 · 获赞 38 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/li0978/article/details/89576846