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都有关系。

相关推荐
白雪落青衣几秒前
buuoj course 1详细解析
android
恋猫de小郭17 分钟前
Android 发布全新性能分析器,实用性和性能大升级
android·前端·flutter
Kapaseker28 分钟前
为什么 Java 的数组需要 new 出来
android·java·kotlin
黄林晴43 分钟前
颠覆开发!Google AI Studio 一句话生成原生 Android App
android·google io
恋猫de小郭1 小时前
Flutter 3.44 发布啦,超级大版本更新!!!
android·flutter·ios
zb200641201 小时前
Laravel10.x重磅升级:新特性全解析
android
2601_957418801 小时前
深入解析Android相机有线连接:PTP与MTP协议栈实现原理与实践
android·数码相机·智能手机
努力努力再努力wz1 小时前
【QT入门系列】QWidget 六大常用属性详解:windowOpacity、cursor、font、focus、toolTip 与 styleSheet
android·开发语言·数据结构·c++·qt·mysql·算法
撩得Android一次心动1 小时前
C语言基础笔记3【个人用】
android·c语言·开发语言·笔记
小离a_a2 小时前
uniapp小程序封装圆环显示比例数据
android·小程序·uni-app