安卓设备是否有唯一的设备ID?

3079

Android设备是否有唯一的ID,如果有的话,使用Java访问它的简单方法是什么?


48
如果你正在使用 ANDROID_ID,请务必阅读这个答案这个漏洞 - Dheeraj Vepakomma
54个回答

45

我认为这是一个可靠的方法来构建唯一ID的框架...看看吧。

伪唯一ID,适用于所有Android设备 有些设备没有电话(例如平板电脑),或者由于某种原因,您不想包括READ_PHONE_STATE权限。但您仍然可以读取ROM版本、制造商名称、CPU类型和其他硬件细节等详细信息,如果您想将该ID用于序列号检查或其他一般目的,则这些细节非常适合。以这种方式计算出的ID不会是唯一的:可能会找到具有相同ID(基于相同硬件和ROM映像)的两个设备,但在实际应用中,这种情况很少见。为此,您可以使用Build类:

String m_szDevIDShort = "35" + //we make this look like a valid IMEI
            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 ; //13 digits

大部分生成的成员都是字符串类型,这里我们计算它们的长度并通过取模转换为一个数字。我们有13个这样的数字,然后在前面添加两个(35)以使ID与IMEI(15位数字)具有相同的长度。其它方法也可以实现,请看这些字符串示例。

返回类似于355715565309247的东西。使用此方法无需特殊权限,非常方便。


(额外信息:上述技术是从Pocket Magic的一篇文章中复制来的。)


10
有趣的解决方案。听起来这是一个情况,你应该只将所有数据连接起来进行哈希处理,而不是试图自己想出“哈希”函数。即使每个值之间有很多不同的数据��也有许多实例会产生碰撞。我的建议:使用哈希函数,然后将二进制结果转换为十进制,并根据需要截断它。但是,要做到正确,你真的应该使用UUID或完整的哈希字符串。 - Steve Pomeroy
25
你应该给予你的来源适当的引用...这段内容直接摘自以下文章:http://www.pocketmagic.net/?p=1662 - Steve Haley
11
这个ID很容易发生碰撞,几乎可以保证在同一运营商的相同设备上是相同的。 - Seva Alekseyev
9
如果设备得到升级,这也有可能会改变。 - David Given
13
非常糟糕的解决方案。在两个Nexus 5上测试过...返回相同的数字。 - Sinan Dizdarević
显示剩余4条评论

43
以下代码使用一个隐藏的Android API返回设备序列号。但是,这段代码在三星Galaxy Tab上不起作用,因为该设备上未设置“ro.serialno”。
String serial = null;

try {
    Class<?> c = Class.forName("android.os.SystemProperties");
    Method get = c.getMethod("get", String.class);
    serial = (String) get.invoke(c, "ro.serialno");
}
catch (Exception ignored) {

}

33

使用以下代码,您可以获取Android操作系统设备的唯一设备ID作为字符串。

deviceId = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID); 

有特定设备或其他限制吗? - gumuruh

23

我想要补充一件事 - 我面临的是一种独特的情况。

使用:

deviceId = Secure.getString(this.getContext().getContentResolver(), Secure.ANDROID_ID);

事实证明,即使我的Viewsonic G平板电脑报告的DeviceID不为Null,但每个G平板电脑都报告相同的数字。

这使得玩“口袋帝国”变得有趣,该游戏根据“唯一”的DeviceID,立即让您访问某人的帐户。

我的设备没有蜂窝网络电台。


2
@Treewallie,它能正常工作吗?你能从不同的应用程序中获取相同的设备ID吗? - Arnold Brown

23

在API 9级别(Android 2.3 - Gingerbread)的Build类中添加了一个Serial字段。文档显示它代表硬件序列号,如果存在于设备上应该是唯一的。

然而,我不知道所有API级别大于等于9的设备是否实际支持(不为空值)此字段。


5
很遗憾,这是“未知的”。 - m0skit0

21

关于如何获取每个安装应用程序的Android设备的唯一标识符的详细说明,请参阅官方的Android Developer博客文章 Identifying App Installations

看起来最好的方法是在安装时自己生成一个标识符,并在应用程序重新启动时读取它。

