浅谈设备唯一 ID

一、前言

设备ID,简单来说就是一串符号(或者数字),映射现实中一台设备。如果这些符号和现实中的设备是一一对应的,可称之为“唯一设备ID(Unique Device Identifier)”。然而,对于Android平台而言,没有稳定的API可以让开发者获取到这样的设备ID。

开发者通常会遇到这样的困境:

  • 随着项目的演进, 越来越多的地方需要用到设备ID

  • 然而随着Android版本的升级,获取设备ID却越来越难了。

  • 加上Android平台碎片化的问题,获取设备ID之路,可以说是步履维艰。

二、设备ID的作用

  • 统计需求:统计需求是设备ID最常见的用途,包括DAU, MAU的统计,行为统计,广告激活的统计等。

  • 业务需求:结合行为统计做用户画像、定向推送、构造分布式ID等

  • 风控需求:防刷单、反作弊

三、获取设备ID的API

获取设备标识的API屈指可数,而且都或多或少有一些问题。常规的API有以下这些

  • IMEI

  • 设备序列号

  • MAC地址

  • ANDROID_ID

IMEI

  • IMEI本该最理想的设备ID,具备唯一性,恢复出厂设置不会变化(真正的设备相关)。

  • 然而,获取IMEI需要 READ_PHONE_STATE 权限,尤其是Android 6.0以后, 这类权限要动态申请,很多用户可能会选择拒绝授权。有的APP不授权这个权限就无法使用, 这可能会降低用户对APP的好感度。

  • 而且,Android 10.0 将彻底禁止第三方应用获取设备的IMEI, 即使申请了 READ_PHONE_STATE 权限。

  • 所以,如果是新APP,不建议用IMEI作为设备标识

  • 如果已经用IMEI作为标识,要赶紧做兼容工作了,尤其是做新设备标识和IMEI的映射。

设备序列号

  • 通过android.os.Build.SERIAL获得,由厂商提供。

  • 如果厂商比较规范的话,设备序列号+Build.MANUFACTURER应该能唯一标识设备。现实是并非所有厂商都按规范来,尤其是早期的设备。但随着手机产业的日渐成熟,传统意义上的山寨设备已越来越少,所以大多数情况下还是唯一的。

  • 最致命的是,Android 8.0 以上,android.os.Build.SERIAL 总返回 “unknown”;要获取序列号,可调用Build.getSerial() ,但是需要申请 READ_PHONE_STATE 权限。

  • 总体来说,设备序列号有点鸡肋:食之无味,弃之可惜。

MAC地址

  • 获取MAC地址也是越来越困难了,

  • Android 6.0以后通过 WifiManager 获取到的mac将是固定的:02:00:00:00:00:00,

  • 再后来连读取 /sys/class/net/wlan0/address 也获取不到了。

  • 如今只剩下面这种方法可以获取(没有开启wifi也可以获取到):

img

ANDROID_ID

Android_ID 由 签名+用户+设备 唯一确定

规则变化

  1. 对于升级到8.0之前安装的应用,ANDROID_ID会保持不变。如果卸载后重新安装的话,ANDROID_ID将会改变。
  2. 对于安装在8.0系统的应用来说,ANDROID_ID根据应用签名和用户的不同而不同。(可能对于广告联盟之类的有所影响),所以Google文档中说“请使用Advertising ID”,但Google的服务在国内用不了。
  • **优点:**获取门槛是最低的,不需要任何权限、64bit 的取值范围,唯一性算是很好的了。

  • 不足:

    • 刷机、root、恢复出厂设置等会使得 Android ID 改变;
    • Android 8.0之后,Android ID的规则发生了变化
  • **总结:**对Android ID做了约束,对隐私保护起到一定作用,并且用来做APP自己的活跃统计也还是没有问题的。

四.设备ID的特性分析

  • **唯一性:**两台不同的设备获取到的设备ID不相同;
  • **稳定性:**同一台设备在不同的时间, 获取到设备ID相同。

4.1唯一性

  • 按规则构造:自增ID(包括分步自增),分段构造的ID(如snowflake算法)等,此类ID能保证唯一性。(eg:IMEI、设备序列号(Build.MANUFACTURER)、MAC)
    1. 设备ID中的,都是按照规则构造的,理论上能保证唯一性。
  • 随机生成:比如UUID和Android ID,这类ID有一定的概率会重复,关键是看ID的长度(有多少bit)。
    1. 32bit取值范围有43亿;50000个32bit的随机数中,至少有两个相同数字的概率为25%
    2. Android ID :lenght=16的十六进制字符串(64bit);50 亿 50%;
    1. randomUUID:128bit(6bit);200亿;10的负18次方

