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 状态,即运行成功。
真机测试
真机测试的代码编辑在 src/androidTest
目录中,因为程序运行时,IDE 会单独打包一个 ‘<application_id>.test’ 的 apk,安装到连接的真实设备,然后运行测试代码。
这里在测试时还遇到一个有意思的问题,在相同的项目中,创建一个新的 app module,并编辑相同的测试代码,运行结果获取的 ANDROID_ID
值是与另一个app module 执行的结果是不同的——由于文章长度不想过长,这个下一篇文章说明。
首先在 app module 的 build.gradle
的 dependencies
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 安装,最后查看测试代码执行结果。
到这里,通过 robolectric 模拟 Android 环境下测试的获取 ANDROID_ID
和真机测试的两种测试场景都可以了。