安卓10:API 29上IMEI不再可用。寻找替代方案。

31

我们客户端的应用主要功能在于追踪他们客户设备,他们提供的产品绑定于特定手机(而非其所有者)。使用设备IMEI号码实现此功能曾经是可能的,但随着Android 10中的隐私更改,这变得不可行了。(https://developer.android.com/about/versions/10/privacy/changes)。

Android有一份关于在特定用户案例中使用哪种标识符的文档,但没有任何一种标识符适用于我们的情况,因为我们需要它是唯一的、恒定的,并且绑定于设备(或至少难以更改)。 https://developer.android.com/training/articles/user-data-ids。 我认为Android ID是一个可能的解决方案,或者使用MAC地址,尽管它们并不是100%可靠。

有什么想法?建议?经验?目前任何东西都可能成为一个选项。


你采用了什么解决方案来解决这个问题?你能帮我一下吗? - Ravindra Kushwaha
我也在这方面苦苦挣扎。 - falkon21
5个回答

25

我建议您阅读Google最佳实践的官方博客,以了解与您的规范相匹配的用例:https://developer.android.com/training/articles/user-data-ids.html

对我来说,我遇到了关于Android标识符唯一性的同样问题,我发现唯一的解决方法是使用MediaDrm API(https://android.googlesource.com/platform/frameworks/base/+/android-cts-4.4_r1/media/java/android/media/MediaDrm.java#539),其中包含一个唯一的设备ID,即使在出厂重置后也可以保持,并且不需要在您的清单文件中添加任何其他权限。

下面是一些代码示例,演示如何在Android 10上检索唯一标识符:

import android.media.MediaDrm
import java.security.MessageDigest
import java.util.*

object UniqueDeviceID {

    /**
     * UUID for the Widevine DRM scheme.
     * <p>
     * Widevine is supported on Android devices running Android 4.3 (API Level 18) and up.
     */
    fun getUniqueId(): String? {

        val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L)
        var wvDrm: MediaDrm? = null
        try {
            wvDrm = MediaDrm(WIDEVINE_UUID)
            val widevineId = wvDrm.getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID)
            val md = MessageDigest.getInstance("SHA-256")
            md.update(widevineId)
            return  md.digest().toHexString()
        } catch (e: Exception) {
            //WIDEVINE is not available
            return null
        } finally {
            if (AndroidPlatformUtils.isAndroidTargetPieAndHigher()) {
                wvDrm?.close()
            } else {
                wvDrm?.release()
            }
        }
    }


    fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }
}

你在多个设备上测试过吗? - SRB Bans
是的,我在我的测试设备上测试过了:三星Note 5,Pixel 3。 - Sofien Rahmouni
好的..太棒了.. :) - SRB Bans
1
它在恢复出厂设置后发生了变化。 - Vasu
@firetrap 你尝试过如何提取其他UUID,比如COMMON_PSSH_UUID吗? - Momin Khan
显示剩余3条评论

19

对于对Sofien的解决方案感兴趣的Java用户,我已经:

  1. 将Sofien的代码转换为Java并进行了进一步的简化;
  2. 在Android 10(API 29)、Android 11(API 30)和之前的版本上进行了广泛的测试。

1. 代码和讨论

@Nullable
String getUniqueID() {
   UUID wideVineUuid = new UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L);
   try {
      MediaDrm wvDrm = new MediaDrm(wideVineUuid);
      byte[] wideVineId = wvDrm.getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID);
      return Arrays.toString(wideVineId);
   } catch (Exception e) {
      // Inspect exception
      return null;
   }
   // Close resources with close() or release() depending on platform API
   // Use ARM on Android P platform or higher, where MediaDrm has the close() method
}

就Sofien的代码而言,我做了两个主要的改变。

  • 我没有使用MessageDigest,这使得代码更简单。此外,MessageDigest.update()方法将SHA-256哈希函数应用于其参数,这会极低概率地导致UUID丢失唯一性。不对UUID进行哈希的唯一缺点是您没有固定长度的UUID,在我的应用程序中我不关心这一点。
  • 我使用Arrays.toString代替Kotlin函数toHexString(Java中没有单行等价物)。这个选择是安全的,因为(A)它不抛出Exception并且(B)它保留wideVineIdString表示之间的一对一对应关系。如果您喜欢坚持十六进制转换,则Apache Commons Codec库提供了一行解决方案

当然,这些更改将导致不同的UUID,其他选择也是可能的。注意,使用Arrays.toString生成的UUID采用以下格式。

