Android 设备有唯一的 ID 吗?

发布于 2024-09-01 00:01:55 字数 51 浏览 5 评论 0 原文

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

Do Android devices have a unique ID, and if so, what is a simple way to access it using Java?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(30

成熟的代价 2024-09-08 00:01:56

Google I/O 上,Reto Meier 发布了关于如何解决此问题的可靠答案,这应该可以满足大多数开发人员跟踪的需求跨安装的用户。安东尼·诺兰在他的答案中展示了方向,但我想我应该写出完整的方法,以便其他人可以轻松地了解如何做到这一点(我花了一段时间才弄清楚细节)。

这种方法将为您提供一个匿名、安全的用户 ID,该 ID 对于用户在不同设备(基于主要 Google 帐户)和安装之间的持久性。基本方法是生成随机用户 ID 并将其存储在应用程序的共享首选项中。然后,您可以使用 Google 的备份代理将链接到云中 Google 帐户的共享首选项存储起来。

让我们看一下完整的方法。首先,我们需要使用 Android 备份服务为 SharedPreferences 创建备份。首先通过 http://developer.android.com/google/backup/signup.html 注册您的应用。

Google 将为您提供一个备份服务密钥,您需要将其添加到清单中。您还需要告诉应用程序使用 BackupAgent,如下所示:

<application android:label="MyApplication"
         android:backupAgent="MyBackupAgent">
    ...
    <meta-data android:name="com.google.android.backup.api_key"
        android:value="your_backup_service_key" />
</application>

然后您需要创建备份代理并告诉它使用共享首选项的帮助代理:

public class MyBackupAgent extends BackupAgentHelper {
    // The name of the SharedPreferences file
    static final String PREFS = "user_preferences";

    // A key to uniquely identify the set of backup data
    static final String PREFS_BACKUP_KEY = "prefs";

    // Allocate a helper and add it to the backup agent
    @Override
    public void onCreate() {
        SharedPreferencesBackupHelper helper = new SharedPreferencesBackupHelper(this,          PREFS);
        addHelper(PREFS_BACKUP_KEY, helper);
    }
}

要完成备份,您需要在主 Activity 中创建 BackupManager 的实例:

BackupManager backupManager = new BackupManager(context);

最后创建一个用户 ID(如果尚不存在),并将其存储在 SharedPreferences 中:

  public static String getUserID(Context context) {
            private static String uniqueID = null;
        private static final String PREF_UNIQUE_ID = "PREF_UNIQUE_ID";
    if (uniqueID == null) {
        SharedPreferences sharedPrefs = context.getSharedPreferences(
                MyBackupAgent.PREFS, Context.MODE_PRIVATE);
        uniqueID = sharedPrefs.getString(PREF_UNIQUE_ID, null);
        if (uniqueID == null) {
            uniqueID = UUID.randomUUID().toString();
            Editor editor = sharedPrefs.edit();
            editor.putString(PREF_UNIQUE_ID, uniqueID);
            editor.commit();

            //backup the changes
            BackupManager mBackupManager = new BackupManager(context);
            mBackupManager.dataChanged();
        }
    }

    return uniqueID;
}

此 User_ID 现在将在各个安装中保持不变,即使用户移动设备也是如此。

有关此方法的详细信息,请参阅 雷托的谈话

有关如何实现备份代理的完整详细信息,请参阅数据备份。我特别推荐底部有关测试的部分,因为备份不会立即发生,因此要测试您必须强制备份。

At Google I/O Reto Meier released a robust answer to how to approach this which should meet most developers needs to track users across installations. Anthony Nolan shows the direction in his answer, but I thought I'd write out the full approach so that others can easily see how to do it (it took me a while to figure out the details).

This approach will give you an anonymous, secure user ID which will be persistent for the user across different devices (based on the primary Google account) and across installs. The basic approach is to generate a random user ID and to store this in the apps' shared preferences. You then use Google's backup agent to store the shared preferences linked to the Google account in the cloud.

Let's go through the full approach. First, we need to create a backup for our SharedPreferences using the Android Backup Service. Start by registering your app via http://developer.android.com/google/backup/signup.html.

Google will give you a backup service key which you need to add to the manifest. You also need to tell the application to use the BackupAgent as follows:

<application android:label="MyApplication"
         android:backupAgent="MyBackupAgent">
    ...
    <meta-data android:name="com.google.android.backup.api_key"
        android:value="your_backup_service_key" />
</application>

Then you need to create the backup agent and tell it to use the helper agent for sharedpreferences:

public class MyBackupAgent extends BackupAgentHelper {
    // The name of the SharedPreferences file
    static final String PREFS = "user_preferences";

    // A key to uniquely identify the set of backup data
    static final String PREFS_BACKUP_KEY = "prefs";

    // Allocate a helper and add it to the backup agent
    @Override
    public void onCreate() {
        SharedPreferencesBackupHelper helper = new SharedPreferencesBackupHelper(this,          PREFS);
        addHelper(PREFS_BACKUP_KEY, helper);
    }
}

To complete the backup you need to create an instance of BackupManager in your main Activity:

BackupManager backupManager = new BackupManager(context);

Finally create a user ID, if it doesn't already exist, and store it in the SharedPreferences:

  public static String getUserID(Context context) {
            private static String uniqueID = null;
        private static final String PREF_UNIQUE_ID = "PREF_UNIQUE_ID";
    if (uniqueID == null) {
        SharedPreferences sharedPrefs = context.getSharedPreferences(
                MyBackupAgent.PREFS, Context.MODE_PRIVATE);
        uniqueID = sharedPrefs.getString(PREF_UNIQUE_ID, null);
        if (uniqueID == null) {
            uniqueID = UUID.randomUUID().toString();
            Editor editor = sharedPrefs.edit();
            editor.putString(PREF_UNIQUE_ID, uniqueID);
            editor.commit();

            //backup the changes
            BackupManager mBackupManager = new BackupManager(context);
            mBackupManager.dataChanged();
        }
    }

    return uniqueID;
}

This User_ID will now be persistent across installations, even if the user moves device.

For more information on this approach see Reto's talk.

And for full details of how to implement the backup agent see Data Backup. I particularly recommend the section at the bottom on testing as the backup does not happen instantaneously and so to test you have to force the backup.

挽清梦 2024-09-08 00:01:56

我认为这是为唯一 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

大多数 Build 成员都是字符串,我们在这里所做的是获取它们的长度并通过数字模进行转换。我们有 13 个这样的数字,并且在前面添加了两个数字 (35),以使其 ID 大小与 IMEI(15 位数字)相同。这里还有其他可能性,只需看看这些字符串即可。
返回类似 355715565309247 的内容。不需要特殊许可,使得这种方法非常方便。


(额外信息:上面给出的技术是从 Pocket Magic 上的一篇文章复制的。)

I think this is sure fire way of building a skeleton for a unique ID... check it out.

Pseudo-Unique ID, that works on all Android devices
Some devices don't have a phone (eg. Tablets) or for some reason, you don't want to include the READ_PHONE_STATE permission. You can still read details like ROM Version, Manufacturer name, CPU type, and other hardware details, that will be well suited if you want to use the ID for a serial key check, or other general purposes. The ID computed in this way won't be unique: it is possible to find two devices with the same ID (based on the same hardware and ROM image) but the changes in real-world applications are negligible. For this purpose you can use the Build class:

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

Most of the Build members are strings, what we're doing here is to take their length and transform it via modulo in a digit. We have 13 such digits and we are adding two more in front (35) to have the same size ID as the IMEI (15 digits). There are other possibilities here are well, just have a look at these strings.
Returns something like 355715565309247. No special permission is required, making this approach very convenient.


(Extra info: The technique given above was copied from an article on Pocket Magic.)

绮烟 2024-09-08 00:01:56

以下代码使用隐藏的 Android API 返回设备序列号。但是,此代码不适用于 Samsung 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) {

}

The following code returns the device serial number using a hidden Android API. But, this code don't works on Samsung Galaxy Tab because "ro.serialno" isn't set on this device.

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) {

}
蓝梦月影 2024-09-08 00:01:56

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

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

Using the code below, you can get the unique device ID of an Android OS device as a string.