我个人认为这样是可接受但并不理想的。Android提供的任何一个标识符都不能在所有情况下正常工作,因为大多数都依赖于手机的无线电状态(Wi-Fi开/关,蜂窝移动开/关,蓝牙开/关)。其他像 Settings.Secure.ANDROID_ID 这样的标识符必须由制造商实施,而且不能保证唯一性。

以下是将数据写入installation文件的示例,该文件将与应用程序本地保存的任何其他数据一起存储。

public class Installation {
    private static String sID = null;
    private static final String INSTALLATION = "INSTALLATION";

    public synchronized static String id(Context context) {
        if (sID == null) {
            File installation = new File(context.getFilesDir(), INSTALLATION);
            try {
                if (!installation.exists())
                    writeInstallationFile(installation);
                sID = readInstallationFile(installation);
            } 
            catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return sID;
    }

    private static String readInstallationFile(File installation) throws IOException {
        RandomAccessFile f = new RandomAccessFile(installation, "r");
        byte[] bytes = new byte[(int) f.length()];
        f.readFully(bytes);
        f.close();
        return new String(bytes);
    }

    private static void writeInstallationFile(File installation) throws IOException {
        FileOutputStream out = new FileOutputStream(installation);
        String id = UUID.randomUUID().toString();
        out.write(id.getBytes());
        out.close();
    }
}

1
如果您想追踪应用程序的安装情况,这是完美的。然而,跟踪设备要困难得多,似乎没有完全可靠的解决方案。 - Luca Spiller
根据设备状态,怎么处理?他们可以轻易地更改此安装ID,对吧? - tasomaniac
当然可以。Root用户可以更改安装ID。您可以使用以下代码块检查是否为Root用户:https://dev59.com/4nNA5IYBdhLWcg3wEpgU#8097801 - Kevin Parker
如果我们重置工厂,文件会被删除吗? - Jamshid
如果您进行了恢复出厂设置并删除或格式化了/data分区,则UUID会发生变化。 - Kevin Parker

16
在类文件中添加以下代码:
final TelephonyManager tm = (TelephonyManager) getBaseContext()
            .getSystemService(SplashActivity.TELEPHONY_SERVICE);
    final String tmDevice, tmSerial, androidId;
    tmDevice = "" + tm.getDeviceId();
    Log.v("DeviceIMEI", "" + tmDevice);
    tmSerial = "" + tm.getSimSerialNumber();
    Log.v("GSM devices Serial Number[simcard] ", "" + tmSerial);
    androidId = "" + android.provider.Settings.Secure.getString(getContentResolver(),
            android.provider.Settings.Secure.ANDROID_ID);
    Log.v("androidId CDMA devices", "" + androidId);
    UUID deviceUuid = new UUID(androidId.hashCode(),
            ((long) tmDevice.hashCode() << 32) | tmSerial.hashCode());
    String deviceId = deviceUuid.toString();
    Log.v("deviceIdUUID universally unique identifier", "" + deviceId);
    String deviceModelName = android.os.Build.MODEL;
    Log.v("Model Name", "" + deviceModelName);
    String deviceUSER = android.os.Build.USER;
    Log.v("Name USER", "" + deviceUSER);
    String devicePRODUCT = android.os.Build.PRODUCT;
    Log.v("PRODUCT", "" + devicePRODUCT);
    String deviceHARDWARE = android.os.Build.HARDWARE;
    Log.v("HARDWARE", "" + deviceHARDWARE);
    String deviceBRAND = android.os.Build.BRAND;
    Log.v("BRAND", "" + deviceBRAND);
    String myVersion = android.os.Build.VERSION.RELEASE;
    Log.v("VERSION.RELEASE", "" + myVersion);
    int sdkVersion = android.os.Build.VERSION.SDK_INT;
    Log.v("VERSION.SDK_INT", "" + sdkVersion);

在 AndroidManifest.xml 中添加:
<uses-permission android:name="android.permission.READ_PHONE_STATE" />

15

围绕ANDROID_ID问题,有许多不同的方法可以解决(有时可能为null或某个特定型号设备始终返回相同的ID),这些方法各有利弊:

