Android设备唯一标识符(适配Android Q)

Android设备唯一标识符(适配Android Q)

 

目录

Android设备唯一标识符(适配Android Q)

一、需求场景

二、Android设备信息

1、DeviceId(IMEI)

2、AndroidId

3、Serial Number

4、Wlan或者蓝牙的MAC地址

5、SIM Serial Number

6、IMSI

三、唯一识别符方案

1、设计原则

2、方案实现


一、需求场景

目前常见的使用场景:

1、标识唯一设备,用于数据统计或者后台服务精准下发

2、用于账号与设备绑定

例如,数据上报到自己的统计服务器;

根据特定用户下发奖励或者其他;

会员账号只能绑定3个设备终端

基于以上需求,我们需要可以获取到当前设备的唯一标识符

二、Android设备信息

1、DeviceId(IMEI)

对于GSM手机来说,DeviceId为IMEI,而对于CDMA手机而言则为MEID,目前国内大部分手机主要是IMIE

IMEI是国际移动设备识别码,即通常说的手机串号,用于标识在移动电话网络中每一部独立的手机等移动通信设备。

(1)获取方式

  /**
     * 获取设备ID,GSM手机为IMEI、CDMA手机为MEID
     *
     * @param context
     * @return
     */
    public static String getDeviceId(Context context) {
        if (context == null) {
            return "";
        }

        try {
            TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
            if (PermissionUtils.checkPermissionForApi23(context, Manifest.permission.READ_PHONE_STATE)) {
                return telephonyManager.getDeviceId();
            } else {
                LogUtils.e(TAG, "getDeviceId error: permission denied");
            }
        } catch (Exception e) {
            e.printStackTrace();
            LogUtils.e(TAG, "getDeviceId error: " + e.getMessage());
        }

        return "";
    }

(2)缺点

  • 在Android6.0以及之后,需要动态获取android.permission.READ_PHONE_STATE 权限。因此存在用户拒绝授权的可能,此外首次启动后就上报设备ID时也可能影响启动速度
  • 可能存在获取不到DeviceId的可能,存在返回null或者000000的垃圾数据可能
  • 只对有电话功能的设备有效(无需插卡,但是需要有对应硬件模块)。例如在部分pad上可能无法获取到DeviceId

 

 

2、AndroidId

设备首次启动后系统会随机生成一个64位的数字,用16进制字符串的形式表示,例如:4351daa4516303b3,4351 daa4 5163 03b3

(1)获取方式

/**
 * 获取AndroidId
 *
 * @param context
 * @return
 */
public static String getAndroidId(Context context) {
    if (context == null) {
        return "";
    }

    String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
    return (TextUtils.isEmpty(androidId) ? "" : androidId);
}

(2)缺点

  • 恢复出厂或者刷机后会被重置
  • 部分厂商定制系统中,可能为空,也可能是不同设备中会产生相同的值
  • 对于CDMA设备汇总,AndroidId和DeviceId会返回相同的值

 

3、Serial Number

产品序列号,这里需要区分DeviceId。

(1)获取方式

/**
 * 获取android序列号
 *
 * @return id或者空串
 */
private static synchronized String getSerialNumber() {
    String serialNumber = null;
    try {
        Class clazz = Class.forName("android.os.SystemProperties");
        if (clazz != null) {
            Method method_get = clazz.getMethod("get", String.class, String.class);
            if (method_get != null) {
                serialNumber = (String) (method_get.invoke(clazz, "ro.serialno", ""));
            }
        }
    } catch (Exception e) {
        if (DEBUG) {
            e.printStackTrace();
        }
    }

    return serialNumber != null ? serialNumber : "";
}

(2)缺点

  • 部分设备无法获取到
  • 部分红米手机都会返回无用值0123456789ABCDEF

 

4、Wlan或者蓝牙的MAC地址

(1)获取方式

/**
 * 获取MAC地址
 *
 * @param context
 * @return
 */
public static String getMacAddress(Context context) {
    String mac;
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
        mac = getMacDefault(context);
    } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
        mac = getMacAddressM();
    } else {
        mac = getMacFromHardware();
    }
    return mac;
}

/**
 * Android  6.0 之前(不包括6.0)
 * 必须的权限  
 * @param context
 * @return
 */
private static String getMacDefault(Context context) {
    String mac = "02:00:00:00:00:00";
    if (context == null) {
        return mac;
    }

    WifiManager wifi = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
    if (wifi == null) {
        return mac;
    }
    WifiInfo info = null;
    try {
        info = wifi.getConnectionInfo();
    } catch (Exception e) {
        e.printStackTrace();
    }
    if (info == null) {
        return mac;
    }

    mac = info.getMacAddress();
    if (!TextUtils.isEmpty(mac)) {
        mac = mac.toUpperCase(Locale.ENGLISH);
    }
    return mac;
}

/**
 * Android 6.0(包括) - Android 7.0(不包括)
 * @return
 */