deviceId = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID); 
山人契 2024-09-08 00:01:56

我要补充的一件事是——我遇到了其中一种独特的情况。

使用:

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

事实证明,即使我的 Viewsonic G Tablet 报告的 DeviceID 不为 Null,但每台 G Tablet 都报告相同的编号。

玩“Pocket Empires”会很有趣,它可以让您根据“唯一”DeviceID 即时访问某人的帐户。

我的设备没有手机无线电。

One thing I'll add - I have one of those unique situations.

Using:

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

Turns out that even though my Viewsonic G Tablet reports a DeviceID that is not Null, every single G Tablet reports the same number.

Makes it interesting playing "Pocket Empires" which gives you instant access to someone's account based on the "unique" DeviceID.

My device does not have a cell radio.

墨落成白 2024-09-08 00:01:56

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

我不知道 API 级别 >= 9 的所有设备是否真正支持它(=not null)。

A Serial field was added to the Build class in API level 9 (Android 2.3 - Gingerbread). Documentation says it represents the hardware serial number. Thus it should be unique, if it exists on the device.

I don't know whether it is actually supported (=not null) by all devices with API level >= 9 though.

三生一梦 2024-09-08 00:01:56

有关如何获取安装应用程序的每个 Android 设备的唯一标识符的详细说明,请参阅官方 Android 开发人员博客文章 识别应用程序安装

似乎最好的方法是您在安装时自己生成一个,然后在应用程序重新启动时读取它。

我个人认为这是可以接受的,但并不理想。 Android 提供的任何标识符都不适用于所有情况,因为大多数标识符取决于手机的无线电状态(Wi-Fi 开/关、蜂窝网络开/关、蓝牙开/关)。其他的,例如 Settings.Secure.ANDROID_ID 必须由制造商实现,并且不保证是唯一的。

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

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

For detailed instructions on how to get a unique identifier for each Android device your application is installed from, see the official Android Developers Blog posting Identifying App Installations.

It seems the best way is for you to generate one yourself upon installation and subsequently read it when the application is re-launched.

I personally find this acceptable but not ideal. No one identifier provided by Android works in all instances as most are dependent on the phone's radio states (Wi-Fi on/off, cellular on/off, Bluetooth on/off). The others, like Settings.Secure.ANDROID_ID must be implemented by the manufacturer and are not guaranteed to be unique.

The following is an example of writing data to an installation file that would be stored along with any other data the application saves locally.

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();
    }
}
不一样的天空 2024-09-08 00:01:56

在类文件中添加以下代码:

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" />

Add Below code in class file:

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

Add in AndroidManifest.xml:

<uses-permission android:name="android.permission.READ_PHONE_STATE" />
眼泪都笑了 2024-09-08 00:01:56

有很多不同的方法可以解决这些 ANDROID_ID 问题(有时可能为 null 或者特定型号的设备总是返回相同的 ID),各有利弊

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

我自己更喜欢使用现有的 OpenUDID 实现(请参阅https://github.com/ylechelle/OpenUDID)适用于 Android(请参阅 https://github.com/vieux/OpenUDID)。它很容易集成并利用 ANDROID_ID 以及针对上述问题的后备方案。

There are a lot of different approaches to work around those ANDROID_ID issues (may be null sometimes or devices of a specific model always return the same ID) with pros and cons:

  • Implementing a custom ID generation algorithm (based on device properties that are supposed to be static and won't change -> who knows)
  • Abusing other IDs like IMEI, serial number, Wi-Fi/Bluetooth-MAC address (they won't exist on all devices or additional permissions become necessary)

I myself prefer using an existing OpenUDID implementation (see https://github.com/ylechelle/OpenUDID) for Android (see https://github.com/vieux/OpenUDID). It is easy to integrate and makes use of the ANDROID_ID with fallbacks for those issues mentioned above.

偷得浮生 2024-09-08 00:01:56

我的两分钱 - 注意,这是针对设备(错误)唯一 ID - 不是 Android 开发者博客

值得注意的是,@emmby 提供的解决方案会回退到每个应用程序 ID,因为 SharedPreferences 未跨进程同步(请参阅此处此处)。所以我完全避免了这种情况。

相反,我将获取(设备)ID 的各种策略封装在枚举中 - 更改枚举常量的顺序会影响获取 ID 的各种方式的优先级。返回第一个非 null ID 或引发异常(按照不赋予 null 含义的良好 Java 实践)。例如,我首先有 TELEPHONY - 但一个好的默认选择是 ANDROID_ID
测试版:

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);
        }
    }
}

My two cents - NB this is for a device (err) unique ID - not the installation one as discussed in the Android developers's blog.

Of note that the solution provided by @emmby falls back in a per application ID as the SharedPreferences are not synchronized across processes (see here and here). So I avoided this altogether.

Instead, I encapsulated the various strategies for getting a (device) ID in an enum - changing the order of the enum constants affects the priority of the various ways of getting the ID. The first non-null ID is returned or an exception is thrown (as per good Java practices of not giving null a meaning). So for instance I have the TELEPHONY one first - but a good default choice would be the 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);
        }
    }
}
感情废物 2024-09-08 00:01:56

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

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

3 个 ID 是:
伪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 "";
}

There are 30+ answers here and some are same and some are unique. This answer is based on few of those answers. One of them being @Lenn Dolling's answer.

It combines 3 IDs and creates a 32-digit hex string. It has worked very well for me.

3 IDs are:
Pseudo-ID - It is generated based on physical device specifications
ANDROID_ID - Settings.Secure.ANDROID_ID
Bluetooth Address - Bluetooth adapter address

It will return something like this: 551F27C060712A72730B0A0F734064B1

Note: You can always add more IDs to the longId string. For example, Serial #. wifi adapter address. IMEI. This way you are making it more unique per device.

@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 "";
}
墨小沫ゞ 2024-09-08 00:01:56

IMEI 怎么样。这对于 Android 或其他移动设备来说是独一无二的。

How about the IMEI. That is unique for Android or other mobile devices.

椒妓 2024-09-08 00:01:56

Android 操作系统设备的唯一设备 ID 作为字符串,使用 TelephonyManagerANDROID_ID 获取:

String deviceId;
final TelephonyManager mTelephony = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
if (mTelephony.getDeviceId() != null) {
    deviceId = mTelephony.getDeviceId();
}
else {
    deviceId = Secure.getString(
                   getApplicationContext().getContentResolver(),
                   Secure.ANDROID_ID);
}

但我强烈推荐 Google 建议的方法,请参阅< a href="http://android-developers.blogspot.mx/2011/03/identifying-app-installations.html" rel="noreferrer">识别应用程序安装

The unique device ID of an Android OS device as String, using TelephonyManager and ANDROID_ID, is obtained by:

String deviceId;
final TelephonyManager mTelephony = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
if (mTelephony.getDeviceId() != null) {
    deviceId = mTelephony.getDeviceId();
}
else {
    deviceId = Secure.getString(
                   getApplicationContext().getContentResolver(),
                   Secure.ANDROID_ID);
}

But I strongly recommend a method suggested by Google, see Identifying App Installations.

挖个坑埋了你 2024-09-08 00:01:56

以下是我生成唯一 id 的方法:

public static String getDeviceId(Context ctx)
{
    TelephonyManager tm = (TelephonyManager) ctx.getSystemService(Context.TELEPHONY_SERVICE);

    String tmDevice = tm.getDeviceId();
    String androidId = Secure.getString(ctx.getContentResolver(), Secure.ANDROID_ID);
    String serial = null;
    if(Build.VERSION.SDK_INT > Build.VERSION_CODES.FROYO) serial = Build.SERIAL;

    if(tmDevice != null) return "01" + tmDevice;
    if(androidId != null) return "02" + androidId;
    if(serial != null) return "03" + serial;
    // other alternatives (i.e. Wi-Fi MAC, Bluetooth MAC, etc.)

    return null;
}

Here is how I am generating the unique id:

