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

  1. applicationId 不同,ANDROID_ID 相同

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

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

  1. applicationId 相同,ANDROID_ID 不同

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

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

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

分析差异

上面的几个场景中,只有 app 包含了完整的功能实现,另外两个 app module 都只保含了测试代码。所以重点是排查 app 内相关配置和可能的实现。经过仔细查看后,发现的差异是在 appbuild.gradlebuildType 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都有关系。

相关推荐
千里马学框架18 小时前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台19 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone19 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc20 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo21 小时前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077001 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼1 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone1 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen1 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone1 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui