目录
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个设备终端
基于以上需求,我们需要可以获取到当前设备的唯一标识符
对于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)缺点
设备首次启动后系统会随机生成一个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)缺点
产品序列号,这里需要区分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)缺点
(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)缺点:
SIM卡的序列码
(1)获取方式
TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
String SimSerialNumber = tm.getSimSerialNumber();
(2)缺点
对于CDMA设备而言,返回的是空值
国际移动用户识别码,用于区分移动用户,存储在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)缺点
上面列举了我们可以获取到的设备信息,而我们唯一的标识符就需要利用上面的信息。
设计方案需要考虑以下因素:
由于国内厂商的定制Rom等因素以及Android 系统本身唯一ID设计问题,我们没有办法保证各种场景下都100%唯一,因此我们的原则就是要在以上几种情况中,尽可能保证同一设备的用户是唯一ID,不会因为卸载或者重装等导致用户Id改变
(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