是否存在唯一的 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(); } });
核心代码 结合多种信息生成设备 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。