Android 如何获取有效的DeviceId

news2024/11/18 15:37:42

在这里插入图片描述

目录

  • 前言
  • 官方唯一标识符建议
    • 使用广告 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.coloradmin.cn/o/545930.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

新手建站:腾讯云轻量服务器安装宝塔镜像和使用方法

腾讯云轻量应用服务器宝塔面板怎么用&#xff1f;轻量应用服务器如何安装宝塔面板&#xff1f;在镜像中选择宝塔Linux面板腾讯云专享版&#xff0c;在轻量服务器防火墙中开启8888端口号&#xff0c;然后远程连接到轻量服务器执行宝塔面板账号密码查询命令&#xff0c;最后登录和…

Java内存模型介绍

Java作为一种面向对象的&#xff0c;跨平台语言&#xff0c;其对象、内存等一直是比较难的知识点。而且很多概念的名称看起来又那么相似&#xff0c;很多人会傻傻分不清楚。比如本文要讨论的JVM内存结构、Java内存模型和Java对象模型&#xff0c;这就是三个截然不同的概念&…

系列四、vue3 初始化项目(图形化界面方式)

一、启动UI界面 vue ui 二、创建项目 2.1、在此创建项目 2.2、创建新项目-详情配置 2.3、创建新项目-预设 2.4、创建新项目-功能 2.5、创建新项目-配置 2.6、运行项目 任务》serve》运行》启动app 2.7、首页 三、安装element-plus 3.1、步骤 ①、运行 vue ui 命令&#…

【C++初阶】想要编译器为你干活吗?来试试模板吧(模板初阶)

一.泛型编程 引入 我们之前都写过交换函数Swap&#xff0c;例如这样的&#xff1a; //交换两个整型 void Swap(int*x1, int *x2) {int tmp *x1;*x1 *x2;*x2 tmp;} 如果要交换其它的类型该怎么办呢&#xff1f; 那只能当个CV工程师了&#xff0c;然后再修修改改&#xff0c;…

java枚举enum

目录 一、概念二、声明枚举三、枚举类四、为枚举添加方法五、EnumMap 与 EnumSet 一、概念 枚举是一个被命名的整型常数的集合&#xff0c;用于声明一组带标识符的常数。枚举在曰常生活中很常见&#xff0c;例如一个人的性别只能是“男”或者“女”&#xff0c;一周的星期只能…

CAN总线通讯协议学习

s目录 CAN&#xff08;controller Area Network) 控制器局域网 CAN通讯 CAN总线的数据帧 解析 CAN&#xff08;controller Area Network) 控制器局域网 CAN总线应用最多的是汽车领域,这里的控制器在汽车领域的专业术语是ECU.(electronic control unit)电子控制单元。可以看成…

【计算机网络之HTTP篇】HTTP协议详解

目录 一、HTTP协议概念 二、HTTP 协议格式 三、HTTP请求详解 认识URL 认识HTTP方法 GET POST Host Content-Length Content-Type User-Agent (简称 UA) Referer Cookie 四、HTTP 响应详解 状态码 200 OK 404 Not Found 403 Forbidden 500 Internal Server E…

IMX6ULL裸机篇之DDR3初始化

一. DDR3L初始化简介 I.MX6U-ALPHA 开发板上带有一个 256MB/512MB 的 DDR3 内存芯片&#xff0c;16 位宽&#xff0c;型号为 NT5CC128M16JR/MT5CC256M16EP&#xff0c;nanya 公司出品的&#xff0c;分为对应 256MB 和 512MB 容量。 我自己用的开发板上 DDR3L内存芯片型号为…

【论文阅读】REPLUG: Retrieval-Augmented Black-Box Language Models

文章目录 前言REPLUGREPLUG LSR: Training the Dense RetrieverComputing Retrieval LikelihoodComputing LM likelihood 前言 原文地址&#xff1a;REPLUG: Retrieval-Augmented Black-Box Language Models 本文提出REPLUG&#xff0c;一个将语言模型视为黑盒检索增强的语言模…

45道SQL题目陆续更新

文章目录 学习视频配置环境第一天内连接 外连接第二天 学习视频 学习视频 配置环境 四张表 配置四张表的sql语句 #创建发据库 create database frogdata charsetutf8&#xff1b;use frogdata;# 学生表 Student create table Student( SId varchar(10), Sname varchar(1…

