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
Top comments (0)