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

Android Test2 获取系统android id

Android Test2 获取系统 android id

这篇文章针对一个常用的功能做一个测试。

在项目中,时常会遇到的一个需求就是:一台设备的唯一标识值。然后,在网络请求中将这个识别值传送到后端服务器,用作后端数据查询的条件。Android 设备上最常用的一个标识值就是 android id,其他也有可作为设备唯一标识值的一些参数值。

Android 标识值

列举下 Android 系统上可以用来作为唯一标识的一些值和可用性的场景。

硬件上

  • IMEI(国际移动设备标识码)—— 这个值的获取需要在设备上有 SIM 卡,否则获取到的就是空值(null)。在 Android 10+ 以上系统,需要系统权限,作为第三方应用无法获取。
  • MEID(移动设备识别码)—— 这个值的获取是需要在 CDMA 网络环境下。这个值的获取在 Android 8+ 以上系统,也收到 SIM 卡和系统权限限制。
  • 序列号(Serial Number)—— 这个值的设置与厂商有关,也有厂商不设置这个值,那么返回的就是空。

系统上

  • ANDROID_ID —— 设备首次开机时产生,在设备重置后会重新生成。
  • MAC(物理地址)—— 一般是获取 WIFI 的 MAC 地址,但这个值在高版本的 Android 系统上,已经是动态生成。不是很合适作为一个设备的唯一标识值。如果 WIFI 未起作用,直接尝试获取 MAC 地址,获取的值可能总是 02:00:00:00:00:00

软件生成

  • UUID(通用唯一识别码)—— 首次启动 app/sdk 时生成,并做存储。应用写在后会失效,需要做好持久化设计。
  • OAID(开放匿名设备标识符)—— 这个一般在国内用于广告追踪,可以被重置。

在我的项目中,使用了 ANDROID_ID 作为识别符,因为获取它无需申请权限,且一般情况下,设备不会被轻易重置。


获取 ANDROID_ID

项目运行的 Android 系统版本要求是 11+,因此只需读取 Settings.Secure.ANDROID_ID 值。

public final class Tools {public static String getDeviceId( @NonNull Context context ) {// Android 10+if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ) {return Settings.Secure.getString( context.getContentResolver(), Settings.Secure.ANDROID_ID );}return ""}
}

下来就是考虑如何通过测试代码获取这个值。

依据第一篇文章的介绍,通过单元测试或者仪器测试都可以尝试。

  • 可以是通过单元测试,代码写于 src/test 中并运行在纯 JVM 环境中。
  • 可以是通过仪器测试,代码写于 src/androidTest 中并运行与真机之上。

单元测试

因为涉及的目标测试方法的参数是 android.content.Context 类,属于 android frameworks 的 API,因此需要引入 robolectric(在 JVM 环境中模拟 android frameworks 的环境)。

下面就看下单元测试代码

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.R])
class ToolsTest {// android id samples: ["02edb4a7f2a32b72"]private lateinit var _context: Context@Beforefun init() {val application = RuntimeEnvironment.getApplication() // ApplicationProvider.getApplicationContext<Context>()_context = application.applicationContextval actualContentResolver = _context.contentResolver//("02edb4a7f2a32b72")Settings.Secure.putString(actualContentResolver,Settings.Secure.ANDROID_ID,"02edb4a7f2a32b72")}@Testfun test_getDeviceId_shouldReturnDeviceId() {val deviceId = Tools.getDeviceId(_context)Assert.assertEquals(deviceId,  "02edb4a7f2a32b72")}
}

上面的代码很简单,做一个基本的描述:

  • 类声明 ToolsTest 头部有标注 @RunWith(RobolectricTestRunner::class)@Config(sdk = [Build.VERSION_CODES.R]),通过这两个标注启用 robolectric 的 shadow 机制,在运行过程中将对 android frameworks API 的调用路由到 robolectric 的 API 调用。

  • RuntimeEnvironment.getApplication() 在测试环境下获取一个 Application 实例,这个方法不会去获取 app/sdk 的真实实现的实例,因为上面的测试代码是运行的 JVM 环境中,不会直接访问 android frameworks 的 API。注释的代码 ApplicationProvider.getApplicationContext<Context>() 也可以运行,这个 API 来自 androidx.test (androidx.test.core.app.ApplicationProvider)包,它的优势在于可以跨环境运行,既可以基于 JVM 环境运行,也可以在 Android 真机上运行,它也是首选的(preferable)获取 Context 的使用方法。

  • 通过 android.content.Context 获取 android.content.ContentResolver 实例,用于准备修改保存 Settings.Secure.ANDROID_ID 字段的值。

  • init() 方法的执行会在 test_getDeviceId_shouldReturnDeviceId() 方法之前运行,由于它标注了 @Before,每个 @Test 方法的执行前都会执行 @Before 方法。在这个方法中,将 Settings.Secure.ANDROID_ID 的值修改为 02edb4a7f2a32b72,也作为最终的比较目标。

  • test_getDeviceId_shouldReturnDeviceId() 方法中,调用 Tools.getDeviceId(Context) 方法获取想要的 Settings.Secure.ANDROID_ID 值,并与 @Before 方法中初始设置的值作比较。