public static String getDeviceId(Context ctx)
{
    TelephonyManager tm = (TelephonyManager) ctx.getSystemService(Context.TELEPHONY_SERVICE);

    String tmDevice = tm.getDeviceId();
    String androidId = Secure.getString(ctx.getContentResolver(), Secure.ANDROID_ID);
    String serial = null;
    if(Build.VERSION.SDK_INT > Build.VERSION_CODES.FROYO) serial = Build.SERIAL;

    if(tmDevice != null) return "01" + tmDevice;
    if(androidId != null) return "02" + androidId;
    if(serial != null) return "03" + serial;
    // other alternatives (i.e. Wi-Fi MAC, Bluetooth MAC, etc.)

    return null;
}
信愁 2024-09-08 00:01:56

另一种方法是在应用程序中使用 /sys/class/android_usb/android0/iSerial 而无需任何权限。

user@creep:~$ adb shell ls -l /sys/class/android_usb/android0/iSerial
-rw-r--r-- root     root         4096 2013-01-10 21:08 iSerial
user@creep:~$ adb shell cat /sys/class/android_usb/android0/iSerial
0A3CXXXXXXXXXX5

要在 Java 中执行此操作,只需使用 FileInputStream 打开 iSerial 文件并读出字符。请确保将其包装在异常处理程序中,因为并非所有设备都有此文件。

至少已知以下设备具有此文件:

  • Galaxy Nexus
  • Nexus S
  • Motorola Xoom 3G
  • Toshiba AT300
  • HTC One V
  • Mini MK802
  • Samsung Galaxy S II

您还可以查看我的博客文章 将 Android 硬件序列号泄露给非特权应用,我在其中讨论了什么其他文件可供参考。

Another way is to use /sys/class/android_usb/android0/iSerial in an app without any permissions whatsoever.

user@creep:~$ adb shell ls -l /sys/class/android_usb/android0/iSerial
-rw-r--r-- root     root         4096 2013-01-10 21:08 iSerial
user@creep:~$ adb shell cat /sys/class/android_usb/android0/iSerial
0A3CXXXXXXXXXX5

To do this in Java one would just use a FileInputStream to open the iSerial file and read out the characters. Just be sure you wrap it in an exception handler, because not all devices have this file.

At least the following devices are known to have this file world-readable:

  • Galaxy Nexus
  • Nexus S
  • Motorola Xoom 3G
  • Toshiba AT300
  • HTC One V
  • Mini MK802
  • Samsung Galaxy S II

You can also see my blog post Leaking Android hardware serial number to unprivileged apps where I discuss what other files are available for information.

眼中杀气 2024-09-08 00:01:56

对于特定 Android 设备的硬件识别,您可以检查 MAC 地址。

你可以这样做:

在 AndroidManifest.xml

现在在你的代码中:

List<NetworkInterface> interfacesList = Collections.list(NetworkInterface.getNetworkInterfaces());

for (NetworkInterface interface : interfacesList) {
   // This will give you the interface MAC ADDRESS
   interface.getHardwareAddress();
}

在每个 Android 设备中,它们至少是“wlan0”接口是 WI-FI 芯片。
即使 WI-FI 未打开,此代码也能工作。

聚苯乙烯
您可以从包含 MACS 的列表中获得许多其他接口,但这可能会在手机之间发生变化。

For hardware recognition of a specific Android device you could check the MAC Addresses.

you can do it that way:

in AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET" />

now in your code:

List<NetworkInterface> interfacesList = Collections.list(NetworkInterface.getNetworkInterfaces());

for (NetworkInterface interface : interfacesList) {
   // This will give you the interface MAC ADDRESS
   interface.getHardwareAddress();
}

In every Android device their is at least a "wlan0" Interface witch is the WI-FI chip.
This code works even when WI-FI is not turned on.

P.S.
Their are a bunch of other Interfaces you will get from the list containing MACS But this can change between phones.

獨角戲 2024-09-08 00:01:56

当设备不具备电话功能时,我使用以下代码获取 IMEI 或使用 Secure.ANDROID_ID 作为替代方案:

String identifier = null;
TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE));
if (tm != null)
      identifier = tm.getDeviceId();
if (identifier == null || identifier .length() == 0)
      identifier = Secure.getString(activity.getContentResolver(),Secure.ANDROID_ID);

I use the following code to get the IMEI or use Secure.ANDROID_ID as an alternative, when the device doesn't have phone capabilities:

String identifier = null;
TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE));
if (tm != null)
      identifier = tm.getDeviceId();
if (identifier == null || identifier .length() == 0)
      identifier = Secure.getString(activity.getContentResolver(),Secure.ANDROID_ID);
似狗非友 2024-09-08 00:01:56

TelephonyManger.getDeviceId() 返回唯一的设备 ID,例如 GSM 的 IMEI 和 CDMA 手机的 MEID 或 ESN。

final TelephonyManager mTelephony = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);            
String myAndroidDeviceId = mTelephony.getDeviceId(); 

但我建议使用:

Settings.Secure.ANDROID_ID,它将 Android ID 作为唯一的 64 位十六进制字符串返回。

    String   myAndroidDeviceId = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID); 

有时 TelephonyManger.getDeviceId() 将返回 null,因此为了确保唯一的 id,您将使用此方法:

public String getUniqueID(){    
    String myAndroidDeviceId = "";
    TelephonyManager mTelephony = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
    if (mTelephony.getDeviceId() != null){
        myAndroidDeviceId = mTelephony.getDeviceId(); 
    }else{
         myAndroidDeviceId = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID); 
    }
    return myAndroidDeviceId;
}

TelephonyManger.getDeviceId() Returns the unique device ID, for example, the IMEI for GSM and the MEID or ESN for CDMA phones.

final TelephonyManager mTelephony = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);            
String myAndroidDeviceId = mTelephony.getDeviceId(); 

But i recommend to use:

Settings.Secure.ANDROID_ID that returns the Android ID as an unique 64-bit hex string.

    String   myAndroidDeviceId = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID); 

Sometimes TelephonyManger.getDeviceId() will return null, so to assure an unique id you will use this method:

public String getUniqueID(){    
    String myAndroidDeviceId = "";
    TelephonyManager mTelephony = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
    if (mTelephony.getDeviceId() != null){
        myAndroidDeviceId = mTelephony.getDeviceId(); 
    }else{
         myAndroidDeviceId = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID); 
    }
    return myAndroidDeviceId;
}
旧时模样 2024-09-08 00:01:56

Google 实例 ID

在 I/O 2015 上发布;在 Android 上需要播放服务 7.5。

https://developers.google.com/instance-id/
https://developers.google.com/instance-id/guides/android-implementation

InstanceID iid = InstanceID.getInstance( context );   // Google docs are wrong - this requires context
String id = iid.getId();  // blocking call

Google 似乎打算使用此 ID 来识别 Android、Chrome 和 iOS 上的安装。

它标识的是安装而不是设备,但话又说回来,ANDROID_ID(这是公认的答案)现在也不再标识设备。使用 ARC 运行时,每次安装都会生成新的 ANDROID_ID (详细信息此处),就像这个新实例 ID 一样。另外,我认为识别安装(而不是设备)是我们大多数人实际上正在寻找的。

实例 ID 的优点

在我看来,Google 打算将其用于此目的(识别您的安装),它是跨平台的,并且可用于许多其他目的(请参阅上面的链接)。

如果您使用 GCM,那么您最终将需要使用此实例 ID,因为您需要它才能获取 GCM 令牌(取代旧的 GCM 注册 ID)。

缺点/问题

在当前实现 (GPS 7.5) 中,当您的应用请求实例 ID 时,会从服务器检索实例 ID。这意味着上面的调用是一个阻塞调用 - 在我不科学的测试中,如果设备在线,则需要 1-3 秒,如果离线则需要 0.5 - 1.0 秒(大概这是它在放弃并生成一个之前等待的时间)随机 ID)。这是在北美使用 Android 5.1.1 和 GPS 7.5 的 Nexus 5 进行测试的。

如果您将 ID 用于他们预期的目的 - 例如。应用程序身份验证、应用程序识别、GCM - 我认为这 1-3 秒可能会很麻烦(当然取决于您的应用程序)。

Google Instance ID

Released at I/O 2015; on Android requires play services 7.5.

https://developers.google.com/instance-id/
https://developers.google.com/instance-id/guides/android-implementation

InstanceID iid = InstanceID.getInstance( context );   // Google docs are wrong - this requires context
String id = iid.getId();  // blocking call

