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设置权限问题
- Android有两种方法检测USB设备插入
- 【Android开发学习01】与Android实体设备的连接
- android蓝牙BLE(三) —— 广播
- android apk的签名和权限问题
- android基础知识17:Android设备常见问题与测试要领
- delphi XE开发微信支付Android获取手机存储权限、Android获取短