private static String getMacAddressM() {
    String wifiaddress = "02:00:00:00:00:00";
    try {
        wifiaddress = new BufferedReader(new FileReader(new File("/sys/class/net/wlan0/address"))).readLine();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return wifiaddress;
}


/**
 * 遍历循环所有的网络接口,找到接口是 wlan0
 * 必须的权限 
 * @return
 */
private static String getMacFromHardware() {
    try {
        List all = Collections.list(NetworkInterface.getNetworkInterfaces());
        for (NetworkInterface nif : all) {
            if (!nif.getName().equalsIgnoreCase("wlan0")) {
                continue;
            }

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

            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 e) {
        e.printStackTrace();
    }
    return "02:00:00:00:00:00";
}

 

(2)缺点:

  • 需要设备具备蓝牙或者wifi硬件
  • 蓝牙Mac,在蓝牙未打开时是无法获取到mac地址的
  • wlan的mac,需要打开过wlan
  • 需要蓝牙、wifi权限

 

5、SIM Serial Number

SIM卡的序列码

(1)获取方式

TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE); 
String SimSerialNumber = tm.getSimSerialNumber(); 

(2)缺点

对于CDMA设备而言,返回的是空值

6、IMSI

国际移动用户识别码,用于区分移动用户,存储在SIM卡中。

(1)获取方式

/**
 * 获取SIM卡的IMSI码
 * 

* SIM卡唯一标识:IMSI 国际移动用户识别码(IMSI:International Mobile Subscriber Identification Number)是区别移动用户的标志, * 储存在SIM卡中,可用于区别移动用户的有效信息。IMSI由MCC、MNC、MSIN组成,其中MCC为移动国家号码,由3位数字组成, * 唯一地识别移动客户所属的国家,我国为460;MNC为网络id,由2位数字组成, * 用于识别移动客户所归属的移动网络,中国移动为00、02,中国联通为01,中国电信为03;MSIN为移动客户识别码,采用等长11位数字构成。 * 唯一地识别国内GSM移动通信网中移动客户。所以要区分是移动还是联通,只需取得SIM卡中的MNC字段即可 * * @param context * @return */ public static String getSubscriberId(Context context) { if (context == null) { return null; } try { TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); if (PermissionUtils.checkPermissionForApi23(context, Manifest.permission.READ_PHONE_STATE)) { return telephonyManager.getSubscriberId(); } else { LogUtils.e(TAG, "getSubscriberId error: permission denied"); } } catch (Exception e) { e.printStackTrace(); } return null; }

(2)缺点

  • 需要有电话功能
  • Android6.0以及以上需要获取权限

 

三、唯一识别符方案

上面列举了我们可以获取到的设备信息,而我们唯一的标识符就需要利用上面的信息。

1、设计原则

设计方案需要考虑以下因素:

  • 卸载重装或者清除数据
  • 刷机或者恢复出厂
  • 是否需要联网或者打开蓝牙

由于国内厂商的定制Rom等因素以及Android 系统本身唯一ID设计问题,我们没有办法保证各种场景下都100%唯一,因此我们的原则就是要在以上几种情况中,尽可能保证同一设备的用户是唯一ID,不会因为卸载或者重装等导致用户Id改变

2、方案实现

(1)Android Q以下

针对Android Q以下(不包括Android Q),这里我们提供一种实现思路:IMEI+Android Id+Serial Number。接下来我们具体来分析

对于设备A来说

信息

取值

影响因素

 

IMEI

Null,或者""或者垃圾值或者具体的有效值

与设备有关,与具体应用无关,不受应用卸载或者重装系统、刷机等影响

 

Android Id

Null或者“”或者垃圾值或者具体有效值

与系统首次启动有关,不受应用卸载重装影响,但是会受到重装或者恢复出厂影响

 

Serial Number

Null或者“”或者垃圾值或者具体有效值

与设备有关,不受应用卸载影响,不受系统重装或者恢复出厂影响

 

 

对于Android Q以下,根据我们上面的设备信息分析,可以根据IMEI+AndroidID+Serial Number来确定唯一设备ID,基本上可以将重复设备的比例降到很低,具体比例没有研究过,但是目前我们的应用采用的就是这套方案

具体如下:

private static synchronized String calcWid(Context ctx) {
    mImei = DeviceUtils.getDeviceId(ctx);
    String s = mImei + getAndroidId(ctx) + getSerialNumber();
    mWid = md5(s.getBytes());

    return mWid;
}

缺点:

(1)系统刷机或者恢复出厂后就是新设备ID,对于恢复出厂和刷机都属于极端操作,概率很低

(2)极端情况下IMI、AndroidID或者SerialNumber均获取失败或者多个设备获取到的都是相同值,那么就会出现多台设备是同一Deviceid,但是这样的概率很低

 

综合分析,以上方案除了极低概率出现问题外,可以解决唯一设备标识符问题

(2)Android Q以上(包含Android Q)

在Android Q以前,我们通过权限

可以获取到IMEI和Serial Number,但是在Android Q中我们获取该权限会被Denied,按照官方API可以通过权限

来获取,但是这个权限只有在系统应用中才可以使用,因此也是无用

 

因此我们无法使用IMEI以及Serial Number,那么只能考虑牺重复率来适配Android Q

 

360浏览器插件中对于Android Q的适配中,使用AndroidId作为唯一设备ID,由于目前了解到的适配方案较少,暂时我们这里可以借鉴采用这种方案,当然带来的问题是用户恢复出厂或者刷机后该值会发生改变。

具体获取AndroidId的方法在前面的内容中这里不做赘述。

 

 

联系方式:

QQ:719074460

 

 

 

 

 

你可能感兴趣的:(Android开发进阶,Android开发学习之路)