是否存在唯一的 Android 设备 ID?

是否存在唯一的 Android 设备 ID?

技术背景

在 Android 开发中,有时需要为设备生成唯一的标识符,用于设备追踪、用户统计等。然而,由于 Android 设备的多样性和各种限制,获取真正唯一的设备 ID 并非易事。不同的方法有不同的优缺点和适用场景。

实现步骤

1. 使用 Settings.Secure.ANDROID_ID

这是一个 64 位的十六进制字符串,理论上每个用户唯一。

1
2
3
4
import android.provider.Settings.Secure;

private String android_id = Secure.getString(getContext().getContentResolver(),
Secure.ANDROID_ID);

2. 结合 TelephonyManager 相关信息

需要 android.permission.READ_PHONE_STATE 权限。

1
2
3
4
5
6
7
8
final TelephonyManager tm = (TelephonyManager) getBaseContext().getSystemService(Context.TELEPHONY_SERVICE);
final String tmDevice, tmSerial, androidId;
tmDevice = "" + tm.getDeviceId();
tmSerial = "" + tm.getSimSerialNumber();
androidId = "" + android.provider.Settings.Secure.getString(getContentResolver(), android.provider.Settings.Secure.ANDROID_ID);

UUID deviceUuid = new UUID(androidId.hashCode(), ((long)tmDevice.hashCode() << 32) | tmSerial.hashCode());
String deviceId = deviceUuid.toString();

3. 生成伪唯一 ID

1
2
3
4
5
6
7
8
9
10
11
12
13
public static String getUniquePsuedoID() {
String m_szDevIDShort = "35" + (Build.BOARD.length() % 10) + (Build.BRAND.length() % 10) + (Build.CPU_ABI.length() % 10) + (Build.DEVICE.length() % 10) + (Build.MANUFACTURER.length() % 10) + (Build.MODEL.length() % 10) + (Build.PRODUCT.length() % 10);

String serial = null;
try {
serial = android.os.Build.class.getField("SERIAL").get(null).toString();
return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
} catch (Exception exception) {
serial = "serial";
}

return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
}

4. 使用 FirebaseInstallations

需要添加依赖:

1
implementation 'com.google.firebase:firebase-messaging:23.0.0'

获取 Firebase ID:

1
2
3
4
5
6
FirebaseInstallations.getInstance().getId().addOnCompleteListener(task -> {
if (task.isSuccessful()) {
String firebaseIdentifier = task.getResult();
// Do what you need with firebaseIdentifier
}
});

核心代码

结合多种信息生成设备 UUID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import android.content.Context;
import android.content.SharedPreferences;
import android.provider.Settings.Secure;
import android.telephony.TelephonyManager;

import java.io.UnsupportedEncodingException;
import java.util.UUID;

public class DeviceUuidFactory {

protected static final String PREFS_FILE = "device_id.xml";
protected static final String PREFS_DEVICE_ID = "device_id";
protected volatile static UUID uuid;

public DeviceUuidFactory(Context context) {
if (uuid == null) {
synchronized (DeviceUuidFactory.class) {
if (uuid == null) {
final SharedPreferences prefs = context
.getSharedPreferences(PREFS_FILE, 0);
final String id = prefs.getString(PREFS_DEVICE_ID, null);
if (id != null) {
uuid = UUID.fromString(id);
} else {
final String androidId = Secure.getString(
context.getContentResolver(), Secure.ANDROID_ID);
try {
if (!"9774d56d682e549c".equals(androidId)) {
uuid = UUID.nameUUIDFromBytes(androidId
.getBytes("utf8"));
} else {
final String deviceId = (
(TelephonyManager) context
.getSystemService(Context.TELEPHONY_SERVICE))
.getDeviceId();
uuid = deviceId != null ? UUID
.nameUUIDFromBytes(deviceId
.getBytes("utf8")) : UUID
.randomUUID();
}
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
prefs.edit()
.putString(PREFS_DEVICE_ID, uuid.toString())
.commit();
}
}
}
}
}

public UUID getDeviceUuid() {
return uuid;
}
}

生成结合 3 种 ID 的 32 位十六进制字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.provider.Settings;
import android.util.Log;

import java.security.MessageDigest;

@SuppressWarnings("deprecation")
@SuppressLint("HardwareIds")
public static String generateDeviceIdentifier(Context context) {

String pseudoId = "35" +
Build.BOARD.length() % 10 +
Build.BRAND.length() % 10 +
Build.CPU_ABI.length() % 10 +
Build.DEVICE.length() % 10 +
Build.DISPLAY.length() % 10 +
Build.HOST.length() % 10 +
Build.ID.length() % 10 +
Build.MANUFACTURER.length() % 10 +
Build.MODEL.length() % 10 +
Build.PRODUCT.length() % 10 +
Build.TAGS.length() % 10 +
Build.TYPE.length() % 10 +
Build.USER.length() % 10;

String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);

BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
String btId = "";

if (bluetoothAdapter != null) {
btId = bluetoothAdapter.getAddress();
}

String longId = pseudoId + androidId + btId;

try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
messageDigest.update(longId.getBytes(), 0, longId.length());

byte md5Bytes[] = messageDigest.digest();

String identifier = "";

for (byte md5Byte : md5Bytes) {
int b = (0xFF & md5Byte);

if (b <= 0xF) {
identifier += "0";
}

identifier += Integer.toHexString(b);
}

identifier = identifier.toUpperCase();
return identifier;
} catch (Exception e) {
Log.e("TAG", e.toString());
}
return "";
}

最佳实践

  • 使用 FirebaseInstallations:对于大多数情况,使用 FirebaseInstallations 生成的 ID 是一个不错的选择,它能满足大多数应用的设备标识需求,且相对可靠。
  • 结合多种方法:如果需要更高的唯一性,可以结合多种方法生成 ID,但要注意权限和兼容性问题。
  • 数据安全:如果需要将设备 ID 存储在远程服务器,建议使用带盐的哈希值存储,以保护用户隐私。

常见问题

1. ANDROID_ID 存在的问题

  • 某些设备(如 Android 2.2 版本的部分设备)可能存在 ANDROID_ID 相同的问题,值为 9774d56d682e549c
  • 设备进行恢复出厂设置后,ANDROID_ID 可能会改变。
  • 已 root 的设备可以修改 ANDROID_ID

2. 硬件标识符的可靠性问题

  • 大多数非 Google 设备(除了 Pixels 和 Nexuses)的硬件标识符(如 IMEI、MAC 地址等)不可靠。
  • 随着 Android 安全更新,获取这些硬件标识符需要更严格的运行时权限,用户可能会拒绝授权。

3. AdvertisingIdClient 的局限性

  • AdvertisingIdClient 仅适用于广告分析场景,不能用于设备标识。
  • 用户可以随时重置或阻止广告 ID。

是否存在唯一的 Android 设备 ID?
https://119291.xyz/posts/2025-05-12.is-there-a-unique-android-device-id/
作者
ww
发布于
2025年5月12日
许可协议