Android 如何获取有效的DeviceId

news/2024/11/15 8:22:57/

在这里插入图片描述

目录

  • 前言
  • 官方唯一标识符建议
    • 使用广告 ID
    • 使用实例 ID 和 GUID
    • 不要使用 MAC 地址
    • 标识符特性
    • 常见用例和适用的标识符
  • 解决方案
    • DeviceId
    • ANDROID_ID
    • Mac地址
    • UUID
    • 补充
  • 总结

前言

从 Android 10 开始,应用必须具有 READ_PRIVILEGED_PHONE_STATE 特许权限才能访问设备的不可重置标识符(包含 IMEI 和序列号)。

而这个权限是系统权限,也就是说一般应用将无法再获取IMEI 和序列号

受影响的方法包括:

  • Build

    • getSerial()
  • TelephonyManager

    • getImei()
    • getDeviceId()
    • getMeid()
    • getSimSerialNumber()
    • getSubscriberId()

如果您的应用没有该权限,但您仍尝试查询不可重置标识符的相关信息,则平台的响应会因目标 SDK 版本而异:

  1. 如果应用以 Android 10 或更高版本为目标平台,则会发生 SecurityException。
  2. 如果应用以 Android 9(API 级别 28)或更低版本为目标平台,则相应方法会返回 null 或占位符数据(如果应用具有 READ_PHONE_STATE 权限)。否则,会发生 SecurityException。

google也给出了一个解决方案

许多使用场景都不需要不可重置的设备标识符。例如,如果您的应用将不可重置的设备标识符用于广告跟踪或用户分析目的,请为这些特定使用场景使用 Android 广告 ID。要了解详情,请参阅唯一标识符的最佳做法。

这里大部分方案对国内无效,比如广告ID,需要google play的服务,但是国内的手机上都阉割掉了。所以我们只能参考一些可用的方案。

官方唯一标识符建议

这部分我们一起来看官方唯一标识的建议

使用广告 ID

国内就不要考虑了,需要依赖google play服务

使用实例 ID 和 GUID

只对单一应用有效,卸载了就变了,不可取。

不要使用 MAC 地址

MAC 地址具有全局唯一性,无法由用户重置,在恢复出厂设置后也不会变化。因此,一般不建议使用 MAC 地址进行任何形式的用户标识。运行 Android 10(API 级别 29)和更高版本的设备会报告不是设备所有者应用的所有应用的随机化 MAC 地址。

在 Android 6.0(API 级别 23)到 Android 9(API 级别 28)中,无法通过第三方 API 使用 Wi-Fi 和蓝牙等本地设备 Mac 地址。WifiInfo.getMacAddress() 方法和 BluetoothAdapter.getDefaultAdapter().getAddress() 方法都返回 02:00:00:00:00:00

此外,在 Android 6.0 到 Android 9 版本中,您还必须拥有下列权限,才能访问通过蓝牙和 Wi-Fi 扫描获得的附近外部设备的 MAC 地址:

方法/属性所需权限
WifiManager.getScanResults()ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION
BluetoothDevice.ACTION_FOUNDACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION
BluetoothLeScanner.startScan(ScanCallback)ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION

所以,mac是仅次于DeviceId的靠谱的标识,不过android 6.0之后获取不到了。不过有其他方法完善,见后面。

标识符特性

一堆废话

常见用例和适用的标识符

也是一堆废话,要么就是国内无法使用,不过提到了SSAID。

SSAID,即ANDROID_ID(Settings.Secure.ANDROID_ID),在8.0系统迎来改变,具体如下:

对于在 OTA 之前安装到某个版本 Android 8.0(API 级别 26)的应用,除非在 OTA 后卸载并重新安装,否则 ANDROID_ID 的值将保持不变。要在 OTA 后在卸载期间保留值,开发者可以使用密钥/值备份关联旧值和新值。

