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 的测试代码。

kotlin 复制代码
@RunWith(AndroidJUnit4::class)
class ToolsAndroidTest {

    companion object {
        const val SDK_33_ANDROID_ID = "fd8aa7fe27625e8d"  // 正常执行 app 程序读取到 ADNROID_ID
    }

    private lateinit var _appContext: Context

    @Before
    fun setup() {
        _appContext = ApplicationProvider.getApplicationContext<Context>()
    }

    @Test
    fun 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 值不同:
    • testsdk 的 applicationId "com.sanren1024.testsdk"。
    • app 的 applicationId "com.sanren1024.phone"。
  2. 实现不同:
    • testsdk 仅有测试代码,没有任何的逻辑实现,包括界面设计。
    • app 中有诸多逻辑的实现,包括自定义的 Application 实现,它是一个完整功能的 app 模块。

分别运行 testsdk 和 app 的测试代码。

  • 运行 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 执行结果是另一个值。

  1. applicationId 不同,ANDROID_ID 相同

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

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

  1. applicationId 相同,ANDROID_ID 不同

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

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

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

分析差异

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

groovy 复制代码
android {
    //...
    signingConfigs {
        //...
        'platform' {
            storeFile file('../platform.keystore')
            storePassword '123456'
            keyAlias 'platform'
            keyPassword '123456'
        }
    }
    
    buildTypes {
        //...
		debug {
            minifyEnabled false
            proguardFiles 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 为另一个签名文件。

groovy 复制代码
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 false
            proguardFiles 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

java 复制代码
// ActivityManagerUtils.java:56
public class ActivityManagerUtils {
    // ...
    /**
     * Return a hash between [0, MAX_VALUE] generated from the android ID.
     */
    @VisibleForTesting
    static 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());  // 获取当前使用用户id
            sAndroidIdHash = getUnsignedHashUnCached(
                    sInjectedAndroidId != null ? sInjectedAndroidId : androidId);
        }
        return sAndroidIdHash;
    }
	// ...
}

// Settings.java:6424
public final class Settings {
    // ...
	public static final class Secure extends NameValueTable {
        // ...
		@UnsupportedAppUsage
        public static String getStringForUser(ContentResolver resolver, String name,
                int userHandle) {
            // ...
            return sNameValueCache.getStringForUser(resolver, name, userHandle);
        }
        // ...
	}
    // ...
    private static class NameValueCache {
               @UnsupportedAppUsage
        public 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都有关系。

相关推荐
geovindu9 小时前
sql:Search-Optimized SQL Queries using mysql 9.0
android·数据库·sql·mysql·数据库开发·数据库架构
知之则吱吱11 小时前
Android 开发者验证机制迎来新变化:核心要求、实施时间与开发者应对指南
android·google
墨天梦12 小时前
D09_Retrofit接口与数据转换
android·kotlin·retrofit
电商API_1800790524713 小时前
闲鱼商品列表API接口商品详情API接口请求示例(含一份真实响应数据示例)
android·数据挖掘·网络爬虫·跨境电商·反向海淘
我命由我1234513 小时前
Android 广播 - 组件通信
android·java·开发语言·android studio·android jetpack·android-studio·android runtime
阿俊-全栈开发14 小时前
LikeShop 单商户标准版商品管理二开:分类、SKU 规格与库存联动实现
android·likeshop·likeshop开源商城
用户21816970493016 小时前
Kotlin 语法二
android
妖星17 小时前
android 源码分析surfaceFinger是如何通过设置属性启动bootanim
android
JoyMonitor17 小时前
APK mmap 内存增长定位:pagemap 与 DEX 结构联合定位方法
android