记得去年我在做一个设备管理应用时,突然发现原本运行良好的设备识别功能在Android 11上失效了。调试了半天才发现,原来从Android 10开始,Google就在逐步收紧设备标识符的访问权限。这就像小区门禁突然升级,以前随便进出的快递员现在需要严格登记了。
Android 11最显著的变化就是彻底封杀了通过Build.getSerial()获取设备序列号的常规途径。即使你申请了READ_PHONE_STATE权限,返回的也永远是UNKNOWN。这背后是Google推行隐私保护政策的结果,他们希望减少应用对设备的追踪能力。
我翻看系统源码时发现,关键限制出现在DeviceIdentifiersPolicyService.java中。系统会检查调用者的包名和权限,不符合条件的直接返回UNKNOWN。有趣的是,这个检查逻辑其实在Android 10就已经存在,但直到Android 11才被严格执行,就像新法规出台后突然加强的执法力度。
当Serial Number不可用时,Settings.Secure.ANDROID_ID成为了最直接的替代品。这个64位的十六进制字符串在应用沙盒内是唯一的,我用下面这段代码就能获取:
java复制String androidId = Settings.Secure.getString(
getContentResolver(),
Settings.Secure.ANDROID_ID
);
但要注意三个坑:
我在项目中就遇到过第三个问题,某品牌平板电脑上的ANDROID_ID全是相同的。后来我们不得不在服务端做了额外的校验逻辑。
Google Play服务提供的广告ID是另一个选择:
java复制AdvertisingIdClient.Info adInfo = AdvertisingIdClient.getAdvertisingIdInfo(context);
String adId = adInfo.getId();
它的优势在于用户可以随时重置,符合GDPR要求。但缺点也很明显:
我建议在电商类App中使用它做推荐系统,但不适合用于关键业务逻辑。
单一标识符都不完美,于是我尝试组合多个硬件特征:
java复制String fingerprint = Build.BOARD + Build.BRAND +
Build.DEVICE + Build.DISPLAY +
Build.HOST + Build.MANUFACTURER +
Build.MODEL + Build.PRODUCT;
这个指纹在80%的设备上都是唯一的。为了提高准确率,我还加入了以下特征:
实测发现,配合机器学习算法,这种方案的识别准确率能达到95%以上。
为了避免每次重新生成指纹,我采用三级存储策略:
关键代码示例:
java复制// AES加密存储
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encrypted = cipher.doFinal(deviceId.getBytes());
// 写入文件
try (FileOutputStream fos = openFileOutput("device.id", MODE_PRIVATE)) {
fos.write(encrypted);
}
对于MDM(移动设备管理)场景,我们可以申请特殊权限:
xml复制<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
但这个权限只对系统应用和特定白名单应用开放。去年我们给某车企定制ROM时,就是通过预装系统应用的方式解决了这个问题。
当需要识别同一用户的多个设备时,我推荐使用账户系统+加密令牌的方案:
这样即使更换设备,只要用户登录就能保持关联。我们在社交App中实施这套方案后,跨设备识别准确率提升了40%。
在适配过程中我踩过不少坑,这里分享三个典型案例:
案例一:厂商兼容性问题
某次更新后,华为设备突然大量报错。排查发现是EMUI修改了Build类的部分字段返回值。解决方案是增加厂商判断逻辑:
java复制if (Build.MANUFACTURER.equalsIgnoreCase("huawei")) {
// 特殊处理逻辑
}
案例二:Android Go设备异常
低配设备上组合指纹的碰撞率较高。我们最终通过降低特征维度(只保留BOARD+MODEL)并增加时间戳来解决。
案例三:用户清除数据
有位用户反馈每次重启App都会收到新手引导。后来我们发现他把"存储"权限关了,导致无法持久化设备ID。现在我们会优雅降级到会话ID方案。
最佳实践总结:
记得在代码中加入足够的日志,但要注意隐私合规。我通常这样记录:
java复制Log.d("DeviceId", "Generated fingerprint: " +
DigestUtils.sha256Hex(deviceFingerprint));