当前位置: 首页 > article >正文

Android Test3 获取的ANDROID_ID值不同

Android Test3 获取的ANDROID_ID值不同

这篇文章来说明上一篇文章中说到的一个现象:在同一个项目中,创建不同的 app module,运行同一段测试代码,获取到的 ANDROID_ID 的值不同。

我也是第一次认真研究这个现象,这个还涉及到了 ANDROID_ID 值的系统访问的执行原理。这里一起来看下,有知道更详细细节的大佬,不吝在评论里添加。

下面几种场景。

ANDROID_ID 结果差异

下面是几种不同差异的场景。

1. applicationId 不同,ANDROID_ID 不同

在同一个项目目录下,创建两个不同的 app module,会产生不同有 applicationId 值。在新建的 app module 的 src/androidTest 目录下拷贝一份原有 app module 的测试代码。

@RunWith(AndroidJUnit4::class)
class ToolsAndroidTest {companion object {const val SDK_33_ANDROID_ID = "fd8aa7fe27625e8d"  // 正常执行 app 程序读取到 ADNROID_ID}private lateinit var _appContext: Context@Beforefun setup() {_appContext = ApplicationProvider.getApplicationContext<Context>()}@Testfun test_getDeviceId_shouldReturnDeviceId() {val deviceId = Tools.getDeviceId(_appContext)Assert.assertNotEquals(deviceId, "", "Unexpected device id.")Assert.assertEquals(SDK_33_ANDROID_ID, deviceId)}
}

新建的 app module 命名 testsdk,原有的 app module 依然叫 app

两个 module 的区别:

  1. applicationId 值不同:
    • testsdkapplicationId "com.sanren1024.testsdk"
    • appapplicationId "com.sanren1024.phone"
  2. 实现不同:
    • testsdk 仅有测试代码,没有任何的逻辑实现,包括界面设计。
    • app 中有诸多逻辑的实现,包括自定义的 Application 实现,它是一个完整功能的 app 模块。

分别运行 testsdkapp 的测试代码。

  • 运行 testsdk 的测试代码,获取的 deviceId 值是 6fafd019bf9cd426,详细信息如下。

    org.junit.ComparisonFailure: expected:<[fd8aa77327a25e8d]> but was:<[6fafd019bf9cd426]>
    at org.junit.Assert.assertEquals(Assert.java:117)
    at org.junit.Assert.assertEquals(Assert.java:146)
    ...
    