[92, -72, 76, -100, 26, -86, 121, -57, 81, -83, -81, -26, -26, 3, -49, 97, -24, -86, 17, -106, 25, 102, 55, 37, 47, -5, 33, -78, 34, 121, -58, 109]

如果您不想在UUID中包含特殊字符,可以使用 String.replaceAll() 函数将其移除。

2. 测试

我已经测试了UUID的持久性

  • 重新安装后
  • 重新安装并重启后

测试设备/操作系统组合如下:

  • Google Pixel 4A / API 30
  • Samsung Galaxy S10 / API 29
  • Samsung Galaxy S9 / API 29
  • Huawei Nexus 6P / API 27(也测试了出厂设置)
  • LG V20 / API 27(也测试了出厂设置)
  • Asus ZenFone 2 / API 23
  • Samsung Galaxy J5 / API 23
  • LG Nexus 5 / API 23
  • LG K4 / API 22
  • Samsung Galaxy J3 / API 22
  • Samsung Galaxy S4 / API 21

在所有的测试中,targetSdkVersion 都是30。欢迎进行更多测试(尤其是在API 29和30上的测试)。


3
您是否在出厂重置后进行过测试?测试结果是否相同? - Iulia Barbu
2
@IuliaBarbu 谢谢您的评论。我现在已经在列出的设备之一上测试了恢复出厂设置(请参见更新的答案)。由于这些设备属于实际用户,我无法在所有设备上测试恢复出厂设置。 - MathMax
你可以通过将字节数组转换为Base64字符串(如Base64.encodeToString(WideVineId,Base64.Default))来缩短值。 - user1154390
WIDEVINE_UUID是否能在恢复出厂设置后保留?有关此事的任何文档资料吗?(我已经搜索过,但找不到相关资源) - Pascal
我的设备在恢复出厂设置后无法存活...只有我一个人吗? - Pascal
显示剩余3条评论

3
  1. 在设备首次启动时,会生成一个随机值并存储。这个值可以通过Settings.Secure.ANDROID_ID访问。它是一个64位的数字,应该在设备的生命周期内保持不变。ANDROID_ID似乎是唯一设备标识符的不错选择,因为它适用于智能手机和平板电脑。要检索该值,您可以使用以下代码:
String androidId = Settings.Secure.getString(getContentResolver(),
                                             Settings.Secure.ANDROID_ID);

然而,如果设备进行恢复出厂设置,那么这个值可能会改变。此外,有一个知名厂家的受欢迎手机存在已知的漏洞,使得每个实例都具有相同的ANDROID_ID。显然,这个解决方案并不是100%可靠。
使用UUID。由于大多数应用程序的要求是识别特定安装而不是物理设备,获取用户唯一标识符的好方法是使用UUID类。以下解决方案由Google的Reto MeierGoogle I/O演示中提出。
SharedPreferences sharedPrefs = context.getSharedPreferences(
PREF_UNIQUE_ID, Context.MODE_PRIVATE);
uniqueID = sharedPrefs.getString(PREF_UNIQUE_ID, null);

4
关于方案2:这正是Android 10不再适用的原因,因为它现在需要READ_PRIVILEGED_PHONE_STATE权限,而第三方应用程序无法获取该权限。 - Michael
2
同样,现在读取序列号也需要READ_PRIVILEGED_PHONE_STATE权限。 - Michael
4
这个问题特别涉及到Android 10,因此删除建议2和3是有意义的,因为它们已经不再可用。 - Michael
1
非常感谢,我得到了相同的结果 :( @KiranManiya - Mahmoud Heretani
@jerinho 感谢您指出。我已经很久没有发布这个答案了。 - Kiran Maniya
显示剩余4条评论

0

MEDIADRM API是一个可以使用的API。

//来自Exo player

val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L)
    val id = MediaDrm(WIDEVINE_UUID)
        .getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID)
    var encodedString: String = Base64.encodeToString(id,Base64.DEFAULT)
    Log.i("Uniqueid","Uniqueid"+encodedString)

-4

我已经在诺基亚手机上测试过了,“当我将手机恢复出厂设置时,标识符会发生变化”。你有在恢复出厂设置后测试过吗?


你的回答毫无意义。请使用标点符号并保持一定的语法结构。写出连像样的一行都不合格的答案是令人震惊的。 - Anklon
我已经修改了答案,我的意思是如果您对手机进行出厂设置,则标识符号会更改。您测试过出厂设置吗? - Yaser Yousef
请使用适当的“标点符号”回答并评论。 - Anklon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接