1、IMEI
获取IMEI是通过TelephonyManager对象,TelephonyManager对象的获取方式是通过context.getSystemService(),传入不同的值获取到的Manager对象不同。
TelephonyManager tm = (TelephonyManager) context.getSystemService(Service.TELEPHONY_SERVICE);
主要分以下几种情况:
Android 6.0
以下,无需权限
获取方式:tm.getDeviceId();
Android 6.0 - Android 8.0
,需要READ_PHONE_STATE
权限,如果用户拒绝权限会抛出SecurityException
异常。
获取方式:tm.getDeviceId();
Android 8.0 - Android 10
,需要READ_PHONE_STATE
权限,如果用户拒绝权限会抛出SecurityException
异常。
获取方式:tm.getImei();
但是此方法获取的imei只有一个,如果手机是双卡的是有两个imei值的,可以使用动态调用的方式都获取到:
public static String getIMEI(Context context) {
TelephonyManager manager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
try {
Method method = manager.getClass().getMethod("getImei", int.class);
String imei1 = (String) method.invoke(manager, 0);
String imei2 = (String) method.invoke(manager, 1);
return imei1;// 根据需求返回
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
Android 10
以上,即使你申请了READ_PHONE_STATE
权限,依然会抛出异常,所以IMEI不能用了。
2、序列号
序列号也和Android版本有关,不同版本获取方式不同,主要分以下情况:
Android 8.0
以下,不需要权限,可以通过android.os.Build.SERIAL
获取。Android 8.0 - Android 10
,通过以上方式获取到的是unknown
,此方式已废弃;需要READ_PHONE_STATE
权限,通过android.os.Build.getSerial()
获取,如果用户拒绝了,抛出SecurityException
异常。Android 10
以上,即使有READ_PHONE_STATE
权限也会抛出SecurityException
异常,和imei一样,Android10以上不能获取了。
3、Mac地址
mac地址分以下几种情况:
Android 6.0
以下,需要ACCESS_WIFI_STATE
权限,通过以下方式获取:
private String getMacAddress(Context context) {
WifiManager manager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
return manager.getConnectionInfo().getMacAddress();
}
Android 6.0
以后,上面方法获取的结果都是02:00:00:00:00:00
,用读取系统文件方式获取:
/**
* Android 6.0 - Android 7.0(不包含)
* @return
*/
private static String getMacAddress() {
String macSerial = null;
String str = "";
InputStreamReader ir = null;
LineNumberReader input = null;
try {
Process pp = Runtime.getRuntime().exec("cat /sys/class/net/wlan0/address");
ir = new InputStreamReader(pp.getInputStream(), StandardCharsets.UTF_8);
input = new LineNumberReader(ir);
for (; null != str; ) {
str = input.readLine();
if (str != null) {
macSerial = str.trim();
break;
}
}
} catch (IOException ex) {
ex.printStackTrace();
} finally {
close(input);
close(ir);
}
return macSerial;
}
private static void close(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
但是此方法在Android 7.0
以后也不能用了,会抛出FileNotFoundException异常,取而代之的是通过扫描所有网络接口来获取:
private static String getAddressMacByInterface() {
try {
List<NetworkInterface> all = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface nif : all) {
if (nif.getName().equalsIgnoreCase("wlan0")) {
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 "";
}
此方式也是目前获取mac最稳定的方式,但是Android 10
加入了mac地址随机化特性,目前市面上大部分手机不支持此特性,需要在开发者选项中设置,普通用户一般连开发者模式都是关闭的,所以暂时可以放心使用,但是随着Android版本的迭代,不排除Google官方把此特性默认设置为打开的。
4、ANDROID_ID
android_id是设备的系统首次启动生成的一串字符,基本可以保证唯一性,获取方式:
String androidId = Settings.System.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
与上面的几种相比,android_id获取比较简单,没有权限限制,也不会抛出异常,但是root、刷机或恢复出厂设置都会导致设备的ANDROID_ID重置。
总结:
总的来说,为设备添加唯一标识可以参照以下优先级:
1、ANDROID_ID:系统首次启动生成,获取方式安全,但是不稳定。
2、MAC地址:获取方式不安全,比较稳定。
由于Android 10的发布,序列号和IMEI基本可以放弃使用了;
3、Android还提供了一个生成id的工具类UUID,可以生成一串随机字符,保存到本地来使用。
4、使用移动安全联盟提供的方法(未尝试过)。
反正由于Android的碎片化,需要开发者不断的适配、更新,如果长时间不适配,说不定哪天系统更新了你的应用就崩了。
关于以上方法的工具类下载。