Android 与 iOS 中的唯一标识符:ANDROID_ID、UUID 及其他方案 原文标题:Unique Identifiers in Android and iOS: ANDROID_ID, UUID, and Other Strategies
内容概要总结
本文是开发者 Alejandro Cordón 的个人技术博客,系统梳理在 Android 与 iOS 上识别设备的主要方式及局限。作者指出 ANDROID_ID 由“签名密钥+用户+设备”三者决定,因此不同签名密钥的两个 App 即使在同一设备同一用户上也不共享同一个值;iOS 没有 ANDROID_ID 的等价物,只能靠 UUID() 生成随机 128 位标识符并自行持久化(如存入 Keychain)。文章还对比 GAID、IDFA、Firebase Installation ID(FID)、OneSignal Player ID、MAC 地址、IMEI 及设备指纹,并给出持久性对照表。核心结论:不存在跨平台、全场景通用的设备标识符,最佳做法是“本地生成 UUID + 安全存储(iOS Keychain / Android EncryptedSharedPreferences 或 KeyStore)”,并预判恢复出厂、卸载、切换用户、权限撤销等丢失场景,遵守 GDPR、CCPA 等法规。文中附 Android 与 iOS 的自定义 UUID 代码示例。
翻译内容
原文内容(English)
在移动应用开发中,通常需要一个能稳定识别每台设备的唯一标识符——无论是用于分析、个性化、授权许可还是使用量追踪。然而,由于隐私政策、平台特定的限制,以及各种可能导致标识符变化的事件,获取和管理这个标识符往往相当棘手。
本文探讨在 Android 和 iOS 中识别设备的主要方式,重点介绍 ANDROID_ID 与 iOS 中的 UUID。我们也会介绍其他可用的替代方案,讨论它们的局限,并回顾实现方面的最佳实践。
主要的原生标识符
Android 中的 ANDROID_ID
ANDROID_ID 的值由三个因素决定:App 的签名密钥、用户、以及设备。这意味着:
- App、用户与设备的每一种组合都有一个唯一的 ANDROID_ID。
- 如果两个 App 的签名密钥不同,即使在同一设备、同一用户上,它们也不会共享同一个 ANDROID_ID。
获取它很简单:
// Kotlin
import android.provider.Settings
val androidId = Settings.Secure.getString(
context.contentResolver,
Settings.Secure.ANDROID_ID
)
// Java
import android.provider.Settings;
String androidId = Settings.Secure.getString(
getContentResolver(),
Settings.Secure.ANDROID_ID
);
行为:它在设备重启和系统更新后依然保留。但在恢复出厂设置后、在多用户设备上切换用户时,或 App 用不同证书签名时,它可能会改变。
局限:它不是永久标识符。在开发期间,像 Firebase App Distribution 这样的工具会使用不同的签名密钥,这意味着系统可能把这些版本当作不同的 App——从而导致 ANDROID_ID 不同,并可能影响依赖其一致性的测试。
iOS 中的 UUID
iOS 没有提供与 ANDROID_ID 直接对应的东西。相反,开发者通常使用 UUID()(Swift)或 NSUUID(Objective-C)来生成随机的、全局唯一的 128 位标识符。
import UIKit
let uuid = UUID().uuidString
print("Generated UUID: \(uuid)")
#import <Foundation/Foundation.h>
NSUUID *uuidObj = [NSUUID UUID];
NSString *uuidString = [uuidObj UUIDString];
NSLog(@"Generated UUID: %@", uuidString);
行为:每次调用都会产生一个全新的 UUID。除非你显式地存储它(例如存到 Keychain 中),否则它不会在 App 卸载或启动之间保留。iOS 默认并不暴露一个单一的、全局的“设备 ID”。
identifierForVendor:UIDevice.current.identifierForVendor?.uuidString 提供一个同一开发者(Team ID)所有 App 共享的 UUID。如果用户卸载了该开发者的所有 App 然后再重装其中一个,它就会改变。它比随机生成的 UUID 更持久,但仍然是可变的。
局限:iOS 中没有像 ANDROID_ID 那样的通用 ID。要让一个 ID 随时间持久,开发者必须把它存储在安全位置,例如 Keychain。
识别设备的其他方式
除 ANDROID_ID 和 UUID 外,还有其他方法,每一种都有各自的隐私和技术约束。
广告标识符
- Google Advertising ID(GAID)——Android:主要用于广告和分析。用户可重置或禁用。不推荐用于长期设备识别。
- IDFA(Identifier for Advertisers)——iOS:与 GAID 类似。自 iOS 14.5 起,需要通过 App Tracking Transparency 获得用户的明确许可。也可由用户重置。作为永久设备 ID 并不可靠。
第三方服务标识符
- Firebase Installation ID(FID):为每次 App 安装生成,若用户卸载并重装 App,它可能改变。
- OneSignal Player ID:与 OneSignal 服务中的订阅绑定。
如果你已经在使用这些平台,它们很容易利用,但该 ID 关联的是安装,而不是物理设备。
MAC 地址与 IMEI
- MAC 地址:曾在两个平台上都可访问,如今出于隐私原因受到严格限制。iOS 常常返回一个占位值,Android 从 Marshmallow 起限制访问。
- IMEI/MEID:蜂窝设备中的调制解调器标识符。在 Android 上访问仅限于特权权限,在 iOS 上不向第三方 App 暴露。
设备指纹
将多个设备属性(操作系统版本、机型、时区、语言、传感器等)组合起来,生成一个充当伪标识符的哈希。它不需要访问某个特定 ID,但并非 100% 可靠——不同设备可能共享相似的指纹,而配置变化会改变结果。它还引发潜在的隐私担忧。
对比与影响
持久性
| 标识符 | 持久性 |
|---|---|
| ANDROID_ID | 在重启和系统更新后保留。恢复出厂设置或切换用户后丢失。 |
| UUID(iOS) | 没有内置持久性。必须显式存储。 |
| identifierForVendor | 如果同一开发者的所有 App 都被卸载,则会改变。 |
| GAID / IDFA | 用户可随时重置。 |
| FID / Player ID | 与 App 安装绑定,依赖外部服务。 |
| MAC / IMEI | 在现代操作系统版本上受限或不可访问。 |
唯一识别
- ANDROID_ID:通常对每个“设备-用户”配对是唯一的,但在特定场景下可能改变。
- UUID(iOS):每次生成调用都产生真正唯一的值,但 iOS 不提供默认的稳定设备 ID。
- 广告 ID:用户可重置,取决于用户同意。
设计考量
- 在每个平台持久化一个 UUID:将自定义生成的 UUID 存储在安全容器中(iOS 用 Keychain,Android 用 EncryptedSharedPreferences 或 KeyStore)。
- 预判丢失:考虑恢复出厂设置、App 卸载、切换用户或权限撤销等情况。
- 遵守隐私政策:Google 和 Apple 对追踪有严格的规定。确保符合用户同意要求以及 GDPR、CCPA 等法规。
代码示例与最佳实践
Android:SharedPreferences 中的自定义 UUID
fun getOrCreateCustomUUID(context: Context): String {
val sharedPreferences = context.getSharedPreferences(
"MyAppPrefs", Context.MODE_PRIVATE
)
var customUuid = sharedPreferences.getString("CUSTOM_UUID", null)
if (customUuid == null) {
customUuid = UUID.randomUUID().toString()
sharedPreferences.edit()
.putString("CUSTOM_UUID", customUuid)
.apply()
}
return customUuid
}
这个 UUID 会持久保留,除非用户清除 App 数据或执行恢复出厂设置。
iOS:Keychain 中的自定义 UUID
func getOrCreateCustomUUID() -> String? {
let key = "CUSTOM_UUID_KEY"
if let existingUuid = KeychainHelper.shared.read(
service: "com.myapp", account: key
) {
return existingUuid
} else {
let newUuid = UUID().uuidString
KeychainHelper.shared.save(
service: "com.myapp", account: key, data: newUuid
)
return newUuid
}
}
在卸载 App 后 Keychain 通常仍然完好,提供长期持久性,除非用户擦除整个设备。
结论
没有一个单一的、通用的设备标识符能同时适用于两个平台并在所有场景下存活。最佳策略是将本地生成的 UUID 与安全存储相结合,以便在大多数情况下识别同一台设备,并在标识符意外改变时处理回退逻辑。
始终遵循 Apple 和 Google 的隐私准则,以及 GDPR、CCPA 等更广泛的数据保护法规,尤其是在处理个人数据时。
作者:Alejandro Cordón — alejandrocordon.com
保持关注
隐私、端侧 AI 与独立开发。有值得分享的内容时发一封邮件。
Anatomy of a Promise:'Your Voice Never Leaves the Phone' 技术栈逐层拆解
2026 年 7 月 20 日 · 4 分钟
从 Vosk 到 Parakeet:九个月里的三个语音引擎
2026 年 7 月 15 日 · 5 分钟
在生产环境端侧运行 Whisper 与 Parakeet 的经验教训
2026 年 7 月 10 日 · 8 分钟
In mobile app development, it is common to need a unique identifier that consistently recognizes each device — whether for analytics, personalization, licensing, or usage tracking. However, obtaining and managing that identifier can be tricky due to privacy policies, platform-specific restrictions, and various events that can cause the identifier to change.
In this article, we explore the main ways to identify devices in Android and iOS, with a focus on ANDROID_ID and UUID in iOS. We’ll also look at other available alternatives, discuss their limitations, and review best practices for implementation.
Main Native Identifiers
ANDROID_ID in Android
The value of ANDROID_ID is defined based on three factors: the app’s signing key, the user, and the device. This means:
- Each combination of app, user, and device has a unique ANDROID_ID.
- If two apps have different signing keys, even on the same device and same user, they will not share the same ANDROID_ID.
Retrieving it is straightforward:
// Kotlin
import android.provider.Settings
val androidId = Settings.Secure.getString(
context.contentResolver,
Settings.Secure.ANDROID_ID
)
// Java
import android.provider.Settings;
String androidId = Settings.Secure.getString(
getContentResolver(),
Settings.Secure.ANDROID_ID
);
Behavior: It persists across device reboots and system updates. However, it may change after a factory reset, when switching users on multi-user devices, or if the app is signed with a different certificate.
Limitations: It is not a permanent identifier. During development, tools like Firebase App Distribution use different signing keys, which means the system may treat these versions as separate apps — causing the ANDROID_ID to differ and potentially impacting tests that rely on its consistency.
UUID in iOS
iOS does not provide a direct equivalent to ANDROID_ID. Instead, developers typically use UUID() (Swift) or NSUUID (Objective-C) to generate random, universally unique 128-bit identifiers.
import UIKit
let uuid = UUID().uuidString
print("Generated UUID: \(uuid)")
#import <Foundation/Foundation.h>
NSUUID *uuidObj = [NSUUID UUID];
NSString *uuidString = [uuidObj UUIDString];
NSLog(@"Generated UUID: %@", uuidString);
Behavior: Each call produces a brand-new UUID. It does not persist between app uninstalls or launches unless you explicitly store it (e.g., in the Keychain). There is no single, global “device ID” that iOS exposes by default.
identifierForVendor: UIDevice.current.identifierForVendor?.uuidString provides a UUID shared among all apps from the same developer (Team ID). It changes if the user uninstalls all apps from that developer and then reinstalls one. More persistent than a randomly generated UUID, but still mutable.
Limitations: There is no universal ID in iOS like ANDROID_ID. To persist an ID over time, developers must store it in a secure location such as the Keychain.
Other Ways to Identify Devices
Beyond ANDROID_ID and UUID, there are other methods, each with its own privacy and technical constraints.
Advertising Identifiers
Google Advertising ID (GAID) — Android: Primarily for advertising and analytics. Users can reset or disable it. Not recommended for long-term device identification.
IDFA (Identifier for Advertisers) — iOS: Similar to GAID. Since iOS 14.5, it requires the user’s explicit permission via App Tracking Transparency. Can also be reset by the user. Not reliable as a permanent device ID.
Third-Party Service Identifiers
Firebase Installation ID (FID): Generated for each app installation and can change if the user uninstalls and reinstalls the app.
OneSignal Player ID: Tied to the subscription in the OneSignal service.
These are easy to leverage if you already use these platforms, but the ID is associated with an installation, not the physical device.
MAC Address and IMEI
MAC Address: Once accessible on both platforms, now strongly restricted for privacy reasons. iOS often returns a placeholder value, and Android restricts access from Marshmallow onward.
IMEI/MEID: The modem identifier in cellular devices. Access is limited to privileged permissions on Android and is not exposed to third-party apps on iOS.
Device Fingerprinting
Combining multiple device attributes (OS version, model, time zone, language, sensors, etc.) to create a hash that acts as a pseudo-identifier. It doesn’t require access to a specific ID, but it’s not 100% reliable — different devices can share similar fingerprints, and configuration changes can alter the result. It also raises potential privacy concerns.
Comparison and Implications
Persistence
| Identifier | Persistence |
|---|---|
| ANDROID_ID | Survives reboots and OS updates. Lost after factory reset or user change. |
| UUID (iOS) | No built-in persistence. Must be stored explicitly. |
| identifierForVendor | Changes if all apps from the same developer are uninstalled. |
| GAID / IDFA | Resettable by the user at any time. |
| FID / Player ID | Tied to app installation, depends on external services. |
| MAC / IMEI | Restricted or inaccessible on modern OS versions. |
Unique Identification
- ANDROID_ID: Typically unique per device-user pairing, but can change in specific scenarios.
- UUID (iOS): Each generation call produces a truly unique value, but iOS provides no default stable device ID.
- Advertising IDs: Resettable by the user, dependent on consent.
Design Considerations
- Persist a UUID per platform: Store a custom-generated UUID in a secure container (Keychain on iOS, EncryptedSharedPreferences or KeyStore on Android).
- Anticipate loss: Consider factory resets, app uninstalls, user changes, or permission revocations.
- Follow privacy policies: Google and Apple have strict guidelines on tracking. Ensure compliance with user consent and regulations like GDPR and CCPA.
Code Examples and Best Practices
Android: Custom UUID in SharedPreferences
fun getOrCreateCustomUUID(context: Context): String {
val sharedPreferences = context.getSharedPreferences(
"MyAppPrefs", Context.MODE_PRIVATE
)
var customUuid = sharedPreferences.getString("CUSTOM_UUID", null)
if (customUuid == null) {
customUuid = UUID.randomUUID().toString()
sharedPreferences.edit()
.putString("CUSTOM_UUID", customUuid)
.apply()
}
return customUuid
}
This UUID persists unless the user clears the app data or performs a factory reset.
iOS: Custom UUID in the Keychain
func getOrCreateCustomUUID() -> String? {
let key = "CUSTOM_UUID_KEY"
if let existingUuid = KeychainHelper.shared.read(
service: "com.myapp", account: key
) {
return existingUuid
} else {
let newUuid = UUID().uuidString
KeychainHelper.shared.save(
service: "com.myapp", account: key, data: newUuid
)
return newUuid
}
}
The Keychain typically remains intact after uninstalling the app, offering long-term persistence unless the user wipes the entire device.
Conclusion
There is no single, universal device identifier that works across both platforms and survives every scenario. The best strategy is to combine a locally generated UUID with secure storage to recognize the same device in most situations, and handle fallback logic when an identifier changes unexpectedly.
Always follow Apple and Google’s privacy guidelines, as well as broader data protection regulations like GDPR and CCPA, especially when dealing with personal data.
Written by Alejandro Cordón — alejandrocordon.com
Stay in the loop
Privacy, on-device AI, and indie dev. One email when there's something worth sharing.
Anatomy of a Promise: The 'Your Voice Never Leaves the Phone' Stack, Piece by Piece
Jul 20, 2026
·
4 min
From Vosk to Parakeet: Three Speech Engines in Nine Months
Jul 15, 2026
·
5 min
Lessons From Running Whisper and Parakeet On-Device in Production
Jul 10, 2026
·
8 min