android获取设备唯一ID方法

1、方案一IMEI

    IMEI(International Mobile Equipment Identity)是国际移动设备识别码的缩写,由15-17位数字组成,与手机是一一对应的关系,该码是全球唯一的,并且永远不会改变。

2、不同版本获取IEMI的方式

    在Android 8.0(API Level 26)以下,可以通过TelephonyManager的getDeviceId()方法获取到设备的IMEI码(其实这里的说法不准确,该方法是会根据手机设备的制式(GSM或CDMA)返回相应的设备码(IMEI、MEID和ESN)),该方法在Android 8.0及之后的版本已经被废弃了,取而代之的是getImei()方法。获取设备IMEI码的示例代码如下:

   

    无论是getDeviceId()方法还是getImei()方法都可以传入一个参数slotIndex,用于设备中插入了双卡的情况,这里就不展示了。
    IMEI码的获取方式很简单,也能保证唯一性和不变性,目前很多应用都使用IMEI码作为设备的唯一标识,但众所周知,在Android 6.0以上获取IMEI码是需要动态申请READ_PHONE_STATE权限的,一旦用户拒绝了该权限就获取不到了。这还不是最要命的,在Android 10中官方已经明确说明第三方应用无法获取到IMEI码,是因为AndroidQ中去掉了READ_PHONE_STATE权限,取而代之的是一个系统级别的权限:READ_PRIVILEGED_PHONE_STATE,所以Android Q平台无论如何也不会再有IMEI和序列号SerialNumber,详细内容可以查看Android 10 中的隐私权变更。

下面是几种不同情况下IMEI码的获取情况:

Android 6.0以下:无需申请权限,可以通过getDeviceId()方法获取到IMEI码
Android 6.0-Android 8.0:需要申请READ_PHONE_STATE权限,可以通过getDeviceId()方法获取到IMEI码,如果用户拒绝了权限,会抛出java.lang.SecurityException异常
Android 8.0-Android 10:需要申请READ_PHONE_STATE权限,可以通过getImei()方法获取到IMEI码,如果用户拒绝了权限,会抛出java.lang.SecurityException异常
Android 10及以上:分为以下两种情况:
targetSdkVersion<29:没有申请权限的情况,通过getImei()方法获取IMEI码时抛出java.lang.SecurityException异常;申请了权限,可以正常获取IMEI。
targetSdkVersion=29:无论是否申请了权限,通过getImei()方法获取IMEI码时都会直接抛出java.lang.SecurityException异常。


3、方案二ANDROID_ID

    ANDROID_ID是设备的系统首次启动时随机生成的一串字符,由16个16进制数(64位)组成,基本上还是可以保证唯一性的,获取ANDROID_ID的示例代码如下:

String androidId = Settings.System.getString(getContentResolver(), Settings.Secure.ANDROID_ID);

    ANDROID_ID的获取门槛是最低的,不需要任何权限,但ANDROID_ID也存在一些缺点,就是无法保证稳定性,root、刷机或恢复出厂设置都会导致设备的ANDROID_ID发生改变。此外,我看到部分文章中有提到某些厂商定制系统的Bug会导致不同的设备可能会产生相同的ANDROID_ID,而且某些设备获取到的ANDROID_ID为null。如果应用对于设备标识的要求不是特别高的话还是一个值得考虑的方案。

4、方案三Pseudo-Unique ID

    有一些特殊的情况,如平板电脑的设置没有通话功能,或者不愿加入READ_PHONE_STATE许可。而仍然想获得唯一序列号之类的东西。这时可以通过取出ROM版本、制造商、CPU型号、以及其他硬件信息来实现这一点。这样计算出来的ID不是唯一的(因为如果两个手机应用了同样的硬件以及Rom 镜像)。但出现类似情况的可能性基本可以忽略。要实现这一点,可以使用Build类,大多数的Build成员都是字符串形式的,我们只取他们的长度信息。我们取到13个数字,并在前面加上“35”。这样这个ID看起来就和15位IMEI一样了:

 

这个的缺点就是不是百分百唯一。

5、解决方案

    为了获取IMEI,尝试了adb shell的方式、反射方式都不行。

Android 6.0以下:无需申请权限,可以通过getDeviceId()方法获取到IMEI码
Android 6.0以上:我们目前的targetSdkVersion=23,需要申请READ_PHONE_STATE权限,可以通过getDeviceId()、getImei()方法获取到IMEI码,如果用户拒绝了权限,有两个方案:

  1. 拒绝权限后退出应用不让使用,下次继续请求权限直到同意为止,如果用户选择了“拒绝且不再提示”这种情况,只能阻塞住,提示用户手动去应用管理打开权限。
  2. 我们每次传给后台两个id(IMEI和android_id),拒绝权限后IMEI传空串,获取android_id上报给后台,如果特殊机型android_id返回null,需要我们跟后台商量一个特殊的标记,或者可以使用Pseudo-Unique ID;后台可以对IMEI和android_id进行双重校验,通过校验这两个id来判断是否同一个设备。


我们在获取权限之前会弹窗提示禁止权限会影响应用的功能使用,尽量引导他同意权限。