← 资料库索引 ← 社区讨论 原始链接 ↗ 🔍
社区讨论

Android 10:API 29 上 IMEI 不再可用,寻找替代方案 原文标题:Android 10: IMEI no longer available on API 29. Looking for alternatives

发表时间:(页面未标注)采集时间:2026-10-09 10:35:04来源:stackoverflow.com原文语言:en状态:完整

内容概要总结

这是 Stack Overflow 上一个得分 34 的问题讨论:提问者的客户应用严重依赖跟踪设备,其产品绑定到特定手机而非其所有者,过去可用设备 IMEI 实现,但 Android 10 的隐私变更使其不可获取。提问者需要唯一、恒定且绑定设备的标识符,正在考虑 Android ID 或 MAC 地址。回答区给出了多种方案:最高票(26 分)推荐使用 MediaDrm API 结合 Widevine UUID 获取设备唯一 ID(getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID)),声称可经受恢复出厂设置且无需额外权限,并给出 Kotlin 代码;21 分的回答把该方案转为 Java 并简化(去掉 MessageDigest 哈希、改用 Arrays.toString),并在多款设备/系统组合(Google Pixel 4A/API 30、Galaxy S10/API 29、Nexus 6P/API 27 等,含恢复出厂设置测试)上验证了 UUID 在重装、重装+重启后的持久性;另有回答讨论 Settings.Secure.ANDROID_ID(64 位、出厂重置会变、某些机型存在所有实例相同的已知 bug)与 UUID 方案,以及一条指出在 Nokia 手机上恢复出厂设置后该标识符会改变的反驳。

翻译内容

原文内容(English)

⚠ 说明:原站直连返回 403(Cloudflare 安全验证),共享浏览器桥接亦被扩展面板阻塞;正文改由 Stack Exchange API(questions/58103580 及 answers)获取并回填至 _work/raw 的 content_text。正文保留问题、各回答及其得分与代码块。

标题:Android 10:API 29 上 IMEI 不再可用,寻找替代方案

链接:https://stackoverflow.com/questions/58103580/android-10-imei-no-longer-available-on-api-29-looking-for-alternatives

得分:34

问题

我们客户应用的主要功能严重依赖跟踪其客户的设备,他们提供的产品绑定到特定手机(而非其所有者)。以前可以用设备的 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% 可靠。

有什么想法吗?建议?经验?此刻任何选项都可能可行

回答(得分 26,未被采纳)

我建议你阅读 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,甚至能在恢复出厂设置后存活,且不需要在你的 manifest 文件中添加任何额外权限。

以下是我们如何在 Android 10 上获取唯一标识符的一段代码:

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

object UniqueDeviceID {

