Build.getSerial();
TelephonyManager.getImei();
TelephonyManager.getMeid()
TelephonyManager.getDeviceId();
TelephonyManager.getSubscriberId();
TelephonyManager.getSimSerialNumber();
1.2 关键技术
关键技术在于数据获取(唯一性&&稳定性)和数据储存。
(1)数据获取——设备标识码
设备标识码是用来标识设备的特征码,常见的设备标识码有以下几种:
表1 常见设备标识码及其定义
IMEI(International Mobile Equipment Identity)
国际移动设备识别码:15位数字组成,与每台手机一一对应,且全球唯一,是GSM设备返回的,并且是写在主板上的,重装APP不会改变IMEI
全球唯一的56bit CDMA制式移动终端标识号:14位数字,标识号会被烧入终端里,且不能被修改。可用来对CDMA制式(电信运营)移动式设备进行身份识别和跟踪
DEVICE_ID
设备ID。Android系统为开发者提供的用于标识手机设备的串号;它根据不同的手机设备返回IMEI,MEID或ESN码;它返回的是设备的真实标识(Q上无法正常获取)
MAC ADDRESS
媒体访问控制地址,也称局域网地址、以太网地址、物理地址:它是一个用来确认网络设备位置的地址。MAC地址用于在网络中唯一标示一个网卡,一台设备若有一或多个网卡,则每个网卡都需要并会有一个唯一的MAC地址。
ANDROID_ID(SSAID)
设备首次启动时,系统会随机生成一个64位的数字,并把这个数字以16进制字符串的形式保存下来。当设备被wipe后该值会被重置(wipe:恢复出厂设置、刷机等)
SN(Serial Number)
产品序列号:是为了验证“产品的合法身份”而引入的,它是用来保障用户的正版权益,享受合法服务的;一套正版的产品只对应一组产品序列号。
ESN (Electronic Serial Number )
美国联邦通信委员会规定的,每一台移动设备(智能手机、平板电脑等)独有的参数,其长度为32位。ESN码一开始使用于AMPS和D-AMPS手机上,当前则于CDMA手机上最为常见;
UUID(Universally Unique Identifier)
通用唯一标识符:一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。由以下几部分组合:当前日期和时间(第一个部分与时间有关),时钟序列,全局唯一的IEEE机器识别号(如果有网卡,从网卡获得,没有网卡以其他方式获得)
GAID(Uni Identifier)
广告ID:是用户特殊、独特、可重置的id,由Google Play Service提供,它用于广告目的的匿名标示符和或者重置起标示符或者退出以利益为基础的Google Play的医用程序。
WIDEVINE_UUID
数字产权管理(DRM)设备 ID。DRM API 中提供一个MediaDrm类,作用是用来获取用于解密受保护的媒体流的密钥。
(2)数据存储
1)存储方式
SP、File、SQLite数据库、网络、ContentProvider
2)Android Q 新变化
Q以上,采用沙箱(Sandboxie)存储机制;
Q以下,采用老的文件存储方式。
沙箱存储机制:
① 访问自己沙盒中的文件无需特定权限
② 访问系统媒体文件(沙盒外的媒体共享文件,如照片\音乐\视频),需要申请新的媒体权限:READ_MEDIA_IMAGES,READ_MEDIA_VIDEO,READ_MEDIA_AUDIO, 申请方法同原来。
③ 访问系统下载文件,暂时没做限制。但要访问其他应用的文件,必须允许用户使用系统
的文件选择器应用来选择文件。
注:Android 10目前已采用以下策略暂时解决兼容,但Android 11将严格采用新的存储机制。
android:requestLegacyExternalStorage="true"
二、解决方案
2.1 谷歌官方推荐方案 (4种)
谷歌官方给予了设备唯一ID最佳做法,但是此方法给出的ID可变。
最佳做法:
1)避免使用硬件标识符。在大多数用例中,您可以避免使用硬件标识符,例如 SSAID (Android ID),而不会限制所需的功能。Android 10(API 级别 29)对不可重置的标识符(包括 IMEI 和序列号)添加了限制。您的应用必须是设备或个人资料所有者应用,具有特殊运营商权限或具有 READ_PRIVILEGED_PHONE_STATE 特许权限,才能访问这些标识符。
2)只针对用户剖析或广告用例使用广告 ID。在使用广告 ID 时,请始终遵循用户关于广告跟踪的选择。此外,请确保标识符无法关联到个人身份信息 (PII),并避免桥接广告 ID 重置。
3)尽一切可能针对防欺诈支付和电话以外的所有其他用例使用实例 ID **或私密存储的 GUID。**对于绝大多数非广告用例,使用实例 ID 或 GUID 应已足够。
4)**使用适合您的用例的 API 以尽量降低隐私权风险。**使用 DRM API 保护重要内容,并使用 SafetyNet API 防止滥用行为。SafetyNet API 是能够确定设备真伪而不会招致隐私权风险的最简单方法。
首先对表1中的设备码进行初步分析, IMEI\MEID号、SN号、DEVICE_ID(一般情况获取手机的也就是手机的IMEI码),Android Q后已禁止获取;MAC ADDRESS在Android Q后返回常量 “02:00:00:00:00:00” ;剩余ANDROID_ID、UUID、WIDEVINE_UUID等信息可以尝试获取。
注:其实还用GAID、IDFASafetyNET、Advertising ID、Firebase InstanceID等方法,但都依赖Google Play Service。中国发行的国行手机,google地图、Play等基础App被阉割掉了,你懂的。
下面我们就详细看看这几个方案具体如何实现,或者有没有别的方案
2.2 实现方案1——GUID方案
该方案是根据谷歌推荐的方案3)私密存储的GUID改进而来(存储改进),具体如下:
1.整体逻辑
第一次生成后存到SP和外部存储的uuid.txt中,下次进入先从SP获取,如果没有依次去外部存储UUID_PATH读取。
2.生成逻辑
1)Android Q之前
利用以前可以拿到的标识(如:SN\Android ID\IMEI\Device ID)去处理(如:混合等)。老的办法网上很多,此处不做介绍;如果为空,则利用randomUUID()随机生成一个。
2)Android Q之后
利用randomUUID()生成一个
uniqueId = UUID.randomUUID().toString()
3.数据存储
方式:SP + 外部存储的隐藏文件夹
注:外部存储路径:
UUID_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/appName/.uuid/.uuid.txt"
4. 优缺点
Q以后的获取方式简单(randomUUID())。
风险包括Q以前的数据获取的唯一性和整体数据存储的稳定性。
① 数据获取(Q以前)
DeviceId (刷机会变)
ANDROID_ID (恢复出厂 + 刷机会变)
SerialNumber (android 9需要设备权限)
此外:ANDROID_ID还具有以下缺点
i)现在网上已有修改设备ANDROID_ID值的APP应用。
ii) 某些厂商定制系统会导致不同的设备产生相同的ANDROID_ID或返回值为null。
iii) 对于CDMA制式的设备,ANDROID_ID和DeviceId返回的值相同
② 数据存储
SP中的数据会随着app的卸载而消失;
隐藏文件夹中存储的uuid.txt存在被用户手动删除或被系统和第三方管理软件清理的风险。
5. 改进方向
1)数据获取——降低的重复率
String uuid = new Date().getTime() + "_AndroidQ_" + UUID.randomUUID().toString();
该方法拼接了写入时间的毫秒数+标志位(也可加设备信息等)+UUID的组合,以降低重复。
测试结果:uuid = 1597109073335_AndroidQ_07e0ee6f-5d6e-41c1-8dc1-725fab218b3f
/生成15位唯一性的设备ID号
private static String getRandomUUID() {
int random = (int) (Math.random() * 9 + 1);
String valueOf = String.valueOf(random);
int hashCode = UUID.randomUUID().toString().hashCode();
if (hashCode < 0) { hashCode = -hashCode; }
String value = valueOf + String.format("%014d", hashCode);
return value;
该方法将获取的随机uuid生成其hashCode值,并进行字符串格式化处理,提高其唯一性。
测试结果:value = 400001981159719
2)数据存储——存储文件名提醒
文件名(现为uuid.txt)建议使用警示语,例如System XXX、重要文件勿动之类的,降低用户手动删除的风险。
注:数据存储适配。由于权限变化,Android 11后现有存在SD卡里文件的方法需要做适配。
2.3 实现方案2——数字版权管理(DRM)方案
该方案是根据谷歌推荐的方案4)使用适合用例的 API 以尽量降低隐私权风险演变而来。主要是使用 DRM API中的MediaDrm,它的作用是来获取用于解密受保护的媒体流的密钥。
Android 开发者和数字内容发布者需要在整个Android 生态系统中实现受支持的一致的 DRM。为了让这类数字内容适用于 Android 设备,并确保至少有一个一致的 DRM 可用于所有设备,Google 会在兼容的 Android 设备上提供无需支付许可费用的DRM。DRM 插件会与 Android DRM 框架集成在一起,并可使用受硬件支持的保护功能来确保付费内容和用户凭据的安全。
1.整体逻辑
MediaExtractor和 MediaCodec对象访问DRM方案标识的UUID(通常来自内容中的元数据),并使用此UUID构造能够支持内容所需的DRM方案的MediaDrm对象的实例。然后利用实例化的MediaDrm对象获取字符串常量PROPERTY_DEVICE_UNIQUE_ID得到设备唯一ID的字节型数组,再将此数组转化为字符串保存起来。
2.核心代码
UUID wideVineUuid = new UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L)
wvDrm = new MediaDrm(wideVineUuid)
byte[] wideVineId = wvDrm.getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID)
uniqueId = Arrays.toString(wideVineId)
原理:发现源码里面写了native(点击此处),再往底层有关的看不到了(一般也不会让你知道),猜测跟sessionId有关。
源码如下:
* Read a MediaDrm byte array property value, given the property name string.
* Standard fields names are {@link #PROPERTY_DEVICE_UNIQUE_ID}
@NonNull
public native byte[] getPropertyByteArray(@ArrayProperty String propertyName);
* Set a MediaDrm byte array property value, given the property name string
* and new value for the property.
public native void setPropertyByteArray(@NonNull @ArrayProperty
String propertyName, @NonNull byte[] value);
private static final native void setCipherAlgorithmNative(
@NonNull MediaDrm drm, @NonNull byte[] sessionId, @NonNull String algorithm);
private static final native void setMacAlgorithmNative(
@NonNull MediaDrm drm, @NonNull byte[] sessionId, @NonNull String algorithm);
@NonNull
private static final native byte[] encryptNative(
@NonNull MediaDrm drm, @NonNull byte[] sessionId,
@NonNull byte[] keyId, @NonNull byte[] input, @NonNull byte[] iv);
@NonNull
private static final native byte[] decryptNative(
@NonNull MediaDrm drm, @NonNull byte[] sessionId,
@NonNull byte[] keyId, @NonNull byte[] input, @NonNull byte[] iv);
@NonNull
private static final native byte[] signNative(
@NonNull MediaDrm drm, @NonNull byte[] sessionId,
@NonNull byte[] keyId, @NonNull byte[] message);
private static final native boolean verifyNative(
@NonNull MediaDrm drm, @NonNull byte[] sessionId,
@NonNull byte[] keyId, @NonNull byte[] message, @NonNull byte[] signature);
1)UUID (Universally Unique Identifier)即通用唯一识别码。
目的是让分布式系统中的所有元素,都能有唯一的辨识信息,而不需要通过中央控制端来做辨识信息的指定。UUID是指在一台机器上生成的数字,由当前日期和时间、时钟序列、全局唯一的IEEE机器识别号等几部分组合形成,它可以保证对在同一时空中的所有机器都是唯一的。UUID在一般情况下很难生成一致的编码。
2)MediaDrm API文档说明
PROPERTY_DEVICE_UNIQUE_ID Added in API level 18
String PROPERTY_DEVICE_UNIQUE_ID
字节数组属性名称:设备唯一标识符在设备设置期间建立,并提供了唯一标识每个设备的方法。
常量值:“deviceUniqueId”
3.测试结果
集成测试结果简述如下:
1)所有已测试的设备在手动删除SD卡上存储的uuid.txt后,卸载重装(删除SP),获取到的uniqueId仍然唯一。
2)绝大部分测试设备恢复出厂设置后(除XiaoMi9 Pro 5G),获取到的uniqueId与恢复出厂前一致。
表2 利用DRM方案获取设备唯一ID的测试结果
XiaoMi9 Pro 5G(Android 10)
[-103, 10, 1, -106, -124, -91, 92, -122, 93, -28, 80, -23, -39, 70, -5, 11, -49, -84, -49, -30, -63, 100, 115, -24, -77, -38, 82, -63, 29, 116, 27, 4]
HUAWEI P20(Android 10)
[81, -73, -54, -58, 59, 106, -57, 100, -31, -32, -60, -56, -108, -22, 96, -77, -60, -93, 55, -12, -85, 28, 71, -88, -65, 93, 22, 21, 38, 13, 113, -63]
OPPO A59m(Android 5.1)
[82, 111, 121, 76, 121, 74, 80, 86, 69, 72, 115, 99, 67, 77, 85, 81, 75, 72, 66, 83, 72, 76, 79, 76, 79, 86, 75, 116, 106, 107, 72, 0]
此外,还测试过的设备有:HUAWEI Mate20(Android10)、OnePlus8(Android 10)、OPPO R9m(Android 5.1)恢复出厂设置前后获取的设备ID一致。
4.优缺点
适配性好:不用分版本(Q以前&Q以后)做适配,测试设备都可用此方法。
唯一性好:根据测试结果显示,应用该方案获取的设备ID唯一性好。
稳定性高:DRM API 中的MediaDrm类为谷歌官方推荐方法4,稳定性较高,只需关注官
方文档更新即可。
部分厂商已经开始注重保护用户隐私,如小米的MIUI系统,恢复出厂设置后利用MediaDrm已经获取不到一致的标识号,后期存在其他手机厂商也收紧这一方法的风险。
5.改进方向
1)数据存储——网络存储
数据存储可以结合后台进一步优化,可以将数据存储到服务器。
大致逻辑:
① 本地生成一个UUID当唯一标识;
② 获取硬件的所有硬件编码和UUID进行绑定,上传至后台数据库,如果应用本地删除或者卸载,下次进入应用根据硬件编码获取UUID;
后台请求逻辑:
① App 请求参数是本地UUID或者刚生成的UUID 和所有App硬件编码标识;
② 后台先根据UUID查询是否存在这个编码,如果存在就返回这条信息;如果没有,根据硬件编码标识(如果不为空)去查询,如果硬件编码标识和数据库里面有两条一样的,那么把这条记录返回给客户端,那么返回的UUID 就是之前保存的那个UUID;如果没有查到请求的UUID和硬件编码标识,则认为是新设备。
2)数据获取——适配处理
目前发现小米恢复出厂后,获取的ID会变,可利用1)中方案或者下文的方案3做适配。
2.4 实现方案3——自定义ID硬件信息拼凑方案
该方案是根据谷歌推荐的方案1)避免使用硬件标识符改进而来(这也行?不是说尽量避免使用硬件信息么。老哥,他说的是避免,又没说不让。)虽然咋们用不了这些:Android ID、IMEI 、SN,但还有别的呀!具体如下:
1.整体逻辑
不依赖随机生成的UUID,将多个可获得的硬件标识(最大程度上降低重复性)拼接起来(尽可能不依赖权限),生成设备唯一ID。如果硬件信息不变,则UUID值不变。
2.硬件信息筛选
设备硬件信息近30种,但不是每种都适合作为自定义ID的来源。需要考虑唯一性和稳定性。测试的华为手机硬件信息详见下表。
表3 常见设备信息
经过对测试信息的分析评估后,发现所有测试设备的以下信息一致,对唯一性贡献度较小,包括Build.CPU_ABI、Build.TAGS 、Build.TYPE、VERSION.SDK_INT、SUPPORTED_32_BIT_ABIS、VERSION.RELEASE、VERSION.CODENAME、VERSION.SECURITY_PATCH等,且部分测试设备的Build.BOOTLOADER信息为unknown,VERSION.BASE_OS信息为空,PREVIEW_SDK_INT导致崩溃,稳定性极差,这些硬件因素排除。
参照网络上大部分方案选取的配置参数名:
Build.BOARD.length() % 10 + Build.BRAND.length() % 10 +
Build.CPU_ABI.length() % 10 + Build.DEVICE.length() % 10 +
Build.DISPLAY.length() % 10 + Build.HOST.length() % 10 +
Build.ID.length() % 10 + Build.MANUFACTURER.length() % 10 +
Build.MODEL.length() % 10 + Build.PRODUCT.length() % 10 +
Build.TAGS.length() % 10 + Build.TYPE.length() % 10 +
Build.USER.length() % 10;
分析筛选后选取的配置参数名:
Build.BOARD.length() % 10 + Build.BRAND.length() % 10 +
Build.DEVICE.length() % 10 + Build.USER.length() % 10 +
Build.DISPLAY.length() % 10 + Build.HOST.length() % 10 +
Build.ID.length() % 10 + Build.MANUFACTURER.length() % 10 +
Build.MODEL.length() % 10 + Build.PRODUCT.length() % 10 +
Build.HARDWARE.length() % 10 + Build.FINGERPRINT.length() % 10 +
Build.VERSION.INCREMENTAL.length() % 10 + Build.getRadioVersion().length() % 10 +
Build.TIME % 10;
3.测试结果
表4 利用自定义ID方法测试结果
4.优缺点
优点:限制性较小,只要硬件信息不变结果就不变。而且该方案可自行定制组合
缺点:1)拼接设备ID的唯一性不是最高的,同样的设备有可能会产生同样的号码;
2)贡献的硬件因素的如果其中一个改变(虽然目前这些硬件信息变动几率很小),拼接得到
的ID就会改变。所以如果硬件因素的数量过多,导致ID改变的概率也就变大,所以要在唯
一性和稳定性之间做一个很好的平衡。
5.改进方向
优化1:拼接设备ID需要在稳定性和唯一性之间权衡,如何做到双赢?
举例1:假如出现重复的概率和发生变化的的概率都是1/1000,则对于两台不同设备,两个设备ID同时重复的概率是1/1000000,两个设备ID至少有一个发生变化约为2/1000。也就是,拼接ID的效果是大大提高唯一性,但是一定程度上降低稳定性(只要其中一个要素变化,拼接的ID就变了)。实际上,如今拿到的设备ID,最突出的矛盾是不稳定,所以,不能为了提高唯一性而牺牲稳定性。
方案1:引入容错方案,在确保唯一性的同时提高稳定性。
"拜占庭容错"方案:采集三个设备ID到服务器端,如果有两个(包括两个以上)的设备ID和之前的记录相同,则认为是同一台设备。
例如1:假设出现重复的概率和发生变化的的概率都是1/1000,则:同一台设备的两次采集,认不出是同一台设备的条件为“至少两个设备ID都和上次不一样”,概率约为3/1000000。两台不同的设备,认为是同一台的条件是为“三个设备ID中,至少有两个设备ID和另一台设备相同”,概率同样约为3/1000000。
结论1:用"拜占庭容错"方案,唯一性和稳定性都能得到提高。
注:涉及服务器端,具体实现可参考此处。
优化2:Build类是提供给上层应用调用的,但Build.CPU_ABI的获取已经显示不建议,后期会不会限制的硬件信息更多?有何办法?
方案2:可以通过SystemProperties类获取硬件参数。
SystemProperties这个类是不对上层开放的,所以要通过反射机制获取到该类的“get”方法,然
后通过该方法获取手机Build.prop文件中的参数。此外,SystemProperties类还能获取到Build
类获取不到的信息。相当于绕过Build类,通过下层接口来获取硬件信息。
例如2:获取Build.FINGERPRINT这个硬件信息,这个参数在build.prop文件里对应的key为“ro.build.fingerprint”,那么可以通过:
try{
Class systemProperties=Class.forName("android.os.SystemProperties");
Method get=systemProperties.getDeclaredMethod("get", String.class);
String fingerprint=(String)get.invoke(null, "ro.build.fingerprint");
Log.d("zz", "Fingerprint:"+fingerprint);
}catch (Exception e){
2.5 实现方案4——移动安全联盟方案
1.产生背景
根据“移动智能终端补充设备标识体系”技术要求,华为、小米、OPPO、vivo、中兴、努比亚、魅族、联想、三星等设备厂商均将逐步实现本标识体系,联盟计划开发并发布支持多厂商的统一的补充设备标识调用SDK,协助移动应用开发者更便捷的访问移动智能终端补充设备标识体系,推进相关业务。
官网地址:移动安全联盟、vivo开发平台移动设备标识服务、小米移动设备标示服务、百度-Android OAID 接入
三星适配指导
相关资料:SDK、标识规范、开发者说明文档、常见问题
2.解决方案
1)类型及特性
提供的类型有4种,如下:
表6 中国信息通信研究院所给解决方案
A.设备唯一标识符UDID: 是指设备唯一硬件标识,设备生产时根据特定的硬件信息生成,可用于设备的生产环境及合法性校验。
B.匿名设备标识符OAID: 是可以连接所有应用数据的标识符,移动智能终端系统首次启动后立即生成,可用于广告业务。
C.开发者匿名设备标识符VAID: 是指用于开放给开发者的设备标识符,可在应用安装时产生,可用于同一开发者不同应用之间的推荐。
D.应用匿名设备标识符AAID: 是指第三方应用获取的匿名设备标识,可在应用安装时产生,可用于用户统计等。
相关特性如下:
分析:结合我们用途,分析【重置】这一列,按照符合度依次排列为UDID > VAID > OAID > AAID。UDID最符合要求,但不对外开发;VAID需要卸载说有开发商应用等特殊情况才重置,但恢复出厂就重置了;OAID用户可手动重置,恢复出厂也重置;AAID用户卸载应用就重置了。
结论:最符合的为C.开发者匿名设备标识符VAID方案,但特殊情况下可重置,劣于方案二。
2)体系架构
在这里插入图片描述
图1 补充设备标识体系的总体架构
3.使用方法
集成第三方SDK,过程常规,详细步骤见相关资料中的开发者说明文档。
4.优缺点
优点:国内主流硬件厂商联盟共同维护、统一提供,来源有保障且稳定
缺点:1)目前只支持国产手机,三星手机不支持,而且国内手机也不一定全部支持;
2)引入一个SDK,可能导致费用成本增加,可维护性相对变差;
3)可能存在用户数据安全隐患。
2.6 实现方案5——数字联盟可信ID方案
可信ID是北京数字联盟网络科技有限公司自主研发的移动设备唯一性识别技术,在终端IMEI、MAC、OAID及iOS自带“IDFA”难稳定获取及易被篡改的情况下,通过派发不易被篡改的更稳定终端唯一标识,完美兼容安卓10/iOS14系统,为APP开发者提供更可信来的唯一ID 作为数据运营统计基准,有效识别设备篡改、虚拟机等作弊行为,识别应用登陆及其它后续行为中的作弊风险,数据实时、准确可靠且可用于独立佐证。实时甄别设备唯一性和虚拟机篡改等特征,完整符合国内外隐私安全规范。
2.产品及服务
1)可信ID
业务场景应用:
① 推广/营销活动防刷: 可信ID及对应状态可实时识别虚拟机、作弊环境,结合客户账户体系,在激活、登陆等节点事前识别业务风险。
② 应用全周期洞察: 以可信ID为基准,依托数盟全域覆盖及判定能力,可有效判断应用安装未激活、卸载及换机状态,提供关键运营数据赋能。
2)中国通用广告ID
除了可信ID,北京数字联盟网络科技有限公司还推出了中国通用广告ID方案
中国通用广告 ID 是一款在 Android 10 时代可全面取代 IMEI、移动全平台通用的 ID 体系。已通过 ISO 27001 信息安全管理体系、等保三级等多项国内外隐私保护条款,不追溯具体用户;且支持设备匿名化、匿名化归因及反作弊。
3.优缺点
1)优点:方便获取,兼容IOS;
2)缺点:引入第三方SDK,成本上升;用不上其复杂功能(如防篡改等),浪费;
三、方案对比
3.1 评估准则
1. 准则说明:
1)唯一性:两台不同的设备获取到的设备ID不相同。
分析唯一性,可以从ID的分配来入手:
① 按规则构造
设备ID中的SN 、DeviceId等,都是按照规则构造的,理论上能保证唯一性。设备序列号是对厂商本身唯一,全局唯一需要在加上 Build.MANUFACTURER。不过,SN的唯一要打个问号,因为要看厂商是否遵守规则。但随着手机产业的日渐成熟,传统意义上的山寨设备已越来越少,所以大多数情况下还是唯一的。
② 随机生成
比如UUID和Android ID,这类ID有一定的概率会重复,关键是看ID的长度(有多少bit)。具体可以参照随机数的冲突概率表:
表7 随机数的冲突概率表
说明:第一栏是bit数量,第二栏是对应的取值范围,第三栏是元素个数以及对应的冲突概率。
例如:假设APP累计激活量达到50亿的APP,则会有2个APP有重复的Android ID(如ANDROID_ID:9774d56d682e549c是长度为16的十六进制字符串,即64bit)。总体而言, Android ID的唯一性还是不错。(Java的字符类型采用的是Unicode编码方案,一个字符2个字节,一个字节8个比特位,所以是16位 。)
结论:JDK的randomUUID,大致可认为是128bit的随机数(其中有6bit是固定的),即使到达200亿的数量,有重复的概率也仅仅是10-18,微乎其微。
2)稳定性:同一台设备在不同的时间, 获取到设备ID相同。
稳定性有两个层面:数据来源和版本变化
① 来源稳定
Build.BOARD、Build.BRAND、Build.DISPLAY等都是硬件相关,即使刷机也不会改变;
Android ID等则稳定性较弱,恢复出厂设置和刷机都会改变Android ID。
② 受版本的变化的影响
随着Android版本的提升,Google对权限是越收越紧了。10以上IMEI、SN已经获取不到,MAC、Android ID目前还可获取,但将来有收紧甚至禁用的风险,且不同签名的APP获取的Android ID值还不一样。
3.2 决策分析
经过上述方案对比分析后,在Android Q以后,选择方案二比较合适。
虽然利用DRM的方案二和方案一GUID都比较适合本项目。但是,方案一存在数据被删除的风险,且在删除存储的数据重装和设备恢复出厂设置前后,暂时无法保证对同一用户的识别。方案二经过测试,发现可以保证删除数据重装对用户的唯一识别,且大部分机型在恢复出厂后获取的ID字符串也能保证唯一,在这个点上方案二更具优势。
后期可以根据需要结合数据库作进一步优化,或者结合底层以及android 11中沙箱存储机制,找到恢复出厂不丢失的存储区域或办法。
研究不易,码字费力;转载出力,说明来意。