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、自定义函数对象 四、模板参数的声明(…...
UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...
Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...
iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...
UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
Linux离线(zip方式)安装docker
目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1:修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本:CentOS 7 64位 内核版本:3.10.0 相关命令: uname -rcat /etc/os-rele…...