    /**
     * UUID for the Widevine DRM scheme.
     * 
     * 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) }
}

回答(得分 21,未被采纳)

对于对 Sofien 的方案感兴趣的 Java 用户,我:

  • 将 Sofien 的代码转换为 Java 并进一步简化;
  • 在 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) 它在 wideVineId 与其 String 表示之间保持一对一对应。如果你倾向于坚持十六进制转换,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,未被采纳)

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

然而,如果对设备执行恢复出厂设置,该值可能会改变。此外,某制造商的一款热门手机还存在一个已知 bug,即每个实例都有相同的 ANDROID_ID。显然,该方案并非 100% 可靠。

  • 使用 UUID。由于大多数应用的需求是标识某个特定的安装而非物理设备,为用户获取唯一 id 的一个好方案是使用 UUID 类。以下方案由来自 Google 的 Reto Meier 在一次 Google I/O 演讲中提出,
SharedPreferences sharedPrefs = context.getSharedPreferences(
PREF_UNIQUE_ID, Context.MODE_PRIVATE);
uniqueID = sharedPrefs.getString(PREF_UNIQUE_ID, null);

回答(得分 0,未被采纳)

可以使用 MEDIADRM API

//From 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,未被采纳)

我在 Nokia 手机上测试过,「当我在手机上恢复出厂设置时该标识符会改变」。你在恢复出厂设置后测试过吗?

TITLE: Android 10: IMEI no longer available on API 29. Looking for alternatives
LINK: https://stackoverflow.com/questions/58103580/android-10-imei-no-longer-available-on-api-29-looking-for-alternatives
SCORE: 34

=== QUESTION ===
Our client's app main feature is heavily relaying on tracking their clients' devices, they offer products that are bound to the specific phone(not its owner). This was possible using the device imei, but with the privacy changes in Android 10, they made it unreachable.
(https://developer.android.com/about/versions/10/privacy/changes).

Android has a documentation about what identifier to use on specific user cases, but non matches our case since we need it to be unique, constant and bound to the device(or at least difficult to change). https://developer.android.com/training/articles/user-data-ids.
I'm considering Android ID to be a possible solution, or using the mac address knowing they aren't 100% reliable.

Any thoughts? recommendations? experiences? at this point anything could be an option

=== ANSWER (score 26, accepted=False) ===
I advice you to read the official blog of the best practice of google to see what the use case match with your specification : https://developer.android.com/training/articles/user-data-ids.html

For me i occcured the same problem about the unicity of android identifiers and i found the only solution is to use the MediaDrm API ( https://android.googlesource.com/platform/frameworks/base/+/android-cts-4.4_r1/media/java/android/media/MediaDrm.java#539 )
which contains a unique device id and can survive even on the factory reset and doesn't need any additional permission on your manifest file.

Here is the couple of code how can we retreive the unique identifier on Android 10 :

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

object UniqueDeviceID {

    /**
     * UUID for the Widevine DRM scheme.
     * 
     * 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) }
}

=== ANSWER (score 21, accepted=False) ===
For Java users that are interested in Sofien's solution, I have:

  • Converted Sofien's code to Java and further simplified;
  • Extensively tested on Android 10 (API 29), Android 11 (API 30) and previous versions.
  1. Code and discussion
@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
}

There are two key differences w.r.t. Sofien's code.

  • I am not using the MessageDigest, which results in a simpler code. Moreover, the MessageDigest.update() method applies the SHA-256 hash function to its argument, which introduces an extremely low probability of losing UUID uniqueness. The only drawback of not hashing the UUID is that you don't have a fixed length UUID, which I don't care about in my application.
  • Instead of the Kotlin function toHexString (which has no one-line counterpart in Java) I am using Arrays.toString. This choice is safe because (A) It throws no Exception and (B) it retains a one-to-one correspondence between the wideVineId and its String representation. If you prefer to stick to hex conversion, the Apache Commons Codec library offers a one-line solution.

Of course, these changes result in a different UUID, needless to say that other choices are possible. Notice also that an UUID generated with Arrays.toString takes the form

[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]

So, if you don't want special characters in your UUID you can remove them with String.replaceAll().

  1. Tests

I have tested the persistence of the UUID

  • over reinstallation
  • over reinstallation AND reboot

on the following device/OS combinations:

  • Google Pixel 4A / API 30
  • Samsung Galaxy S10 / API 29
  • Samsung Galaxy S9 / API 29
  • Huawei Nexus 6P / API 27 (tested also factory reset)
  • LG V20 / API 27 (tested also factory reset)
  • 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

In all of the tests, the targetSdkVersion is 30. More tests (especially on API 29 and 30) are welcome.

=== ANSWER (score 3, accepted=False) ===

  • On a device first boot, a random value is generated and stored. This value is available via Settings.Secure.ANDROID_ID. It’s a 64-bit number that should remain constant for the lifetime of a device. ANDROID_ID seems a good choice for a unique device identifier because it’s available for smartphones and tablets. To retrieve the value, you can use the following code,
String androidId = Settings.Secure.getString(getContentResolver(),
                                             Settings.Secure.ANDROID_ID);

However, the value may change if a factory reset is performed on the device. There is also a known bug with a popular handset from a manufacturer where every instance has the same ANDROID_ID. Clearly, the solution is not 100% reliable.

  • Use UUID. As the requirement for most of the applications is to identify a particular installation and not a physical device, a good solution to get the unique id for a user if to use UUID class. The following solution has been presented by Reto Meier from Google in a Google I/O presentation,
SharedPreferences sharedPrefs = context.getSharedPreferences(
PREF_UNIQUE_ID, Context.MODE_PRIVATE);
uniqueID = sharedPrefs.getString(PREF_UNIQUE_ID, null);

=== ANSWER (score 0, accepted=False) ===
MEDIADRM API is one can use

//From 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)

=== ANSWER (score -4, accepted=False) ===
I have tested it in Nokia phone "the identifier is changed when I reset my phone on factory reset". Did you test it on factory reset?