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 的測試代碼。
@RunWith(AndroidJUnit4::class)
class ToolsAndroidTest {companion object {const val SDK_33_ANDROID_ID = "fd8aa7fe27625e8d" // 正常執行 app 程序讀取到 ADNROID_ID}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(SDK_33_ANDROID_ID, deviceId)}
}
新建的 app module 命名 testsdk,原有的 app module 依然叫 app。
兩個 module 的區別:
applicationId
值不同:- testsdk 的
applicationId "com.sanren1024.testsdk"
。 - app 的
applicationId "com.sanren1024.phone"
。
- testsdk 的
- 實現不同:
- 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 執行結果是另一個值。
2. applicationId
不同,ANDROID_ID
相同
再新建一個 app module,命名 testapp,與 testsdk 一樣,只包含測試代碼。testapp 的 applicationId
值為 "com.sanren1024.phone"
,這個值與 app 相同。
比較 testsdk 和 testapp 的測試代碼結果,testsdk 執行結果是 6fafd019bf9cd426
, testapp 執行結果是 6fafd019bf9cd426
。看出來了,兩者的結果值是相同的。
3. 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 的簽名。
android {//...signingConfigs {//...'platform' {storeFile file('../platform.keystore')storePassword '123456'keyAlias 'platform'keyPassword '123456'}}buildTypes {//...debug {minifyEnabled falseproguardFiles 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
為另一個簽名文件。
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 falseproguardFiles 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
// ActivityManagerUtils.java:56
public class ActivityManagerUtils {// .../*** Return a hash between [0, MAX_VALUE] generated from the android ID.*/@VisibleForTestingstatic 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()); // 獲取當前使用用戶idsAndroidIdHash = getUnsignedHashUnCached(sInjectedAndroidId != null ? sInjectedAndroidId : androidId);}return sAndroidIdHash;}// ...
}// Settings.java:6424
public final class Settings {// ...public static final class Secure extends NameValueTable {// ...@UnsupportedAppUsagepublic static String getStringForUser(ContentResolver resolver, String name,int userHandle) {// ...return sNameValueCache.getStringForUser(resolver, name, userHandle);}// ...}// ...private static class NameValueCache {@UnsupportedAppUsagepublic 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都有關系。