  • 实现自定义ID生成算法(基于设备属性,这些属性应该是静态的并且不会改变->谁知道)
  • 滥用其他ID,如IMEI,序列号,Wi-Fi /蓝牙MAC地址(它们不会存在于所有设备上或需要附加权限)

我个人喜欢在Android上使用现有的OpenUDID实现(请参见https://github.com/ylechelle/OpenUDID),也可参考于https://github.com/vieux/OpenUDID。它易于集成,并利用ANDROID_ID来解决上述问题的后备方案。


13

我的两分 - 注意这是针对设备 (错) 唯一 ID 而非在 Android 开发者博客 中讨论的安装 ID。

需要注意的是,@emmby 提供的 解决方案 会退回到应用程序 ID,因为 SharedPreferences 在进程间不同步(请参见此处此处)。所以我完全避免了这个问题。

相反,我将获取 (设备) ID 的各种策略封装在一个枚举中——改变枚举常量的顺序会影响获取 ID 的各种方式的优先级。返回第一个非空 ID 或抛出异常(根据好的 Java 实践,不给 null 赋予含义)。因此,例如我首先选择 TELEPHONY,但 ANDROID_ID 是一个很好的默认选择。 beta:

import android.Manifest.permission;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.wifi.WifiManager;
import android.provider.Settings.Secure;
import android.telephony.TelephonyManager;
import android.util.Log;

// TODO : hash
public final class DeviceIdentifier {

    private DeviceIdentifier() {}

    /** @see http://code.google.com/p/android/issues/detail?id=10603 */
    private static final String ANDROID_ID_BUG_MSG = "The device suffers from "
        + "the Android ID bug - its ID is the emulator ID : "
        + IDs.BUGGY_ANDROID_ID;
    private static volatile String uuid; // volatile needed - see EJ item 71
    // need lazy initialization to get a context

    /**
     * Returns a unique identifier for this device. The first (in the order the
     * enums constants as defined in the IDs enum) non null identifier is
     * returned or a DeviceIDException is thrown. A DeviceIDException is also
     * thrown if ignoreBuggyAndroidID is false and the device has the Android ID
     * bug
     *
     * @param ctx
     *            an Android constant (to retrieve system services)
     * @param ignoreBuggyAndroidID
     *            if false, on a device with the android ID bug, the buggy
     *            android ID is not returned instead a DeviceIDException is
     *            thrown
     * @return a *device* ID - null is never returned, instead a
     *         DeviceIDException is thrown
     * @throws DeviceIDException
     *             if none of the enum methods manages to return a device ID
     */
    public static String getDeviceIdentifier(Context ctx,
            boolean ignoreBuggyAndroidID) throws DeviceIDException {
        String result = uuid;
        if (result == null) {
            synchronized (DeviceIdentifier.class) {
                result = uuid;
                if (result == null) {
                    for (IDs id : IDs.values()) {
                        try {
                            result = uuid = id.getId(ctx);
                        } catch (DeviceIDNotUniqueException e) {
                            if (!ignoreBuggyAndroidID)
                                throw new DeviceIDException(e);
                        }
                        if (result != null) return result;
                    }
                    throw new DeviceIDException();
                }
            }
        }
        return result;
    }

    private static enum IDs {
        TELEPHONY_ID {

            @Override
            String getId(Context ctx) {
                // TODO : add a SIM based mechanism ? tm.getSimSerialNumber();
                final TelephonyManager tm = (TelephonyManager) ctx
                        .getSystemService(Context.TELEPHONY_SERVICE);
                if (tm == null) {
                    w("Telephony Manager not available");
                    return null;
                }
                assertPermission(ctx, permission.READ_PHONE_STATE);
                return tm.getDeviceId();
            }
        },
        ANDROID_ID {

            @Override
            String getId(Context ctx) throws DeviceIDException {
                // no permission needed !
                final String andoidId = Secure.getString(
                    ctx.getContentResolver(),
                    android.provider.Settings.Secure.ANDROID_ID);
                if (BUGGY_ANDROID_ID.equals(andoidId)) {
                    e(ANDROID_ID_BUG_MSG);
                    throw new DeviceIDNotUniqueException();
                }
                return andoidId;
            }
        },
        WIFI_MAC {

            @Override
            String getId(Context ctx) {
                WifiManager wm = (WifiManager) ctx
                        .getSystemService(Context.WIFI_SERVICE);
                if (wm == null) {
                    w("Wifi Manager not available");
                    return null;
                }
                assertPermission(ctx, permission.ACCESS_WIFI_STATE); // I guess
                // getMacAddress() has no java doc !!!
                return wm.getConnectionInfo().getMacAddress();
            }
        },
        BLUETOOTH_MAC {

            @Override
            String getId(Context ctx) {
                BluetoothAdapter ba = BluetoothAdapter.getDefaultAdapter();
                if (ba == null) {
                    w("Bluetooth Adapter not available");
                    return null;
                }
                assertPermission(ctx, permission.BLUETOOTH);
                return ba.getAddress();
            }
        }
        // TODO PSEUDO_ID
        // http://www.pocketmagic.net/2011/02/android-unique-device-id/
        ;

        static final String BUGGY_ANDROID_ID = "9774d56d682e549c";
        private final static String TAG = IDs.class.getSimpleName();

        abstract String getId(Context ctx) throws DeviceIDException;

        private static void w(String msg) {
            Log.w(TAG, msg);
        }

        private static void e(String msg) {
            Log.e(TAG, msg);
        }
    }

    private static void assertPermission(Context ctx, String perm) {
        final int checkPermission = ctx.getPackageManager().checkPermission(
            perm, ctx.getPackageName());
        if (checkPermission != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("Permission " + perm + " is required");
        }
    }

    // =========================================================================
    // Exceptions
    // =========================================================================
    public static class DeviceIDException extends Exception {

        private static final long serialVersionUID = -8083699995384519417L;
        private static final String NO_ANDROID_ID = "Could not retrieve a "
            + "device ID";

        public DeviceIDException(Throwable throwable) {
            super(NO_ANDROID_ID, throwable);
        }

        public DeviceIDException(String detailMessage) {
            super(detailMessage);
        }

        public DeviceIDException() {
            super(NO_ANDROID_ID);
        }
    }

    public static final class DeviceIDNotUniqueException extends
            DeviceIDException {

        private static final long serialVersionUID = -8940090896069484955L;

        public DeviceIDNotUniqueException() {
            super(ANDROID_ID_BUG_MSG);
        }
    }
}

13

这里有30多个答案,有些相同,有些独特。本答案基于其中一些答案,其中之一是@Lenn Dolling的答案。

它结合了3个ID并创建了一个32位十六进制字符串。它对我非常有效。

3个ID分别为:
Pseudo-ID - 它基于物理设备规格生成
ANDROID_ID - Settings.Secure.ANDROID_ID
蓝牙地址 - 蓝牙适配器地址

它将返回类似于这样的内容:551F27C060712A72730B0A0F734064B1

注意:您可以始终向longId字符串添加更多的ID,例如串号、wifi适配器地址、IMEI。这样,您就可以使每个设备更加独特。

@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());