It seems that Google intends for this ID to be used to identify installations across Android, Chrome, and iOS.

It identifies an installation rather then a device, but then again, ANDROID_ID (which is the accepted answer) now no longer identifies devices either. With the ARC runtime a new ANDROID_ID is generated for every installation (details here), just like this new instance ID. Also, I think that identifying installations (not devices) is what most of us are actually looking for.

The advantages of instance ID

It appears to me that Google intends for it to be used for this purpose (identifying your installations), it is cross-platform, and can be used for a number of other purposes (see the links above).

If you use GCM, then you will eventually need to use this instance ID because you need it in order to get the GCM token (which replaces the old GCM registration ID).

The disadvantages/issues

In the current implementation (GPS 7.5) the instance ID is retrieved from a server when your app requests it. This means that the call above is a blocking call - in my unscientific testing it takes 1-3 seconds if the device is online, and 0.5 - 1.0 seconds if off-line (presumably this is how long it waits before giving up and generating a random ID). This was tested in North America on Nexus 5 with Android 5.1.1 and GPS 7.5.

If you use the ID for the purposes they intend - eg. app authentication, app identification, GCM - I think this 1-3 seconds could be a nuisance (depending on your app, of course).

寒尘 2024-09-08 00:01:56

借助以下函数获取设备设备 UUID、带有品牌名称的型号及其版本号

在 Android 10 中完美运行,无需允许读取手机状态权限。

代码片段:

private void fetchDeviceInfo() {
    String uniquePseudoID = "35" +
            Build.BOARD.length() % 10 +
            Build.BRAND.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 serial = Build.getRadioVersion();
    String uuid=new UUID(uniquePseudoID.hashCode(), serial.hashCode()).toString();
    String brand=Build.BRAND;
    String modelno=Build.MODEL;
    String version=Build.VERSION.RELEASE;
    Log.e(TAG, "fetchDeviceInfo: \n "+
            "\n uuid is : "+uuid+
            "\n brand is: "+brand+
            "\n model is: "+modelno+
            "\n version is: "+version);
}

调用上述函数并检查上述代码的输出。请在 android studio 中查看您的日志猫。它如下所示:

在此处输入图像描述

Get Device UUID, model number with brand name and its version number with the help of below function.

Work in Android 10 perfectly and no need to allow read phone state permission.

Code Snippets:

private void fetchDeviceInfo() {
    String uniquePseudoID = "35" +
            Build.BOARD.length() % 10 +
            Build.BRAND.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 serial = Build.getRadioVersion();
    String uuid=new UUID(uniquePseudoID.hashCode(), serial.hashCode()).toString();
    String brand=Build.BRAND;
    String modelno=Build.MODEL;
    String version=Build.VERSION.RELEASE;
    Log.e(TAG, "fetchDeviceInfo: \n "+
            "\n uuid is : "+uuid+
            "\n brand is: "+brand+
            "\n model is: "+modelno+
            "\n version is: "+version);
}

Call Above function and to check output of above code. please see your log cat in android studio. It look likes below:

enter image description here

疯到世界奔溃 2024-09-08 00:01:56

Google 现在拥有广告 ID
这也可以使用,但要注意:

广告ID是特定于用户的、唯一的、可重置的ID

ID

使用户能够重置其标识符或选择退出 Google Play 应用中基于兴趣的广告。

因此,尽管这个 id 可能会改变,但似乎很快就会 我们可能别无选择,具体取决于此 ID 的用途。

更多信息@develper.android

在此处复制粘贴代码

HTH

Google now has an Advertising ID.
This can also be used, but note that :

The advertising ID is a user-specific, unique, resettable ID

and

enables users to reset their identifier or opt out of interest-based ads within Google Play apps.

So though this id may change, it seems that soon we may not have a choice, depends on the purpose of this id.

More info @ develper.android

Copy-paste code here

HTH

阿楠 2024-09-08 00:01:55

Settings.Secure#ANDROID_ID每个用户唯一的 Android ID 返回< /a> 64 位十六进制字符串。

import android.provider.Settings.Secure;

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

另请阅读唯一标识符的最佳实践https:// developer.android.com/training/articles/user-data-ids

Settings.Secure#ANDROID_ID returns the Android ID as an unique for each user 64-bit hex string.

import android.provider.Settings.Secure;

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

Also read Best practices for unique identifiers: https://developer.android.com/training/articles/user-data-ids

浅浅淡淡 2024-09-08 00:01:55

更新:从最新版本的 Android 开始,ANDROID_ID 的许多问题已得到解决,我相信不再需要这种方法。请查看安东尼的回答