对于安装在运行 Android 8.0 的设备上的应用,ANDROID_ID 的值现在将根据应用签署密钥和用户确定作用域。应用签署密钥、用户和设备的每个组合都具有唯一的 ANDROID_ID 值。因此,在相同设备上运行但具有不同签署密钥的应用将不会再看到相同的 Android ID(即使对于同一用户来说,也是如此)。

只要签署密钥相同(并且应用未在 OTA 之前安装到某个版本的 O),ANDROID_ID 的值在软件包卸载或重新安装时就不会发生变化。

即使系统更新导致软件包签署密钥发生变化,ANDROID_ID 的值也不会变化。

可以看到8.0之后ANDROID_ID是与应用签名关联的,同签名的应用共用相同的ANDROID_ID,而且卸载重装不会变化。

而8.0之前,ANDROID_ID是与设备关联的,当设备首次启动时,系统会随机生成一个64位的数字,并以16进制字符串的形式保存到手机系统中,当手机恢复出厂设置后,Android ID会被重置,这是Android ID与Device ID的主要区别。当然还有其他bug,比如有些厂家获取为null之类的。

所以,ANDROID_ID是可以考虑的选择之一,后面细说。

解决方案

想要一个行为获取稳定的DeviceId是不可能的,我们需要多个行为结合处理。

DeviceId

首先就是传统的DeviceId,在Android 10一下还是很稳定的。

ANDROID_ID

在Android 8.0之后,就可以考虑用ANDROID_ID来代替DeviceId了。

Settings.System.getString(BaseApp.getAppContext().getContentResolver(), Settings.Secure.ANDROID_ID);

这样可以做一个版本判断,低于10.0(或8.0)获取DeviceId,否则获取ANDROID_ID

Mac地址

如果上面两步获取的还是null,那么可以使用mac地址,但是mac由于6.0之后无法通过WifiInfo.getMacAddress()获取了,所以我们需要处理一下,代码如下:

public static String getMac(Context context) {String mac = "";if (context == null) {return mac;}if (Build.VERSION.SDK_INT < 23) {mac = getMacBySystemInterface(context);} else {mac = getMacByJavaAPI();if (TextUtils.isEmpty(mac)){mac = getMacBySystemInterface(context);}}return mac;}@TargetApi(9)
private static String getMacByJavaAPI() {try {Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();while (interfaces.hasMoreElements()) {NetworkInterface netInterface = interfaces.nextElement();if ("wlan0".equals(netInterface.getName()) || "eth0".equals(netInterface.getName())) {byte[] addr = netInterface.getHardwareAddress();if (addr == null || addr.length == 0) {return null;}StringBuilder buf = new StringBuilder();for (byte b : addr) {buf.append(String.format("%02X:", b));}if (buf.length() > 0) {buf.deleteCharAt(buf.length() - 1);}return buf.toString().toLowerCase(Locale.getDefault());}}} catch (Throwable e) {}return null;
}private static String getMacBySystemInterface(Context context) {if (context == null) {return "";}try {WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);if (checkPermission(context, Manifest.permission.ACCESS_WIFI_STATE)) {WifiInfo info = wifi.getConnectionInfo();return info.getMacAddress();} else {return "";}} catch (Throwable e) {return "";}
}

可以看到6.0即23以下直接获取,否则先通过NetworkInterface获取,获取不到再通过原方法获取。
目前来看这一步还是能稳定获取的。

UUID

兜底行为。因为需要我们手动生成,且每次生成的都不一样。

UUID.randomUUID().toString()

所以必须生成一次保存起来。这样就有一个问题,如果保存到应用内部存储,卸载后重装一定要重新生成,这样就无法判断是同一设备了。

所以最好将其保存到外部存储,保证卸载重装后还能读取到上次的值。

这样一般情况下是最稳定的,除非手动删除该文件。

所以最好的方案,就是将上面四个方案融合在一起,一个个兜底。目前来看,各手机厂商的指导方案也就这几个方案。

补充