4.2稳定性

  • ID的生命周期:IMEI,序列号,MAC等都是硬件相关,即使刷机也不会改变;Android ID则稳定性较弱,恢复出厂设置和刷机都会改变Android ID。
  • 受版本的变化的影响:随着Android版本的提升,Google对权限是越收越紧了。获取设备ID的API,要么收起不给用(IMEI), 要么获取变得困难(SERIAL ),要么不同签名的APP获取的值不一样(Android ID)。

同时,Android 10中存储权限也收缩了,之前的那种生成唯一ID写到SD卡的某个角落的,以求卸载重装后读之前的ID等方法也不奏效了。

五、唯一设备ID的构造

  • 前面分析的设备ID中,在可用的前提下,出现重复的概率较小;
  • 如何在本来就较小的概率的前提下,继续降低概率呢?

5.1方案分析

  • 组合设备ID(直接拼接,或者拼接后计算摘要):大大提高唯一性,但是一定程度上降低稳定性(只要其中一个要素变化,拼接的ID就变了)。
  • 引入"拜占庭容错"方案:简单地说,就是要采集三个设备ID到云端,如果有两个(包括两个以上)的设备ID和之前的记录相同,则认为是同一台设备。

5.2 具体实现

基本思想:

服务端有一张设备 ID 的表,核心的属性(****Column)有:img

客户请求时,上传三个设备 ID**,服务端检索****😗*

img

如果检索到记录,其中至少两个did和上传的相同,则返回 id;

否则,插入上传的三个设备 ID**,并将新插入记录的** id 返回。

通常情况下,服务端表的主键为自增序列(为了确保插入的有序性),所以我们不能直接返回表的主键,否则容易被他人推测其他的设备 ID,以及知晓用户数量。

因此,在主键 ID 之外,我们需要另外一个唯一 ID。

六、生成单独的唯一 ID 的两种思路

  • 随机化,比如用randomUUID:这种方案优点是具有隐蔽性,从UUID完全不可能得知主键ID,但是占空间,检索效率一般。
  • 根据主键 id 经过计算出另一个id(也是Long类型):此方案优点是节省空间,检索快,但是要求和主键ID一一映射,以确保不会重复,同时要求计算结果有离散性(计算结果和原ID,以及原ID较少的改变,比方说+1,会引起结果的巨大变化)。

选择需要的三个设备 ID

  • IMEI 需要 READ_PHONE_STATE 权限,而且Android 10以后取不到了,所以我们这里就不讨论 IMEI 了。

  • Android ID 和 MAC地址都还可以取到,如果有 READ_PHONE_STATE 权限,那么设备序列号也有了,实施方案的条件就凑齐了。

  • 利用设备相关信息做补充

结合机型,保守估计有上千甚至上万种可能性,相对Android ID 的 2^64 当然相差很远了,但是仍可作为辅助的参考信息。

关于硬件信息

  • 需满足一个要求:在设备重启、恢复出厂设置等操作之后,不会变化。

  • 常规信息有CPU核心数,RAM/ROM大小(以Gb为单位采集,而不是精确到比特,否则容易变化),屏幕分辨率和dpi等,结合机型,保守估计有上千甚至上万种可能性,相对Android ID 的 2^64 当然相差很远了,但是仍可作为辅助的参考信息。

  • 常规的设备信息容易遭到篡改,所以,在常规信息之外,我们可以挖掘一些冷门的设备特征,比如 NetworkInterface 和 传感器 的相关信息。

  • 为了方便检索,我们可以用MurmurHash将信息压缩到64bit(Long的长度)。

匹配规则:

img

解释一下:

  1. 如果设备序列号、Android ID、MAC全都不等,则前面的SQL查询不会返回记录(也就是没有匹配的设备)。

  2. 如果设备序列号,Android ID 和 MAC 全部相同,直接返回。

  3. 否则,给deviceId分配优先级,然后放入优先队列(最大堆),遍历列表结束后,取优先级最高的deviceId(堆顶元素)返回。

  4. 如果优先队列没有放入元素,则 queue.peek() 返回null。

  5. 如果只有Android ID 或 MAC 之一相等,但是设备信息都匹配不上的话,也认为不是同一个设备。

  6. 如果没有匹配的设备,则认为是新设备;生成新的udid返回,同时插入新设备的相关信息(设备ID,硬件信息)。

总结:

  • 按照近几年的趋势,各种设备ID的API或许还会越收越紧,单从客户端去构造可靠的设备ID 是比较困难的,而基于信息采集和云端综合计算则相对容易。
  • 客户端可以定时发送自己的唯一 ID 给服务端进行同步,即使有一个设备参数改变,服务端也能及时更新。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alex_ChuTT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值