完全披露:我的应用程序最初使用以下方法,但不再使用此方法,我们现在使用 Android 开发者博客 条目,emmby 的回答 链接到(即,生成并保存 UUID#randomUUID())。


这个问题有很多答案,其中大多数只能在“某些”时间起作用,不幸的是,这还不够好。

根据我对设备的测试(所有手机,至少其中一部未激活):

  1. 所有测试的设备均返回 TelephonyManager.getDeviceId() 的值
  2. 所有 GSM 设备(均使用 SIM 卡进行测试)均返回TelephonyManager.getSimSerialNumber() 的值
  3. 所有 CDMA 设备都为 getSimSerialNumber() 返回 null(如预期)
  4. 所有添加了 Google 帐户的设备都返回 ANDROID_ID 的值
  5. 所有 CDMA 设备都为 ANDROID_IDTelephonyManager.getDeviceId() 返回相同的值(或相同值的派生值) - 只要 在设置过程中已添加 Google 帐户。
  6. 我还没有机会测试没有 SIM 卡的 GSM 设备、没有添加 Google 帐户的 GSM 设备或任何处于飞行模式的设备。

因此,如果您想要设备本身独有的东西,TM.getDeviceId()应该就足够了。显然,某些用户比其他用户更加偏执,因此对这些标识符中的 1 个或多个进行哈希处理可能会很有用,这样该字符串实际上对于设备来说仍然是唯一的,但不会明确标识用户的实际设备。例如,使用 String.hashCode() 与 UUID: 结合使用

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

可能会产生如下结果: 00000000-54b3-e7c7-0000-000046bffd97

它对我来说足够好了。

正如 Richard 在下面提到的,不要忘记您需要读取 TelephonyManager 属性的权限,因此将其添加到您的清单中:

<uses-permission android:name="android.permission.READ_PHONE_STATE" />

导入库

import android.content.Context;
import android.telephony.TelephonyManager;
import android.view.View;

UPDATE: As of recent versions of Android, many of the issues with ANDROID_ID have been resolved, and I believe this approach is no longer necessary. Please take a look at Anthony's answer.

Full disclosure: my app used the below approach originally but no longer uses this approach, and we now use the approach outlined in the Android Developer Blog entry that emmby's answer links to (namely, generating and saving a UUID#randomUUID()).


There are many answers to this question, most of which will only work "some" of the time, and unfortunately, that's not good enough.

Based on my tests of devices (all phones, at least one of which is not activated):

  1. All devices tested returned a value for TelephonyManager.getDeviceId()
  2. All GSM devices (all tested with a SIM) returned a value for TelephonyManager.getSimSerialNumber()
  3. All CDMA devices returned null for getSimSerialNumber() (as expected)
  4. All devices with a Google account added returned a value for ANDROID_ID
  5. All CDMA devices returned the same value (or derivation of the same value) for both ANDROID_ID and TelephonyManager.getDeviceId() -- as long as a Google account has been added during setup.
  6. I did not yet have a chance to test GSM devices with no SIM, a GSM device with no Google account added, or any of the devices in airplane mode.

So if you want something unique to the device itself, TM.getDeviceId() should be sufficient. Obviously, some users are more paranoid than others, so it might be useful to hash 1 or more of these identifiers, so that the string is still virtually unique to the device, but does not explicitly identify the user's actual device. For example, using String.hashCode(), combined with a UUID:

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

might result in something like: 00000000-54b3-e7c7-0000-000046bffd97

It works well enough for me.

As Richard mentions below, don't forget that you need permission to read the TelephonyManager properties, so add this to your manifest:

<uses-permission android:name="android.permission.READ_PHONE_STATE" />

import libraries

import android.content.Context;
import android.telephony.TelephonyManager;
import android.view.View;
失去的东西太少 2024-09-08 00:01:55

#最后更新时间:2015 年 6 月 2 日


在阅读了所有关于创建唯一 ID 的 Stack Overflow 帖子、Google 开发者博客和 Android 文档后,我觉得“伪 ID”是最好的选择。

主要问题:硬件与软件

硬件

  • 用户可以更改他们的硬件、Android 平板电脑或手机,因此基于硬件的唯一 ID 对于
  • 跟踪用户来说并不是一个好主意。这是一个好主意

软件

  • 用户如果获得 root 权限,可以擦除/更改他们的 ROM
  • 您可以跨平台(iOS、Android、Windows 和 Web)跟踪用户
  • 最想要跟踪个人用户的用户同意只是让他们登录(使用 OAuth 实现无缝)

#Android 的总体细分

###- 保证 API 的唯一性(包括已 root 的设备)>= 9/10(99.5% 的 Android)设备)
###- 没有额外的权限

伪代码:

if API >= 9/10: (99.5% of devices)

return unique ID containing serial id (rooted devices may be different)

else

return the unique ID of build information (may overlap data - API < 9)

感谢@stansult 发布我们的所有选项(在这个 Stack Overflow 问题中)。

##选项列表 - 为什么/为什么不使用它们的原因:

  • 用户电子邮件 - 软件

  • 用户可以更改电子邮件 - 极不可能

  • API 5+

  • API 14+ (如何获取 Android 设备的主电子邮件地址)

  • 用户电话号码 - 软件

  • 用户可以更改电话号码 - 极不可能

  • IMEI - 硬件(仅限手机,需要 android.permission.READ_PHONE_STATE

  • 大多数用户讨厌它说“电话”在许可中。一些用户给出差评是因为他们认为您只是在窃取他们的个人信息,而您真正想做的只是跟踪设备安装。很明显您正在收集数据。

  • Android ID - 硬件(可以为空,可以在恢复出厂设置时更改) ,可以在 root 设备上更改)

  • 由于它可以为“null”,因此我们可以检查“null”并更改它的价值,但这意味着它不再是唯一的。

  • 如果您的用户拥有恢复出厂设置的设备,则该值可能已在已取得 root 权限的设备上更改或更改,因此如果您正在跟踪用户安装,则可能会出现重复条目​​。

  • WLAN MAC 地址 - 硬件(需要 android.permission.ACCESS_WIFI_STATE

  • 这可能是第二好的选择,但您仍然收集并存储直接来自用户的唯一标识符。很明显您正在收集数据。

  • 蓝牙 MAC 地址 - 硬件(具有蓝牙功能的设备,需要 android.permission.BLUETOOTH

  • 市面上大多数应用程序不使用蓝牙,因此,如果您的应用程序不使用蓝牙,而您将其包含在内,则用户可能会产生怀疑。

  • 伪唯一 ID - 软件(适用于所有 Android 设备)

  • 非常有可能,可能包含冲突 - 请参阅下面发布的我的方法!

  • 这使您可以从用户那里获得“几乎唯一”的 ID,而无需获取任何私人信息。您可以根据设备信息创建您自己的匿名 ID。


我知道没有任何“完美”的方法可以在不使用权限的情况下获得唯一的 ID;然而,有时我们只需要跟踪设备安装。在创建唯一 ID 时,我们可以仅根据 Android API 提供给我们的信息创建“伪唯一 ID”,而无需使用额外的权限。这样,我们可以表达对用户的尊重,并尽力提供良好的用户体验。

使用伪唯一 ID,您实际上只会遇到这样一个事实:由于存在相似的设备,因此可能存在重复项。您可以调整组合方法以使其更加独特;然而,一些开发人员需要跟踪设备安装,这将根据类似设备实现技巧或性能。

##API >= 9:

如果他们的 Android 设备是 API 9 或更高版本,则由于“Build.SERIAL”字段,这保证是唯一的。

记住,从技术上讲,您只错过了大约 0.5% 的用户谁拥有 API < 9.。所以您可以专注于其余的:这是 99.5% 的用户!

##API < 9:

如果用户的Android设备低于API 9;希望他们没有恢复出厂设置,并且他们的“Secure.ANDROID_ID”将被保留或不为“null”。 (请参阅http://developer.android.com/about/dashboards/index.html)

##如果所有其他方法都失败:

如果所有其他方法都失败,如果用户的 API 低于 9(低于 Gingerbread),已重置其设备,或者“Secure.ANDROID_ID”返回“null”,则只需 ID返回将仅基于其 Android 设备信息。这就是可能发生碰撞的地方。

更改:

  • 删除了“Android.SECURE_ID”,因为恢复出厂设置可能会导致值发生变化
  • 编辑了 API 上的代码以进行更改
  • 更改了 Pseudo

请查看以下方法:

/**
 * Return pseudo unique ID
 * @return ID
 */
public static String getUniquePsuedoID() {
    // If all else fails, if the user does have lower than API 9 (lower
    // than Gingerbread), has reset their device or 'Secure.ANDROID_ID'
    // returns 'null', then simply the ID returned will be solely based
    // off their Android device information. This is where the collisions
    // can happen.
    // Thanks http://www.pocketmagic.net/?p=1662!
    // Try not to use DISPLAY, HOST or ID - these items could change.
    // If there are collisions, there will be overlapping data
    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);

    // Thanks to @Roman SL!
    // https://stackoverflow.com/a/4789483/950427
    // Only devices with API >= 9 have android.os.Build.SERIAL
    // http://developer.android.com/reference/android/os/Build.html#SERIAL
    // If a user upgrades software or roots their device, there will be a duplicate entry
    String serial = null;
    try {
        serial = android.os.Build.class.getField("SERIAL").get(null).toString();

        // Go ahead and return the serial for api => 9
        return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
    } catch (Exception exception) {
        // String needs to be initialized
        serial = "serial"; // some value
    }

    // Thanks @Joe!
    // https://stackoverflow.com/a/2853253/950427
    // Finally, combine the values we have found by using the UUID class to create a unique identifier
    return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
}

#New(适用于带有广告和 Google Play 服务的应用程序):

< strong>从 Google Play 开发者控制台:

自 2014 年 8 月 1 日起,Google Play 开发者计划政策
需要上传和更新全新的应用程序才能使用广告 ID
代替用于任何广告目的的任何其他持久标识符。
了解更多

实现

权限:

<uses-permission android:name="android.permission.INTERNET" />

代码:

import com.google.android.gms.ads.identifier.AdvertisingIdClient;
import com.google.android.gms.ads.identifier.AdvertisingIdClient.Info;
import com.google.android.gms.common.GooglePlayServicesAvailabilityException;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import java.io.IOException;
...

// Do not call this function from the main thread. Otherwise, 
// an IllegalStateException will be thrown.
public void getIdThread() {

  Info adInfo = null;
  try {
    adInfo = AdvertisingIdClient.getAdvertisingIdInfo(mContext);

  } catch (IOException exception) {
    // Unrecoverable error connecting to Google Play services (e.g.,
    // the old version of the service doesn't support getting AdvertisingId).
 
  } catch (GooglePlayServicesAvailabilityException exception) {
    // Encountered a recoverable error connecting to Google Play services. 

  } catch (GooglePlayServicesNotAvailableException exception) {
    // Google Play services is not available entirely.
  }
  final String id = adInfo.getId();
  final boolean isLAT = adInfo.isLimitAdTrackingEnabled();
}

源代码/文档:

http://developer.android.com/google/play-services/id.html
http://developer.android.com/reference /com/google/android/gms/ads/identifier/AdvertisingIdClient.html

##重要:

广告 ID 的目的是完全取代现有的
使用其他标识符用于广告目的(例如使用 ANDROID_ID
当 Google Play 服务可用时。案例
Google Play 服务不可用的地方由
GooglePlayServicesNotAvailableException 被抛出
getAdvertisingIdInfo()。

##警告,用户可以重置:

http://en.kioskea .net/faq/34732-android-reset-your-advertising-id

我尝试引用我从中获取信息的每个链接。如果有遗漏需要补充的请评论!

Google 播放器服务实例 ID

https://developers.google.com/instance-id/

#Last Updated: 6/2/15


After reading every Stack Overflow post about creating a unique ID, the Google developer blog, and Android documentation, I feel as if the 'Pseudo ID' is the best possible option.

Main Issue: Hardware vs Software

Hardware

  • Users can change their hardware, Android tablet, or phone, so unique IDs based on hardware are not good ideas for TRACKING USERS
  • For TRACKING HARDWARE, this is a great idea

Software

  • Users can wipe/change their ROM if they are rooted
  • You can track users across platforms (iOS, Android, Windows, and Web)
  • The best want to TRACK AN INDIVIDUAL USER with their consent is to simply have them login (make this seamless using OAuth)

#Overall breakdown with Android

###- Guarantee uniqueness (include rooted devices) for API >= 9/10 (99.5% of Android devices)
###- No extra permissions

Psuedo code:

if API >= 9/10: (99.5% of devices)

return unique ID containing serial id (rooted devices may be different)

else

return the unique ID of build information (may overlap data - API < 9)

Thanks to @stansult for posting all of our options (in this Stack Overflow question).

##List of options - reasons why/ why not to use them:

  • User Email - Software

  • User could change email - HIGHLY unlikely

  • API 5+ <uses-permission android:name="android.permission.GET_ACCOUNTS" /> or

  • API 14+ <uses-permission android:name="android.permission.READ_PROFILE" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> (How to get the Android device's primary e-mail address)

  • User Phone Number - Software

  • Users could change phone numbers - HIGHLY unlikely

  • <uses-permission android:name="android.permission.READ_PHONE_STATE" />

  • IMEI - Hardware (only phones, needs android.permission.READ_PHONE_STATE)

  • Most users hate the fact that it says "Phone Calls" in the permission. Some users give bad ratings because they believe you are simply stealing their personal information when all you really want to do is track device installs. It is obvious that you are collecting data.

  • <uses-permission android:name="android.permission.READ_PHONE_STATE" />

  • Android ID - Hardware (can be null, can change upon factory reset, can be altered on a rooted device)

  • Since it can be 'null', we can check for 'null' and change its value, but this means it will no longer be unique.

  • If you have a user with a factory reset device, the value may have changed or altered on the rooted device so there may be duplicates entries if you are tracking user installs.

  • WLAN MAC Address - Hardware (needs android.permission.ACCESS_WIFI_STATE)

  • This could be the second-best option, but you are still collecting and storing a unique identifier that comes directly from a user. This is obvious that you are collecting data.

  • <uses-permission android:name="android.permission.ACCESS_WIFI_STATE "/>

  • Bluetooth MAC Address - Hardware (devices with Bluetooth, needs android.permission.BLUETOOTH)

  • Most applications on the market do not use Bluetooth, and so if your application doesn't use Bluetooth and you are including this, the user could become suspicious.

  • <uses-permission android:name="android.permission.BLUETOOTH "/>

  • Pseudo-Unique ID - Software (for all Android devices)

  • Very possible, may contain collisions - See my method posted below!

  • This allows you to have an 'almost unique' ID from the user without taking anything that is private. You can create your own anonymous ID from device information.


I know there isn't any 'perfect' way of getting a unique ID without using permissions; however, sometimes we only really need to track the device installation. When it comes to creating a unique ID, we can create a 'pseudo unique id' based solely on information that the Android API gives us without using extra permissions. This way, we can show the user respect and try to offer a good user experience as well.

With a pseudo-unique id, you really only run into the fact that there may be duplicates based on the fact that there are similar devices. You can tweak the combined method to make it more unique; however, some developers need to track device installs and this will do the trick or performance based on similar devices.

##API >= 9:

If their Android device is API 9 or over, this is guaranteed to be unique because of the 'Build.SERIAL' field.

REMEMBER, you are technically only missing out on around 0.5% of users who have API < 9. So you can focus on the rest: This is 99.5% of the users!

##API < 9:

If the user's Android device is lower than API 9; hopefully, they have not done a factory reset and their 'Secure.ANDROID_ID' will be preserved or not 'null'. (see http://developer.android.com/about/dashboards/index.html)

##If all else fails:

If all else fails, if the user does have lower than API 9 (lower than Gingerbread), has reset their device, or 'Secure.ANDROID_ID' returns 'null', then simply the ID returned will be solely based on their Android device information. This is where the collisions can happen.

Changes:

  • Removed 'Android.SECURE_ID' because factory resets could cause the value to change
  • Edited the code to change on API
  • Changed the Pseudo

Please take a look at the method below:

/**
 * Return pseudo unique ID
 * @return ID
 */
public static String getUniquePsuedoID() {
    // If all else fails, if the user does have lower than API 9 (lower
    // than Gingerbread), has reset their device or 'Secure.ANDROID_ID'
    // returns 'null', then simply the ID returned will be solely based
    // off their Android device information. This is where the collisions
    // can happen.
    // Thanks http://www.pocketmagic.net/?p=1662!
    // Try not to use DISPLAY, HOST or ID - these items could change.
    // If there are collisions, there will be overlapping data
    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);

    // Thanks to @Roman SL!
    // https://stackoverflow.com/a/4789483/950427
    // Only devices with API >= 9 have android.os.Build.SERIAL
    // http://developer.android.com/reference/android/os/Build.html#SERIAL
    // If a user upgrades software or roots their device, there will be a duplicate entry
    String serial = null;
    try {
        serial = android.os.Build.class.getField("SERIAL").get(null).toString();

        // Go ahead and return the serial for api => 9
        return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
    } catch (Exception exception) {
        // String needs to be initialized
        serial = "serial"; // some value
    }

    // Thanks @Joe!
    // https://stackoverflow.com/a/2853253/950427
    // Finally, combine the values we have found by using the UUID class to create a unique identifier
    return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
}

#New (for apps with ads AND Google Play Services):

From the Google Play Developer's console:

Beginning August 1st, 2014, the Google Play Developer Program Policy
requires all-new app uploads and updates to use the advertising ID in
lieu of any other persistent identifiers for any advertising purposes.
Learn more

Implementation:

Permission:

<uses-permission android:name="android.permission.INTERNET" />

Code:

import com.google.android.gms.ads.identifier.AdvertisingIdClient;
import com.google.android.gms.ads.identifier.AdvertisingIdClient.Info;
import com.google.android.gms.common.GooglePlayServicesAvailabilityException;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import java.io.IOException;
...

// Do not call this function from the main thread. Otherwise, 
// an IllegalStateException will be thrown.
public void getIdThread() {

  Info adInfo = null;
  try {
    adInfo = AdvertisingIdClient.getAdvertisingIdInfo(mContext);

  } catch (IOException exception) {
    // Unrecoverable error connecting to Google Play services (e.g.,
    // the old version of the service doesn't support getting AdvertisingId).
 
  } catch (GooglePlayServicesAvailabilityException exception) {
    // Encountered a recoverable error connecting to Google Play services. 

  } catch (GooglePlayServicesNotAvailableException exception) {
    // Google Play services is not available entirely.
  }
  final String id = adInfo.getId();
  final boolean isLAT = adInfo.isLimitAdTrackingEnabled();
}

Source/Docs:

http://developer.android.com/google/play-services/id.html
http://developer.android.com/reference/com/google/android/gms/ads/identifier/AdvertisingIdClient.html

##Important:

It is intended that the advertising ID completely replace existing
usage of other identifiers for ads purposes (such as the use of ANDROID_ID
in Settings.Secure) when Google Play Services is available. Cases
where Google Play Services is unavailable are indicated by a
GooglePlayServicesNotAvailableException being thrown by
getAdvertisingIdInfo().

##Warning, users can reset:

http://en.kioskea.net/faq/34732-android-reset-your-advertising-id

I have tried to reference every link that I took information from. If you are missing and need to be included, please comment!

Google Player Services InstanceID

https://developers.google.com/instance-id/

丑疤怪 2024-09-08 00:01:55

正如 Dave Webb 提到的,Android 开发者博客有一篇文章 涵盖了这一点。他们首选的解决方案是跟踪应用程序安装而不是设备,这对于大多数用例都适用。这篇博客文章将向您展示实现该功能所需的代码,我建议您查看一下。

但是,如果您需要设备标识符而不是应用程序安装标识符,该博客文章将继续讨论解决方案。我与 Google 的某人进行了交谈,以便在您需要时对一些项目进行一些额外的说明。以下是我在上述博客文章中未提及的有关设备标识符的发现:

  • ANDROID_ID 是首选设备标识符。 ANDROID_ID 在 Android <=2.1 或 >=2.3 版本上完全可靠。只有2.2有帖子中提到的问题。
  • 多个制造商的多个设备受到 2.2 中 ANDROID_ID 错误的影响。
  • 据我所知,所有受影响的设备都具有相同的 ANDROID_ID,即9774d56d682e549c。顺便说一句,这也是模拟器报告的相同设备 ID。
  • Google 相信 OEM 厂商已经为他们的许多或大部分设备修复了这个问题,但我能够证实,至少截至 2011 年 4 月上旬,仍然很容易找到 ANDROID_ID 损坏的设备。

根据 Google 的建议,我实现了一个类,该类将为每个设备生成唯一的 UUID,在适当的情况下使用 ANDROID_ID 作为种子,必要时依靠 TelephonyManager.getDeviceId() ,如果失败,则诉诸随机生成的唯一 UUID该信息在应用程序重新启动后仍然存在(但不影响应用程序重新安装)。

请注意,对于必须回退设备 ID 的设备,唯一 ID在恢复出厂设置后仍然保留。这是需要注意的事情。如果您需要确保恢复出厂设置会重置您的唯一 ID,您可能需要考虑直接回退到随机 UUID,而不是设备 ID。

同样,此代码用于设备 ID,而不是应用程序安装 ID。在大多数情况下,您可能需要的是应用程序安装 ID。但如果您确实需要设备 ID,那么以下代码可能适合您。

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) {
                        // Use the ids previously computed and stored in the
                        // prefs file
                        uuid = UUID.fromString(id);
                    } else {
                        final String androidId = Secure.getString(
                            context.getContentResolver(), Secure.ANDROID_ID);
                        // Use the Android ID unless it's broken, in which case
                        // fallback on deviceId,
                        // unless it's not available, then fallback on a random
                        // number which we store to a prefs file
                        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);
                        }
                        // Write the value out to the prefs file
                        prefs.edit()
                                .putString(PREFS_DEVICE_ID, uuid.toString())
                                .commit();
                    }
                }
            }
        }
    }

    /**
     * Returns a unique UUID for the current android device. As with all UUIDs,
     * this unique ID is "very highly likely" to be unique across all Android
     * devices. Much more so than ANDROID_ID is.
     * 
     * The UUID is generated by using ANDROID_ID as the base key if appropriate,
     * falling back on TelephonyManager.getDeviceID() if ANDROID_ID is known to
     * be incorrect, and finally falling back on a random UUID that's persisted
     * to SharedPreferences if getDeviceID() does not return a usable value.
     * 
     * In some rare circumstances, this ID may change. In particular, if the
     * device is factory reset a new device ID may be generated. In addition, if
     * a user upgrades their phone from certain buggy implementations of Android
     * 2.2 to a newer, non-buggy version of Android, the device ID may change.
     * Or, if a user uninstalls your app on a device that has neither a proper
     * Android ID nor a Device ID, this ID may change on reinstallation.
     * 
     * Note that if the code falls back on using TelephonyManager.getDeviceId(),
     * the resulting ID will NOT change after a factory reset. Something to be
     * aware of.
     * 
     * Works around a bug in Android 2.2 for many devices when using ANDROID_ID
     * directly.
     * 
     * @see http://code.google.com/p/android/issues/detail?id=10603
     * 
     * @return a UUID that may be used to uniquely identify your device for most
     *         purposes.
     */
    public UUID getDeviceUuid() {
        return uuid;
    }
}