  • 运行 app 的测试代码,获取的 deviceId 值是 fd8aa77327a25e8d.

两者的测试获取的值不同。预期的 testsdk 结果值应该与 app 的执行值一致,而实际 testsdk 执行结果是另一个值。


2. applicationId 不同,ANDROID_ID 相同

再新建一个 app module,命名 testapp,与 testsdk 一样,只包含测试代码。testappapplicationId 值为 "com.sanren1024.phone",这个值与 app 相同。

比较 testsdktestapp 的测试代码结果,testsdk 执行结果是 6fafd019bf9cd426testapp 执行结果是 6fafd019bf9cd426。看出来了,两者的结果值是相同的。


3. applicationId 相同,ANDROID_ID 不同

分别运行 apptestapp,这两个 app module 的 applicationId 相同,查看运行结果。

app 测试代码执行结果 fd8aa77327a25e8dtestapp 测试代码执行结果 6fafd019bf9cd426。两者也不同。

上面三种情况下,导致了我对 ANDROID_ID 值变化的疑惑。


分析差异

上面的几个场景中,只有 app 包含了完整的功能实现,另外两个 app module 都只保含了测试代码。所以重点是排查 app 内相关配置和可能的实现。经过仔细查看后,发现的差异是在 appbuild.gradlebuildType block 中,配置了 debug 这个 build variant 的签名。

android {//...signingConfigs {//...'platform' {storeFile file('../platform.keystore')storePassword '123456'keyAlias 'platform'keyPassword '123456'}}buildTypes {//...debug {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules-sqaDebug.pro'signingConfig signingConfigs.'platform'versionNameSuffix ".0"debuggable true}}
}

找到了这个差异,于是将 signingConfig 设置项注释掉,并再次执行测试代码。于是惊喜出现了,得到下面的异常信息。

org.junit.ComparisonFailure: expected:<[fd8aa77327a25e8d]> but was:<[6fafd019bf9cd426]>
at org.junit.Assert.assertEquals(Assert.java:117)
at org.junit.Assert.assertEquals(Assert.java:146)
...

与 场景1 中贴出的错误信息一致。那就猜想一个事实,app 读取的 ANDROID_ID 值与签名有关联。

为了验证猜想,修改 debug block 的 signingConfig 为另一个签名文件。

android {//...signingConfigs {'debug_alter' {storeFile file('../debug_alter.jks')storePassword '123456'keyAlias 'debug_alter'keyPassword '123456'}'platform' {storeFile file('../platform.keystore')storePassword '123456'keyAlias 'platform'keyPassword '123456'}}buildTypes {//...debug {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules-sqaDebug.pro'signingConfig signingConfigs.'debug_alter'versionNameSuffix ".0"debuggable true}}
}

执行测试代码后,结果错误信息如下。

org.junit.ComparisonFailure: expected:<[fd8aa77327a25e8d]> but was:<[3e1b82e6762993df]>
at org.junit.Assert.assertEquals(Assert.java:117)
at org.junit.Assert.assertEquals(Assert.java:146)
...

从上面这段输出结果看出,这次执行后的 ADNROID_ID 结果是 3e1b82e6762993df,与开始执行结果不同。这也佐证了上面的猜想。

随机去查看源码:

文件:

frameworks/base/services/core/java/com/android/server/am/ActivityManagerUtils.java:61

frameworks/base/core/java/android/provider/Settings.java

// ActivityManagerUtils.java:56
public class ActivityManagerUtils {// .../*** Return a hash between [0, MAX_VALUE] generated from the android ID.*/@VisibleForTestingstatic int getAndroidIdHash() {// No synchronization is required. Double-initialization is fine here.if (sAndroidIdHash == null) {final ContentResolver resolver = ActivityThread.currentApplication().getContentResolver();// 读取 ANDROID_ID 最直接的调用位置final String androidId = Settings.Secure.getStringForUser(resolver,Settings.Secure.ANDROID_ID,resolver.getUserId());  // 获取当前使用用户idsAndroidIdHash = getUnsignedHashUnCached(sInjectedAndroidId != null ? sInjectedAndroidId : androidId);}return sAndroidIdHash;}// ...
}// Settings.java:6424
public final class Settings {// ...public static final class Secure extends NameValueTable {// ...@UnsupportedAppUsagepublic static String getStringForUser(ContentResolver resolver, String name,int userHandle) {// ...return sNameValueCache.getStringForUser(resolver, name, userHandle);}// ...}// ...private static class NameValueCache {@UnsupportedAppUsagepublic String getStringForUser(ContentResolver cr, String name, final int userHandle) {// ......}}
}

从上面源码调用流程上,它最终调用到 NameValueCache#getStringForUser(ContentResolver, String, final int) 方法,最后的值与系统的 user id 和 当前 app 的信息(签名,ApplicationInfo 等)都有关系。

结论:不同 app 的 apk 在同一台设备上读取到的 ADNROID_ID 基本肯定是不同的。同一个 app 的不同签名的 apk 在同一设备上基本是不同的。(基本不同是因为还与 Android 的系统版本有关系)


结论

造成文章开头说的 ANDROID_ID 值不同的原因是 Android 系统的设计导致的。在版本高些的 Android 系统上,ANDROID_ID 的值与系统版本,应用签名,用户ID都有关系。

相关文章:

Android Test3 获取的ANDROID_ID值不同

Android Test3 获取的ANDROID_ID值不同 这篇文章来说明上一篇文章中说到的一个现象&#xff1a;在同一个项目中&#xff0c;创建不同的 app module&#xff0c;运行同一段测试代码&#xff0c;获取到的 ANDROID_ID 的值不同。 我也是第一次认真研究这个现象&#xff0c;这个还…...

[蓝桥杯 2024 国 B] 立定跳远

问题描述 在运动会上&#xff0c;小明从数轴的原点开始向正方向立定跳远。项目设置了 n 个检查点 a1,a2,...,an且 ai≥ai−1>0。小明必须先后跳跃到每个检查点上且只能跳跃到检查点上。同时&#xff0c;小明可以自行再增加 m 个检查点让自己跳得更轻松。在运动会前&#xf…...

内容力重塑品牌增长:开源AI大模型驱动下的智能名片与S2B2C商城赋能抖音生态种草范式

摘要&#xff1a;内容力已成为抖音生态中品牌差异化竞争的核心能力&#xff0c;通过有价值、强共鸣的内容实现产品"种草"与转化闭环。本文基于"开源AI大模型AI智能名片S2B2C商城小程序源码"技术架构&#xff0c;提出"技术赋能内容"的新型种草范式…...

手机号在网状态查询接口如何用PHP实现调用?

一、什么是手机号在网状态查询接口 通过精准探测手机号的状态&#xff0c;帮助平台减少此类问题的发生&#xff0c;提供更个性化的服务或进行地域性营销 二、应用场景 1. 金融风控 通过运营商在网态查询接口&#xff0c;金融机构可以核验贷款申请人的手机状态&#xff0c;拦…...

【Java微服务组件】分布式协调P4-一文打通Redisson:从API实战到分布式锁核心源码剖析

欢迎来到啾啾的博客&#x1f431;。 记录学习点滴。分享工作思考和实用技巧&#xff0c;偶尔也分享一些杂谈&#x1f4ac;。 有很多很多不足的地方&#xff0c;欢迎评论交流&#xff0c;感谢您的阅读和评论&#x1f604;。 目录 引言Redisson基本信息Redisson网站 Redisson应用…...

一个简单的德劳内三角剖分实现

德劳内&#xff08;Delaunay&#xff09;三角剖分是一种经典的将点集进行三角网格化预处理的手段&#xff0c;在NavMesh、随机地牢生成等场景下都有应用。 具体内容百度一大堆&#xff0c;就不介绍了。 比较知名的算法是Bowyer-Watson算法&#xff0c;也就是逐点插入法。 下雨闲…...

Python入门手册:异常处理

在编程过程中&#xff0c;异常处理是一个非常重要的环节。它可以帮助我们处理程序运行时可能出现的错误和异常情况&#xff0c;确保程序的稳定性和可靠性。Python提供了强大的异常处理机制&#xff0c;使得我们能够优雅地处理各种异常情况。今天&#xff0c;就让我们一起深入学…...

C#子线程更新主线程UI及委托回调使用示例

1.声明线程方法 2.线程中传入对象 3.声明委托与使用 声明委托对象 委托作为参数传入方法 4.在线程中传入委托 5.调用传入的委托...

使用VuePress2.X构建个人知识博客,并且用个人域名部署到GitHub Pages中

使用VuePress2.X构建个人知识博客&#xff0c;并且用个人域名部署到GitHub Pages中 什么是VuePress VuePress 是一个以 Markdown 为中心的静态网站生成器。你可以使用 Markdown 来书写内容&#xff08;如文档、博客等&#xff09;&#xff0c;然后 VuePress 会帮助你生成一个…...

手写Promise.all

前言 之前在看远方os大佬直播的时候看到有让手写的Promise.all的问题&#xff0c;然后心血来潮自己准备手写一个 开始 首先&#xff0c;我们需要明确原本js提供的Promise.all的特性 Promise.all返回的是一个Promise如果传入的数据中有一个reject即整个all返回的就是reject&…...

调试器基本原理

调试器基本原理 前言 调试器(debugger)&#xff0c;是一种用于控制其他程序执行流程、监控和修改其他程序状态的软件工具。 调试器通过实时分析程序的执行状态&#xff0c;协助开发者定位代码错误、了解程序工作原理、性能调优及逆向工程等。 1. 调试器核心功能 1.1 控制程…...

2025年6月|注意力机制|面向精度与推理速度提升的YOLOv8模型结构优化研究:融合ACmix的自研改进方案

版本&#xff1a; 8.3.143(Ultralytics YOLOv8框架) ACmix模块原理 在目标检测任务中&#xff0c;小目标&#xff08;如裂缝、瑕疵、零件边缘等&#xff09;由于其尺寸较小、纹理信息稀疏&#xff0c;通常更容易受到图像中复杂背景或噪声的干扰&#xff0c;从而导致漏检或误检…...

JAVA开发代码小工具集合

目录 前言编号生成工具EasyExcel 工具断言工具HTTP 工具字符串 工具验证码生成工具Excel 工具Class 工具Enum 工具分页工具断言工具2IP 地址工具Map 工具 前言 这些工具都是日常开发中能用到的&#xff0c;前后端都有&#xff0c;觉得好用就拿过来了… 编号生成工具 import j…...

利用qcustomplot绘制曲线图

本文详细介绍了qcustomplot绘制曲线图的流程&#xff0c;一段代码一段代码运行看效果。通过阅读本文&#xff0c;读者可以了解到每一项怎么用代码进行配置&#xff0c;进而实现自己想要的图表效果。&#xff08;本文只针对曲线图&#xff09; 1 最简单的图形&#xff08;入门&…...

【基础算法】枚举(普通枚举、二进制枚举)

文章目录 一、普通枚举1. 铺地毯(1) 解题思路(2) 代码实现 2. 回文日期(1) 解题思路思路一&#xff1a;暴力枚举思路二&#xff1a;枚举年份思路三&#xff1a;枚举月日 (2) 代码实现 3. 扫雷(2) 解题思路(2) 代码实现 二、二进制枚举1. 子集(1) 解题思路(2) 代码实现 2. 费解的…...

智能对联网页小程序的仓颉之旅

#传统楹联遇上AI智能体&#xff1a;我的Cangjie Magic开发纪实 引言&#xff1a;一场跨越千年的数字对话 "云对雨&#xff0c;雪对风&#xff0c;晚照对晴空"。昨天晚上星空璀璨&#xff0c;当我用仓颉语言写下第一个智能对联网页小程序的Agent DSL代码时&#xff0…...

Go字符串切片操作详解:str1[:index]

在Go语言中&#xff0c;return str1[:index] 是一个​​字符串切片操作​​&#xff0c;它截取字符串的一部分。让我们深入解析这个操作的含义和原理&#xff1a; 基本语法和含义 str1&#xff1a;原始字符串[:index]&#xff1a;切片操作符str1[:index]&#xff1a; ​​起始…...

JavaScript 本地存储 (localStorage) 完全指南

文章目录 JavaScript 本地存储 (localStorage) 完全指南 &#x1f510;一、什么是 localStorage&#xff1f;&#x1f4a1;二、如何使用 localStorage&#xff1f;&#x1f527;1. 存储数据2. 读取数据3. 删除数据4. 清空所有数据 三、存储对象和数组的技巧 &#x1f3a8;1. 存…...

从golang的sync.pool到linux的slab分配器

最近学习golang的时候&#xff0c;看到golang并发编程中有一个sync.pool&#xff0c;即对象池&#xff0c;猛地一看这不跟linux的slab分配器类似嘛&#xff0c;赶紧学习记录下 这里先总结下设计sync.pool和slab的目的 sync.pool 为了缓解特定类型的对象频繁创建和销毁&#x…...

Python分形几何可视化—— 复数迭代、L系统与生物分形模拟

Python分形几何可视化—— 复数迭代、L系统与生物分形模拟 本节将深入探索分形几何的奇妙世界&#xff0c;实现Mandelbrot集生成器和L系统分形树工具&#xff0c;并通过肺部血管分形案例展示分形在医学领域的应用。我们将使用Python的NumPy进行高效计算&#xff0c;结合Matplo…...

【超详细】英伟达Jetson Orin NX-YOLOv8配置与TensorRT测试

文章主要内容如下&#xff1a; 1、基础运行环境配置 2、Torch-GPU安装 3、ultralytics环境配置 4、Onnx及TensorRT导出详解 5、YOLOv8推理耗时分析 基础库版本&#xff1a;jetpack5.1.3, torch-gpu2.1.0, torchvision0.16.0, ultralytics8.3.146 设备的软件开发包基础信息 需…...

Go语言学习-->项目中引用第三方库方式

Go语言学习–&#xff1e;项目中引用第三方库方式 1 执行 go mod tidy 分析引入的依赖有没有正常放在go.mod里面 找到依赖的包会自动下载到本地 并添加在go.mod里面 执行结果&#xff1a; 2 执行go get XXXX&#xff08;库的名字&#xff09;...

Vue Fragment vs React Fragment

文章目录 前言&#x1f9e9; 一、概念对比&#xff1a;Vue Fragment vs React Fragment&#x1f4e6; 二、使用示例对比✅ Vue 3 中使用 Fragment✅ React 中使用 Fragment &#x1f50d; 三、差异解析1. **使用方式**2. **传递属性&#xff08;如 key&#xff09;**3. **插槽系…...

【LRU】 (最近最少使用)

LRU (最近最少使用) 文章目录 LRU (最近最少使用)一、LRU是什么&#xff1f;二、实现1.常规算法2.双栈更替总结 一、LRU是什么&#xff1f; LRU&#xff08;Least Recently Used&#xff09;是一种常见的缓存淘汰策略&#xff0c;核心思想是 “淘汰最长时间未被使用的缓存数据…...

每日Prompt:云朵猫

提示词 仰视&#xff0c;城镇的天空&#xff0c;一片形似猫咪的云朵&#xff0c;用黑色的简笔画&#xff0c;勾勒出猫咪的形状&#xff0c;可爱&#xff0c;俏皮&#xff0c;极简...

AI浪潮下的IT行业:威胁、转变与共生之道

目录 前言1 AI在IT行业的具体应用场景1.1 软件开发中的AI助手1.2 运维与监控的智能化1.3 测试自动化与质量保障1.4 安全防护中的智能威胁识别 2 AI对IT从业者的实际影响2.1 工作内容的结构性变化2.2 技能结构的再平衡 3 IT从业者不可替代的能力与价值3.1 复杂系统的架构与抽象能…...

基于功能基团的3D分子生成扩散模型 - D3FG 评测

D3FG 是一个在口袋中基于功能团的3D分子生成扩散模型。与通常分子生成模型直接生成分子坐标和原子类型不同&#xff0c;D3FG 将分子分解为两类组成部分&#xff1a;官能团和连接体&#xff0c;然后使用扩散生成模型学习这些组成部分的类型和几何分布。 一、背景介绍 D3FG 来源…...

Python Cookbook-7.12 在 SQLite 中储存 BLOB

任务 想将 BLOB 存入一个 SQLite 数据库, 解决方案 Python的 PySQLite 扩展提供了 sqlite.encode 函数,它可帮助你在 SOLite 数据库中插入二进制串。可以基于这个函数编写一个小巧的适配器类: import sqlite,cPickle class Blob(object):自动转换二进制串def __init__(self…...

蓝耘服务器与DeepSeek的结合:引领智能化时代的新突破

&#x1f31f; 嗨&#xff0c;我是Lethehong&#xff01;&#x1f31f; &#x1f30d; 立志在坚不欲说&#xff0c;成功在久不在速&#x1f30d; &#x1f680; 欢迎关注&#xff1a;&#x1f44d;点赞⬆️留言收藏&#x1f680; &#x1f340;欢迎使用&#xff1a;小智初学…...

无人机光纤FC接口模块技术分析

运行方式 1. 信号转换&#xff1a;在遥控器端&#xff0c;模块接收来自遥控器主控板的电信号。 2.电光转换&#xff1a;模块内部的激光发射器将电信号转换成特定波长的光信号。 3.光纤传输&#xff1a;光信号通过光纤跳线传输。光纤利用全内反射原理将光信号约束在纤芯内进行…...