网易云音乐开发--SongDetail搭建

SongDetail静态页面搭建 我们再新建一个页面songDetail 先写结构 再写结构 然后在写样式&#xff0c;把这个图片放进去 这样就放进去了&#xff0c;这里有一个新的让元素居中的方式就是&#xff0c;子绝父相&#xff0c;然后 position: absolute;top: 0;left: 0;right: 0;bot…

MultipartFile来上传单个及多个文件代码示例

目录 一、MultipartFile上传单个文件代码示例1.1、MultipartFile上传单个文件,不包含其它参数1.2、MultipartFile上传单个文件,包含其它参数1.3、MultipartFile上传单个文件,包含其它请求实体 二、MultipartFile上传多个文件代码示例2.1、MultipartFile上传多个文件,不包含其它…

java(springboot+ssm)/python/php/nodejs/基于vue的景区门票预约管理系统

后端&#xff1a;java(springbootssm)/python/php/nodejs/ 开发运行&#xff1a;微信开发者/hbuilderx 后端:idea/eclipse/vscode/pycharm 模块划分&#xff1a;公告类型、公告信息、用户信息、用户咨询、地区信息、景区信息、景区开放、景区预约、统计信息 本技术是Java平台的…

国考省考行测:年均增长率,等速率增长率问题

国考省考行测&#xff1a;年均增长率&#xff0c;平均增长率 2022找工作是学历、能力和运气的超强结合体! 公务员特招重点就是专业技能&#xff0c;附带行测和申论&#xff0c;而常规国考省考最重要的还是申论和行测&#xff0c;所以大家认真准备吧&#xff0c;我讲一起屡屡申…

异常和中断

异常和中断机制 ​ 现代计算机中都配有完善的异常和中断处理系统&#xff0c;CPU的数据通路中有相应的异常检测和响应逻辑&#xff0c;外设接口中有相应的中断请求和控制逻辑&#xff0c;操作系统中有相应的中断服务程序。 异常和中断的基本概念 异常&#xff08;内中断&#…

数据结构之树,实现堆的增删改查接口及堆的应用

目录 一、树 1.树的概念及结构 1.1树的概念 1.2树的相关概念 1.3树的表示 2.二叉树的概念及结构 2.1二叉树的概念 2.2特殊的二叉树 2.3二叉树的性质 2.4二叉树的存储结构 3.二叉树的顺序结构及实现 3.1二叉树的顺序结构 3.2堆的概念及结构 二、堆的实现 0.定义堆…

神经网络:Zero2Hero 2 - MLP、Embedding

Zero → \to → Hero : 2 接上篇&#xff0c;Zero → \to → Hero : 1&#xff0c;进一步的扩展模型&#xff1a; 增加输入字符序列的长度&#xff0c;通过多个字符预测下一个字符的概率分布增加模型的深度&#xff0c;通过多层的MLP来学习和预测字符的生成概率增加嵌入层&…

LeetCode高频算法刷题记录5

文章目录 1. 最长递增子序列【中等】1.1 题目描述1.2 解题思路1.3 代码实现 2. 接雨水【困难】2.1 题目描述2.2 解题思路2.3 代码实现 3. 二叉树中的最大路径和【困难】3.1 题目描述3.2 解题思路3.3 代码实现 4. 二叉树的中序遍历【简单】4.1 题目描述4.2 解题思路4.3 代码实现…

HTB靶机014-Sunday-WP

Sunday 靶机IP&#xff1a;10.10.10.76 PortScan Nmap 快速扫描&#xff1a; ┌──(xavier㉿kali)-[~] └─$ sudo nmap -sSV -T4 -F 10.10.10.76 Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-06 00:10 CST Nmap scan report for 10.10.10.76 Host is …

2023年广东省中职网络安全Web渗透测试解析(超详细)

一、竞赛时间 180分钟 共计3小时 二、竞赛阶段 1.访问地址http://靶机IP/task1,分析页面内容,获取flag值,Flag格式为flag{xxx}; 2.访问地址http://靶机IP/task2,访问登录页面。用户user01的密码为1-1000以内的数,获取用户user01的密码,将密码作为Flag进行提交,Flag格式…