As Dave Webb mentions, the Android Developer Blog has an article that covers this. Their preferred solution is to track app installs rather than devices, and that will work well for most use cases. The blog post will show you the necessary code to make that work, and I recommend you check it out.

However, the blog post goes on to discuss solutions if you need a device identifier rather than an app installation identifier. I spoke with someone at Google to get some additional clarification on a few items in the event that you need to do so. Here's what I discovered about device identifiers that's NOT mentioned in the aforementioned blog post:

  • ANDROID_ID is the preferred device identifier. ANDROID_ID is perfectly reliable on versions of Android <=2.1 or >=2.3. Only 2.2 has the problems mentioned in the post.
  • Several devices by several manufacturers are affected by the ANDROID_ID bug in 2.2.
  • As far as I've been able to determine, all affected devices have the same ANDROID_ID, which is 9774d56d682e549c. Which is also the same device id reported by the emulator, btw.
  • Google believes that OEMs have patched the issue for many or most of their devices, but I was able to verify that as of the beginning of April 2011, at least, it's still quite easy to find devices that have the broken ANDROID_ID.

Based on Google's recommendations, I implemented a class that will generate a unique UUID for each device, using ANDROID_ID as the seed where appropriate, falling back on TelephonyManager.getDeviceId() as necessary, and if that fails, resorting to a randomly generated unique UUID that is persisted across app restarts (but not app re-installations).