接着运行 test_getDeviceId_shouldReturnDeviceId() 方法,最后的结果是 passed 状态,即运行成功。

unit test



真机测试

真机测试的代码编辑在 src/androidTest 目录中,因为程序运行时,IDE 会单独打包一个 ‘<application_id>.test’ 的 apk,安装到连接的真实设备,然后运行测试代码。

这里在测试时还遇到一个有意思的问题,在相同的项目中,创建一个新的 app module,并编辑相同的测试代码,运行结果获取的 ANDROID_ID 值是与另一个app module 执行的结果是不同的——由于文章长度不想过长,这个下一篇文章说明。

首先在 app module 的 build.gradledependencies block 中检查依赖项的配置。当前不作界面测试,因此这里不配置 Espresso, Rules 等库。

android {defaultConfig {// other configstestInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"}
}denpendencies {// ...testImplementation "junit:junit:4.13.2"androidTestImplementation "androidx.test.ext:junit:1.2.1"
}

针对这篇文章内的例子,这两个基本库就可以了。

接着,就是编辑测试代码。

@RunWith(AndroidJUnit4::class)
class ToolsAndroidTest {companion object {const val SDK_33_ANDROID_ID = "fd8aa7fe27625e8d"}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(deviceId, SDK_33_ANDROID_ID)}
}

最后运行测试方法,等待编译好的 ‘<application_id>.test’ apk 安装,最后查看测试代码执行结果。

Instrumentation Test

到这里,通过 robolectric 模拟 Android 环境下测试的获取 ANDROID_ID 和真机测试的两种测试场景都可以了。

http://www.xdnf.cn/news/892441.html

相关文章:

  • 8086寻址解剖图:7种武器解锁x86内存访问的基因密码
  • Simplicity studio SDK下载和安装,创建工程
  • 使用 DuckLake 和 DuckDB 构建 S3 数据湖实战指南
  • 在表单输入框按回车页面刷新的问题
  • 使用 minicom 录制串口报文并回放
  • 【YOLO 系列】基于YOLO的飞机表面缺陷智能检测系统【python源码+Pyqt5界面+数据集+训练代码】
  • 掌握YOLOv8:从视频目标检测到划定区域统计计数的实用指南
  • 图简记。。
  • 深度解析数字营销专属大模型 AdLLM 的训练思路
  • 【学习笔记】Lamba表达式[匿名函数]
  • 搜索子字符串的思路与算法分享
  • 类似东郊到家app系统源码开发
  • 《神经渲染变局:高斯泼溅能否改写NeRF规则》
  • 【强化学习】——03 Model-Free RL之基于价值的强化学习
  • hbase资源和数据权限控制
  • 经典算法:回文链表
  • 开发在线问诊APP要注意什么?互联网医院系统源码、功能、合规全详解
  • MATLAB仿真:偏振光在光纤通信中的应用研究_可复现,有问题请联系博主
  • RocketMQ基础概念的理解
  • 28. Revit API:尺寸标注(Dimension)
  • C++STL-vector的使用
  • 非隔离电源方案
  • 【信息系统项目管理师-选择真题】2025上半年(第一批)综合知识答案和详解
  • 【Python训练营打卡】day44 @浙大疏锦行
  • 【PhysUnits】15.15 变量类型(variable.rs)
  • 前端没有“秦始皇“,但可以做跨端的王[特殊字符]
  • 驭码CodeRider 2.0 产品体验 — 搭建邮件服务
  • Web前端之原生表格动态复杂合并行、Vue
  • 农田水利如何「聪明」起来?Modbus转Ethernet IP破解设备互联
  • C语言| 指针在数组中的移动