            // get md5 bytes
            byte md5Bytes[] = messageDigest.digest();

            // creating a hex string
            String identifier = "";

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

                // if it is a single digit, make sure it have 0 in front (proper padding)
                if (b <= 0xF) {
                    identifier += "0";
                }

                // add number to string
                identifier += Integer.toHexString(b);
            }

            // hex string to uppercase
            identifier = identifier.toUpperCase();
            return identifier;
        } catch (Exception e) {
            Log.e("TAG", e.toString());
        }
        return "";
}

3
UUID 添加到 longId 中并将其存储在文件中,将使它成为最独特的标识符:String uuid = UUID.randomUUID().toString(); - Mousa Alfhaily
3
如果一切尝试都失败了,如果用户的API低于9(低于Gingerbread),已重置他们的手机或'secure.ANDROID_ID'。如果返回'null',则返回的ID将仅基于其Android设备信息。这就是可能发生碰撞的地方。 请勿使用DISPLAY、HOST或ID - 这些项目可能会更改。 如果存在碰撞,则会出现重叠数据。来源:https://gist.github.com/pedja1/fe69e8a80ed505500caa - Mousa Alfhaily
2
@Ninja 由于BLE mac地址是唯一的,所以生成的ID将始终是唯一的。但是,如果您真的想确保,我建议将UUID添加到“longId”中。像这样更改那一行:String longId = pseudoId + androidId + btId + UUID.randomUUID().toString(); 这可以确保生成的ID是唯一的。 - ᴛʜᴇᴘᴀᴛᴇʟ

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接