Note that for devices that have to fallback on the device ID, the unique ID WILL persist across factory resets. This is something to be aware of. If you need to ensure that a factory reset will reset your unique ID, you may want to consider falling back directly to the random UUID instead of the device ID.

Again, this code is for a device ID, not an app installation ID. For most situations, an app installation ID is probably what you're looking for. But if you do need a device ID, then the following code will probably work for you.

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) {
                        // Use the ids previously computed and stored in the
                        // prefs file
                        uuid = UUID.fromString(id);
                    } else {
                        final String androidId = Secure.getString(
                            context.getContentResolver(), Secure.ANDROID_ID);
                        // Use the Android ID unless it's broken, in which case
                        // fallback on deviceId,
                        // unless it's not available, then fallback on a random
                        // number which we store to a prefs file
                        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);
                        }
                        // Write the value out to the prefs file
                        prefs.edit()
                                .putString(PREFS_DEVICE_ID, uuid.toString())
                                .commit();
                    }
                }
            }
        }
    }

    /**
     * Returns a unique UUID for the current android device. As with all UUIDs,
     * this unique ID is "very highly likely" to be unique across all Android
     * devices. Much more so than ANDROID_ID is.
     * 
     * The UUID is generated by using ANDROID_ID as the base key if appropriate,
     * falling back on TelephonyManager.getDeviceID() if ANDROID_ID is known to
     * be incorrect, and finally falling back on a random UUID that's persisted
     * to SharedPreferences if getDeviceID() does not return a usable value.
     * 
     * In some rare circumstances, this ID may change. In particular, if the
     * device is factory reset a new device ID may be generated. In addition, if
     * a user upgrades their phone from certain buggy implementations of Android
     * 2.2 to a newer, non-buggy version of Android, the device ID may change.
     * Or, if a user uninstalls your app on a device that has neither a proper
     * Android ID nor a Device ID, this ID may change on reinstallation.
     * 
     * Note that if the code falls back on using TelephonyManager.getDeviceId(),
     * the resulting ID will NOT change after a factory reset. Something to be
     * aware of.
     * 
     * Works around a bug in Android 2.2 for many devices when using ANDROID_ID
     * directly.
     * 
     * @see http://code.google.com/p/android/issues/detail?id=10603
     * 
     * @return a UUID that may be used to uniquely identify your device for most
     *         purposes.
     */
    public UUID getDeviceUuid() {
        return uuid;
    }
}
梦忆晨望 2024-09-08 00:01:55

以下是 Reto Meier 在今年的 Google I/O 演示中使用的代码,用于获取唯一 ID用户:

private static String uniqueID = null;
private static final String PREF_UNIQUE_ID = "PREF_UNIQUE_ID";

public synchronized static String id(Context context) {
    if (uniqueID == null) {
        SharedPreferences sharedPrefs = context.getSharedPreferences(
                PREF_UNIQUE_ID, Context.MODE_PRIVATE);
        uniqueID = sharedPrefs.getString(PREF_UNIQUE_ID, null);
        if (uniqueID == null) {
            uniqueID = UUID.randomUUID().toString();
            Editor editor = sharedPrefs.edit();
            editor.putString(PREF_UNIQUE_ID, uniqueID);
            editor.commit();
        }
    }
    return uniqueID;
}

