Android 如何获取有效的DeviceId

目录
- 前言
- 官方唯一标识符建议
- 使用广告 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 版本而异:
- 如果应用以 Android 10 或更高版本为目标平台,则会发生 SecurityException。
- 如果应用以 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_FOUND | ACCESS_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,流程大致如下:
你可能感兴趣:
Android 13发布,一起来看看有哪些新功能
详细解读Android中的事件分发机制
相关文章:
Android 如何获取有效的DeviceId
目录 前言官方唯一标识符建议使用广告 ID使用实例 ID 和 GUID不要使用 MAC 地址标识符特性常见用例和适用的标识符 解决方案DeviceIdANDROID_IDMac地址UUID补充 总结 前言 从 Android 10 开始,应用必须具有 READ_PRIVILEGED_PHONE_STATE 特许权限才能访问设备的不可…...
<SQL>《SQL命令(含例句)精心整理版(2)》
《SQL命令(含例句)精心整理版(2)》 跳转《SQL命令(含例句)精心整理版(1)8 函数8.1 文本处理函数8.2 数值处理函数8.3 时间处理函数8.3.1 时间戳转化为自定义格式from_unixtime8.3.2 …...
完全自主研发,聚芯微发布3D dToF图像传感器芯片!
日前,由中国半导体行业协会IC设计分会(ICCAD)、芯原股份、松山湖管委会主办的主题为“AR/VR/XR元宇宙”的“2023松山湖中国IC创新高峰论坛”正式在广东东莞松山湖召开。武汉市聚芯微电子有限责任公司发布了完全自主知识产权的3D dToF图像传感…...
MySQL 事物(w字)
目录 事物 首先我们来看一个简单的问题 什么是事务 为什么会出现事务 事务的版本支持 事务提交方式 事务常见操作方式 设置隔离级别 事物操作 事物结论 事务隔离级别 理解隔离性 隔离级别 查看与设置隔离性 注意可重复读【Repeatable Read】的可能问题ÿ…...
字节跳动测试岗四面总结....
字节一面 1、 简单做一下自我介绍 2、 简要介绍一下项目/你负责的模块/选一个模块说一下你设计的用例 3 、get请求和post请求的区别 4、 如何判断前后端bug/3xx是什么意思 5、 说一下XXX项目中你做的接口测试/做了多少次 6、 http和https的区别 7、 考了几个ADB命令/查看…...
基于.NetCore开源的Windows的GIF录屏工具
推荐一个Github上Start超过20K的超火、好用的屏幕截图转换为 GIF 动图开源项目。 项目简介 这是基于.Net Core WPF 开发的、开源项目,可将屏幕截图转为 GIF 动画。它的核心功能是能够简单、快速地截取整个屏幕或者选定区域,并将其转为 GIF动画&#x…...
PCB 基础~典型的PCB设计流程,典型的PCB制造流程
典型的PCB设计流程 典型的PCB制造流程 • 从客户手中拿到Gerber, Drill以及其它PCB相关文件 • 准备PCB基片和薄片 – 铜箔的底片会被粘合在基材上 • 内层图像蚀刻 – 抗腐蚀的化学药水会涂在需要保留的铜箔上(例如走线和过孔) – 其他药水…...
Python logging使用
目录 logging模块 logging核心组件 logger handler StreamHandler:把日志内容在控制台中输出 FileHandler:把日志内容写入到文件中 filter formatter 注意日志级别的继承问题 logger.exception 上述样例的整体代码 日志的配置文件及其模板 lo…...
红黑树的实现原理和应用场景
红黑树的实现原理和应用场景; 有如图所示的表,现在希望查询的结果将列成行 建表语句如下: CREATE TABLE TEST_TB_GRADE2 ( ID int(10) NOT NULL AUTO_INCREMENT, USER_NAME varchar(20) DEFAULT NULL, CN_SCORE float DEFAULT NU…...
idea插件完成junit代码生成,和springboot代码示例
在idea环境下,可以用过插件的方式自动生成juint模板代码。不过具体要需要自己手动编写。 1、安装插件 打开idea,file–settings–plugins,搜索和安装插件(JunitGenerator V2.0和JUnit),安装后,后…...
【Redis面试点总结】
1、缓存 1.1、穿透 查询一个空数据,mysql也查不到也不会写入缓存可能导致多次请求数据库 方案一:缓存设空即可(可能发生数据不一致就是这条数据有了但此时缓存是空,消耗内存) 方案二:布隆过滤器&#x…...
打卡智能中国(五):博士都去哪儿了?
《打卡智能中国》系列更新了几期,有读者表示,很爱看这类接地气的真实故事,也有读者反映,不是电工,就是文员、农民、治沙人,人工智能不是高精尖学科吗?那些学历很高的博士都去哪儿了?…...
[Nacos] Nacos Client获取调用服务的提供者列表 (四)
文章目录 1.Nacos Client获取调用服务的提供者列表1.1 从Ribbon的负载均衡入手到Nacos Client获取调用服务的提高者列表1.2 getServers方法返回分析1.3 通过selectInstances方法查找Instances实例1.4 获取到要调用服务的serviceInfo Nacos Client 从Ribbon负载均衡调用服务。 …...
gcc编译一个程序的步骤(嵌入式学习)
1.预处理(Preprocessing): 在这个步骤中,预处理器将处理与#相关的代码,包括展开头文件、删除无用定义和替换宏定义。预处理器会生成一个经过宏替换和条件编译处理的中间文件。 gcc -E xxx.c -o xxx.i2.编译࿰…...
邹检验,结构变化识别及其R语言实现
在描述多维数据的维度关系时,线性模型无疑应用最多。然而某些情况下,我们关心随着时间变化或随着样本分组,线性关系的具体参数是否发生了变化,即是否发生结构变化Structural break。邹检验Chow test提供了最基本的一种结构变化显著…...
腾讯云,物联网开发平台产品,动态注册步骤
1. 下载后解压,qcloud_iot_mqtt_sign-master.zip GitHub - tencentyun/qcloud_iot_mqtt_signContribute to tencentyun/qcloud_iot_mqtt_sign development by creating an account on GitHub.https://github.com/tencentyun/qcloud_iot_mqtt_sign 2. 按照readme文…...
Padding, Spacer, Initializer 的使用
1. Padding 的使用 1.1 样式一 1) 实现 func testText1()-> some View{Text("Hello, World!").background(Color.yellow) // 背景颜色//.padding() // 默认间距.padding(.all, 10) // 所有的间距.padding(.leading, 20) // 开始的间距.ba…...
少儿编程 中国电子学会图形化编程等级考试Scratch编程四级真题解析(判断题)2023年3月
2023年3月scratch编程等级考试四级真题 判断题(共10题,每题2分,共20分) 11、在使用自定义积木时,不可以传递布尔型参数 答案:错 考点分析:考查自定义积木的使用,使用自定义积木的时候可以传递数字、文本和布尔型参数,所以错误 12、执行如下图程序后,输出的结果为“…...
Makefile学习笔记
目录 一、概述 1.1 Makefile 介绍 1.2规则 1.3核心 1.4示例 1.5定义命令 1.6 make是如何工作的 1.7、makefile中使用变量 1.8让make自动推导 1.9、另类风格的makefile 1.10、清空目标文件的规则 二、Makefile 总述 2.1、Makefile里有什么? 2.2、 mak…...
C++ 函数模板基础
文章目录 一、什么是函数模板二、函数模板的优点1、代码重用2、类型安全3、可读性4、泛型编程5、性能优化6、库开发 三、函数模板的使用场景1、通用操作2、数据结构与容器操作3、排序与查找算法4、数学与统计函数5、类型转换器6、自定义函数对象 四、模板参数的声明(…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
深度学习在微纳光子学中的应用
深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...
[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...
LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...