除了上面的方案,还有移动安全联盟(信通院牵头)提供的sdk,可以获取几种设备标识符,大部分国内厂商都支持。

不过需要申请使用,还没测试过。

总结

通过上面分析可以看到,官方确实给出了不少替代方案,但是大部分都由于国内的限制而无法使用。所以国内基本上都是通过依次获取DeviceId、ANDROID_ID、MAC、UUID的方式来得到一个唯一id,流程大致如下:

不存在
不存在
不存在
存在
存在
存在
DeviceId?
ANDROID_ID?
MAC?
生成UUID
返回

你可能感兴趣:
Android 13发布,一起来看看有哪些新功能

详细解读Android中的事件分发机制


http://www.ppmy.cn/news/72553.html

相关文章

图像去雾的简单介绍

文章目录 图像去雾的简单介绍1. 图像去雾的定义2. 图像去雾的原理3. 图像去雾的方法3.1 基于暗通道先验的方法3.2 基于物理模型的方法3.3 基于深度学习的方法 4. 图像去雾的应用5. 总结 图像去雾的简单介绍 图像去雾是一项重要的图像处理技术&#xff0c;可以提高图像的可见性…

TikTok选品,什么产品在TikTok最受欢迎?

TikTok是当下的海外社交媒体推广新风口已经有目共睹&#xff0c;与此同时跨境商家们或许都面临着同样的问题&#xff0c;就是TikTok要怎么选品&#xff0c;什么产品才会比较受欢迎。俗话说&#xff1a;七分靠选品&#xff0c;三分靠运营。选品选对了&#xff0c;运营也就事半功…

STM32 使用SYN6288语音模块

文章目录 前言一、SYN6288介绍二、SYN6288原理讲解三.数据的异或校验四.代码编写1.串口的初始化2.SYN6288代码编写3.main函数逻辑 总结 前言 本篇文章带大家学习一下SYN6288语音模块&#xff0c;这个模块可以用于车载设备&#xff0c;语音电子书等众多产品当中&#xff0c;而且…

看完这篇文章你就彻底懂啦{保姆级讲解}-----(I.MX6U驱动UART串口通信) 2023.5.20

目录 前言整体文件结构源码分析&#xff08;保姆级讲解&#xff09;串口驱动初始化部分UART1数据收发函数部分 编译结果验证结束语 前言 串口是我们在开发过程中最常用到的外设&#xff0c;所以我们必须掌握。 整体文件结构 源码分析&#xff08;保姆级讲解&#xff09; 串口…

java设计模式之抽象工厂模式

什么是抽象工厂设计模式&#xff1f; 抽象工厂模式是一种创建型设计模式&#xff0c;它提供了一种创建一系列相关或依赖对象的方法&#xff0c;而无需指定它们具体的类。抽象工厂模式是工厂方法模式的扩展&#xff0c;它使用一组相关的工厂来创建对象&#xff0c;而工厂方法模…

【JVM】6. 堆

文章目录 6.1. 堆&#xff08;Heap&#xff09;的核心概述6.1.1. 堆内存细分6.1.2. 堆空间内部结构&#xff08;JDK7&#xff09;6.1.3. 堆空间内部结构&#xff08;JDK8&#xff09; 6.2. 设置堆内存大小与OOM6.2.1. 堆空间大小的设置6.2.2. OutOfMemory举例 6.3. 年轻代与老年…

全国职业院校技能大赛网络建设与运维赛项赛题(九)

全国职业院校技能大赛 网络建设与运维 赛题 (九)

Asp.Net Core MVC开发 基础入门教程

一、Asp.Net Core MVC开发 基础入门教程1.Asp.Net Core MVC概念、历史、发展和Vue简介 MVC&#xff1a;Model-View-Controller&#xff08;模型-视图-控制器&#xff09;&#xff0c;MVC是一种软件开发架构模式。 学习参考&#xff1a;https://www.cnblogs.com/tianma3798/p/3…