如果您将此与备份策略结合起来以将首选项发送到云(Reto 的 talk,您应该有一个与用户关联的 id,并在设备被擦除甚至更换后保留下来。我打算使用这个在未来的分析中(换句话说,我还没有做到这一点:)。

Here is the code that Reto Meier used in the Google I/O presentation this year to get a unique id for the user:

private static String uniqueID = null;
private static final String PREF_UNIQUE_ID = "PREF_UNIQUE_ID";

public synchronized static String id(Context context) {
    if (uniqueID == null) {
        SharedPreferences sharedPrefs = context.getSharedPreferences(
                PREF_UNIQUE_ID, Context.MODE_PRIVATE);
        uniqueID = sharedPrefs.getString(PREF_UNIQUE_ID, null);
        if (uniqueID == null) {
            uniqueID = UUID.randomUUID().toString();
            Editor editor = sharedPrefs.edit();
            editor.putString(PREF_UNIQUE_ID, uniqueID);
            editor.commit();
        }
    }
    return uniqueID;
}

If you couple this with a backup strategy to send preferences to the cloud (also described in Reto's talk, you should have an id that ties to a user and sticks around after the device has been wiped, or even replaced. I plan to use this in analytics going forward (in other words, I have not done that bit yet :).

一杯敬自由 2024-09-08 00:01:55

这是一个简单的问题,没有简单的答案。

此外,这里所有现有的答案要么已经过时,要么不可靠。

因此,如果您正在寻找 2020 年之后的解决方案

以下是需要记住的一些事项:

所有基于硬件的标识符(IMEI、MAC、序列号等)对于非 Google 设备(Pixel 和 Nexus 除外)来说都是不可靠的,统计数据全球大多数 Android 活跃设备。因此,官方 Android 标识符最佳实践明确指出:

避免使用硬件标识符,例如 IMEI、MAC 地址等...

这使得此处的大多数答案无效。另外,由于不同的 Android 安全更新,其中一些需要更新且更严格的运行时权限,而用户可以简单地拒绝这些权限。

例如,CVE-2018-9489 会影响上述所有基于 WIFI 的技术。

这使得这些标识符不仅不可靠,而且在许多情况下也无法访问。

简而言之:不要使用这些技术

这里的许多其他答案都建议使用 AdvertisingIdClient,它也是不兼容的,因为它的设计仅用于广告分析。 官方参考中也有说明

仅将广告 ID 用于用户分析或广告用例

这不仅对于设备识别而言不可靠,而且您还必须遵循 有关广告跟踪的用户隐私政策,该政策明确规定用户可以随时重置或阻止该政策。

所以也不要使用它

因为您无法拥有所需的静态全局唯一且可靠的设备标识符。 Android官方参考建议:

对于除支付欺诈预防和电话之外的所有其他用例,尽可能使用 Firebase 安装 ID (FID) 或私人存储的 GUID

它对于设备上的应用程序安装来说是独一无二的,因此当用户卸载该应用程序时,它就会被清除,因此它不是 100% 可靠,但它是退而求其次的。

注意 从今天开始,FirebaseInstanceId 已弃用,您应该使用 FirebaseInstallations 代替。

要使用 FirebaseInstallations,请添加最新的 firebase-messaging 依赖项 进入你的 gradle

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

并使用下面的代码来获取 firebase ID:

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

如果你需要将设备标识存储在远程服务器上,那么不要按原样存储(纯文本),而是 加盐哈希

如今,这不仅是最佳实践,实际上您还必须根据 GDPR - 标识符 和类似规定。

It's a simple question, with no simple answer.

Moreover, all of the existing answers here are either out of date or unreliable.

So if you're searching for a solution after 2020.

Here are a few things to keep in mind:

All the hardware-based identifiers (IMEI, MAC, Serial Number, etc.) are unreliable for non-google devices (except Pixels and Nexuses), which are statistically most of the android active devices worldwide. Therefore official Android identifiers best practices clearly states:

Avoid using hardware identifiers, such as IMEI, MAC address, etc...

Which makes most of the answers here invalid. Also due to different android security updates, some of them require newer and stricter runtime permissions, which can be simply denied by the user.

For example CVE-2018-9489 affects all the WIFI based techniques mentioned above.

That makes those identifiers not only unreliable but also inaccessible in many cases.

So in simpler words: don't use those techniques.

Many other answers here are suggesting to use the AdvertisingIdClient, which is also incompatible, as it's by design only for ads profiling. It's also stated in the official reference

Only use an Advertising ID for user profiling or ads use cases

It's not only unreliable for device identification, but you also must follow the user privacy regarding ad tracking policy, which states clearly that users can reset or block it at any moment.

So don't use it either.

Since you cannot have the desired static globally unique and reliable device identifier. Android's official reference suggests:

Use a Firebase installation ID (FID) or a privately stored GUID whenever possible for all other use cases, except for payment fraud prevention and telephony.

It's unique for the application installation on the device, so when the user uninstalls the app - it's wiped out, so it's not 100% reliable, but it's the next best thing.

Note As of today the FirebaseInstanceId is deprecated, you should use FirebaseInstallations instead.

To use FirebaseInstallations add the latest firebase-messaging dependency into your gradle

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

And use the code below to get the firebase ID:

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

If you need to store the device identification on your remote server, then don't store it as is (plain text), but a hash with salt.

Today it's not only a best practice, you actually must do it by law according to GDPR - identifiers and similar regulations.

一瞬间的火花 2024-09-08 00:01:55

您还可以考虑 Wi-Fi 适配器的 MAC 地址。像这样检索:

WifiManager wm = (WifiManager)Ctxt.getSystemService(Context.WIFI_SERVICE);
return wm.getConnectionInfo().getMacAddress();

需要清单中的权限android.permission.ACCESS_WIFI_STATE

据报道,即使未连接 Wi-Fi 也可用。如果上面答案中的乔在他的许多设备上尝试一下,那就太好了。

在某些设备上,当 Wi-Fi 关闭时该功能不可用。

注意:从 Android 6.x 开始,它返回一致的假 MAC 地址:02:00:00:00:00:00

Also you might consider the Wi-Fi adapter's MAC address. Retrieved like this:

WifiManager wm = (WifiManager)Ctxt.getSystemService(Context.WIFI_SERVICE);
return wm.getConnectionInfo().getMacAddress();

Requires permission android.permission.ACCESS_WIFI_STATE in the manifest.

Reported to be available even when Wi-Fi is not connected. If Joe from the answer above gives this one a try on his many devices, that'd be nice.

On some devices, it's not available when Wi-Fi is turned off.

NOTE: From Android 6.x, it returns consistent fake mac address: 02:00:00:00:00:00

单调的奢华 2024-09-08 00:01:55

这里有相当有用的信息。

它涵盖五种不同的 ID 类型:

  1. IMEI(仅适用于使用手机的 Android 设备;需要 android.permission.READ_PHONE_STATE
  2. 伪唯一 ID(对于所有 Android 设备)
  3. Android ID(可以为空,可以在恢复出厂设置时更改,可以在 root 后的手机上更改)
  4. WLAN MAC 地址字符串(需要 android. permission.ACCESS_WIFI_STATE)
  5. BT MAC Address 字符串(具有蓝牙功能的设备,需要 android.permission.BLUETOOTH

There’s rather useful info here.

It covers five different ID types:

  1. IMEI (only for Android devices with Phone use; needs android.permission.READ_PHONE_STATE)
  2. Pseudo-Unique ID (for all Android devices)
  3. Android ID (can be null, can change upon factory reset, can be altered on rooted phone)
  4. WLAN MAC Address string (needs android.permission.ACCESS_WIFI_STATE)
  5. BT MAC Address string (devices with Bluetooth, needs android.permission.BLUETOOTH)
时光瘦了 2024-09-08 00:01:55

官方 Android 开发者博客现在有一篇关于这个主题的完整文章,识别应用程序安装

The official Android Developers Blog now has a full article just about this very subject, Identifying App Installations.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文