是否有唯一的 Android 设备 ID?

Is there a unique Android device ID?

提问人:Tyler 提问时间:5/7/2010 最后编辑:alamshahbaz16497Tyler 更新时间:10/4/2022 访问量:1262825

问:

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

Android 唯一标识符

评论

48赞 Dheeraj Vepakomma 1/16/2013
如果您正在使用,请务必阅读此答案此错误ANDROID_ID

答:

2236赞 Anthony Forloney 5/7/2010 #1

Settings.Secure#ANDROID_ID 将 Android ID 作为每个用户的唯一 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

评论

521赞 Seva Alekseyev 6/23/2010
它有时是空的,它被记录为“恢复出厂设置后可以更改”。使用风险自负,并且可以在有根手机上轻松更改。
23赞 2/6/2011
我认为我们在第一个答案中使用哈希中的ANDROID_ID需要小心,因为它可能在应用程序首次运行时未设置,可能在以后设置,甚至可能在理论上发生变化,因此唯一 ID 可能会发生变化
51赞 emmby 4/8/2011
请注意,此解决方案存在巨大的局限性:android-developers.blogspot.com/2011/03/...
41赞 Tom 12/16/2012
ANDROID_ID不再唯一标识设备(自 4.2 起):stackoverflow.com/a/13465373/150016
1191赞 Joe 5/18/2010 #2

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

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


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

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

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

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

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;

评论

162赞 Seva Alekseyev 6/23/2010
基于电话的 ID 不会出现在平板电脑设备上,是吗?
24赞 Joe 6/30/2010
因此,为什么我说大多数不会一直有效:)我还没有看到这个问题的任何答案对所有设备、所有设备类型和所有硬件配置都是可靠的。这就是为什么这个问题在这里开始。很明显,这个问题没有万能的解决方案。个别设备制造商可能有设备序列号,但这些序列号不会公开供我们使用,也不是必需的。因此,我们只剩下我们可用的东西。
33赞 Richard 2/28/2011
代码示例效果很好。请记住添加到清单文件。如果存储在数据库中,则返回的字符串长度为 36 个字符。<uses-permission android:name="android.permission.READ_PHONE_STATE" />
12赞 emmby 4/8/2011
请注意,此解决方案存在巨大的局限性:android-developers.blogspot.com/2011/03/...
22赞 Joe 7/22/2011
@softarn:我相信你指的是 emmby 已经链接到的 Android 开发者博客,它解释了你想说什么,所以也许你应该简单地给他的评论投赞成票。无论哪种方式,正如 emmby 在他的回答中提到的那样,即使博客信息仍然存在问题。该问题要求提供唯一的设备标识符(而不是安装标识符),所以我不同意您的陈述。该博客假设您想要的不一定是跟踪设备,而问题只是要求这样做。否则我同意博客。
106赞 Seva Alekseyev 6/23/2010 #3

此外,您可以考虑 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

评论

9赞 ohhorob 11/1/2010
这需要android.permission.ACCESS_WIFI_STATE
6赞 chrisdowney 5/22/2012
我想你会发现,当WiFi关闭时,它几乎在所有安卓设备上都不可用。关闭 WiFi 会在内核级别删除设备。
13赞 ocodo 9/4/2013
@Sanandrea - 让我们面对现实吧,在有根设备上,一切都可以被欺骗。
6赞 brz 12/22/2015
在 Android M 上访问 WiFi MAC 地址已被阻止:stackoverflow.com/questions/31329733/...
8赞 Behrouz.M 7/10/2016
从 Android 6.x 开始,它会返回一致的假 mac 地址:02:00:00:00:00:00
43赞 Roman SL 1/25/2011 #4

以下代码使用隐藏的 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) {

}
23赞 rony l 3/6/2011 #5

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

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

评论

5赞 m0skit0 4/15/2013
不幸的是,它是“未知的”。
23赞 Tony Maro 3/22/2011 #6

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

用:

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

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

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

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

45赞 Lenn Dolling 3/25/2011 #7

我认为这是为唯一 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) 再添加两个数字,以具有与 IMEI 相同大小的 ID(15 位数字)。这里还有其他可能性,看看这些字符串就知道了。 返回类似 .不需要特殊权限,因此这种方法非常方便。355715565309247


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

评论

10赞 Steve Pomeroy 4/13/2011
有趣的解决方案。听起来在这种情况下,您确实应该对所有连接的数据进行哈希处理,而不是尝试提出自己的“哈希”函数。在许多情况下,即使每个值都有大量不同的数据,也会出现冲突。我的建议:使用哈希函数,然后将二进制结果转换为十进制并根据需要截断它。要做到这一点,尽管您确实应该使用 UUID 或完整的哈希字符串。
25赞 Steve Haley 5/16/2011
你应该把功劳归功于你的消息来源......这已直接从以下文章中删除:pocketmagic.net/?p=1662
11赞 Seva Alekseyev 5/27/2011
此 ID 容易发生冲突,就像您不知道是什么一样。它实际上可以保证在同一运营商的相同设备上是相同的。
9赞 David Given 1/25/2012
如果设备升级,这也可能会改变。
13赞 Sinan Dizdarević 3/5/2015
非常非常糟糕的解决方案。在两个 Nexus 5 上进行了测试...返回相同的数字。
55赞 BoD 4/6/2011 #8

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

评论

6赞 Tim Bray 4/12/2011
这个论点的关键点是,如果你试图从硬件中获取一个唯一的ID,你可能犯了一个错误。
4赞 Seva Alekseyev 5/5/2011
如果您允许通过恢复出厂设置来重置设备锁,那么您的试用软件模型就和死一样好。
0赞 Bruno Bieri 9/16/2020
这篇博文已经链接到这个网站:developer.android.com/training/articles/user-data-ids
355赞 emmby 4/12/2011 #9

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

但是,这篇博文继续讨论是否需要设备标识符而不是应用安装标识符的解决方案。我与 Google 的某个人进行了交谈,以在您需要这样做时对一些项目进行一些进一步的澄清。以下是我在上述博客文章中未提及的有关设备标识符的发现:

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

根据 Google 的建议,我实现了一个类,该类将为每个设备生成一个唯一的 UUID,在适当的情况下使用 ANDROID_ID 作为种子,必要时回退到 TelephonyManager.getDeviceId(),如果失败,则求助于随机生成的唯一 UUID,该 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;
    }
}

评论

10赞 Samuel 5/19/2011
ANDROID_ID可以在恢复出厂设置时更改,因此也无法识别设备
2赞 Mr_and_Mrs_D 9/18/2013
正如我在这里指出的相同答案,不能保证您会在进程中获得相同的生成 UUID - SharedPrefs 副本是本地的!
33赞 Mohit Kanada 5/17/2011 #10

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

deviceId = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID); 
12赞 Elzo Valugi 7/15/2011 #11

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

评论

11赞 Brill Pappin 1/6/2012
不适用于我的平板电脑,它们没有 IMEI,因为它们没有连接到我的移动运营商。
3赞 David Given 1/25/2012
更不用说具有ESN而不是IMEI的CDMA设备了。
21赞 Kevin Parker 8/2/2011 #12

有关如何获取安装应用程序的每个 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();
    }
}

评论

1赞 Luca Spiller 10/4/2011
如果您想跟踪应用程序安装,这是完美的。不过,跟踪设备要棘手得多,而且似乎没有一个完全密不透风的解决方案。
0赞 tasomaniac 5/10/2012
有根设备呢?他们可以轻松更改此安装 ID,对吗?
0赞 Kevin Parker 5/19/2012
绝对。Root 可以更改安装 ID。您可以使用以下代码块检查 root:stackoverflow.com/questions/1101380/...
0赞 Kevin Parker 1/12/2015
如果将 /data 分区恢复出厂设置并删除或格式化 /data 分区,则 UUID 会有所不同。
188赞 Anthony Nolan 10/28/2011 #13

以下是 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 的演讲中也进行了描述),您应该有一个与用户绑定的 ID,并在设备被擦除甚至更换后仍然存在。我计划在未来的分析中使用它(换句话说,我:)还没有做过这一点。

评论

4赞 Kyle Clegg 5/16/2012
如果您在卸载和重新安装后不需要唯一 ID 来保留(例如,您有三次获胜机会的促销活动/游戏,期间),这是一个很好的选择。
3赞 Carl 12/22/2012
Meier 演示文稿依赖于使用 Android 备份管理器,而 Android 备份管理器又取决于用户选择打开该功能。这对于应用程序用户的偏好(Meier 的使用)来说很好,因为如果用户没有选择该选项,她就不会备份这些选项。但是,最初的问题是关于为设备生成唯一 ID,并且此 ID 是按应用程序生成的,甚至不是按安装生成的,更不用说每个设备了,并且由于它依赖于用户选择备份选项,因此其超出用户偏好的用途(例如,用于限时试用)受到限制。
17赞 John Shelley 9/2/2014
这在卸载或清除数据时不起作用。
92赞 stansult 2/8/2012 #14

这里有相当有用的信息。

它涵盖五种不同的 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 地址字符串(带蓝牙的设备,需要android.permission.BLUETOOTH)

评论

3赞 Tom 10/8/2012
重要的一点被遗漏了(这里和文章中):除非打开WLAN或BT MAC,否则无法获得WLAN或BT MAC!否则,我认为WLAN MAC将是完美的标识符。你不能保证用户会打开他们的Wi-Fi,我真的不认为自己打开它是“合适的”。
2赞 Marqs 8/4/2014
@Tom你错了。即使 WLAN 或 BT MAC 已关闭,您仍然可以读取它们。但是,不能保证设备具有可用的WLAN或BT模块。
4赞 sarika kate 3/30/2016
最值得注意的是,本地 WiFi 和蓝牙 MAC 地址不再可用。从现在开始,aWifiInfo 对象的 getMacAddress() 方法和 BluetoothAdapter.getDefaultAdapter().getAddress() 方法都将返回 02:00:00:00:00:00
5赞 Smeet 4/13/2016
@sarikakate 只有在 6.0 Marshmallow 及更高版本中才是正确的......它在低于 6.0 棉花糖时仍按预期工作。
15赞 Andreas Klöber 11/18/2012 #15

有许多不同的方法可以解决这些问题(有时可能是,或者特定型号的设备始终返回相同的 ID),各有利弊:ANDROID_IDnull

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

我自己更喜欢在Android上使用现有的OpenUDID实现(见 https://github.com/ylechelle/OpenUDID)(见 https://github.com/vieux/OpenUDID)。它很容易集成,并利用回退来解决上述问题。ANDROID_ID

53赞 TechnoTony 12/22/2012 #16

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

这种方法将为您提供一个匿名、安全的用户 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>

然后,您需要创建备份代理,并告诉它使用 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);
    }
}

要完成备份,您需要在主 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也将在安装中持久存在。

有关此方法的详细信息,请参阅 Reto 的演讲

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

评论

9赞 Tosa 3/13/2013
当用户拥有多个设备时,这不会导致多个设备具有相同的 ID 吗?例如,平板电脑和手机。
0赞 mohammad reza sarsarabi 8/23/2022
如果用户在手机上禁用了备份怎么办?在这种情况下,不会备份任何应用程序数据!
10赞 insitusec 1/31/2013 #17

另一种方法是在没有任何权限的情况下在应用程序中使用。/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 文件并读出字符。只需确保将其包装在异常处理程序中,因为并非所有设备都有此文件。

已知至少以下设备具有全局可读的此文件:

  • 银河Nexus
  • Nexus S的
  • 摩托罗拉 Xoom 3G
  • 东芝AT300
  • HTC One V
  • 迷你MK802
  • 三星Galaxy S II

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

评论

0赞 Tom 5/1/2013
我刚刚读了你的博客文章。我相信这不是唯一的:Build.SERIAL也可以在没有任何权限的情况下使用,并且(理论上)是一个唯一的硬件序列号。
2赞 insitusec 3/15/2014
你是对的。这只是跟踪您的设备的另一种方式,正如您所说,这两种方式都不需要应用程序权限。
16赞 Android 2/27/2013 #18

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

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" />
10赞 Asaf Pinhassi 4/15/2013 #19

我使用以下代码来获取或使用 Secure。或者,当设备没有电话功能时:IMEIANDROID_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);
11赞 Eng.Fouad 5/28/2013 #20

以下是我生成唯一 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;
}

评论

0赞 Harsha 11/17/2016
如果我们在 6.0 版本中使用 ReadPhoneState 请求运行时权限
472赞 Jared Burrows 7/13/2013 #21

#Last 最近更新时间: 6/2/15


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

主要问题:硬件与软件

硬件

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

软件

  • 如果他们被扎根,用户可以擦除/更改他们的ROM
  • 您可以跨平台(iOS、Android、Windows 和 Web)跟踪用户
  • 个人用户同意的情况下跟踪他们最好的方法是让他们登录(使用 OAuth 实现无缝)

Android #Overall 故障

###- 保证 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 问题中)。

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

  • 用户电子邮件 - 软件

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

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

  • API 14+(如何获取 Android 设备的主电子邮件地址<uses-permission android:name="android.permission.READ_PROFILE" /><uses-permission android:name="android.permission.READ_CONTACTS" />)

  • 用户电话号码 - 软件

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

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

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

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

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

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

  • 由于它可以是 'null',我们可以检查 'null' 并更改其值,但这意味着它将不再是唯一的。

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

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

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

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

  • 蓝牙 MAC 地址 - 硬件(带蓝牙的设备,需要 android.permission.BLUETOOTH)

  • 市场上的大多数应用程序都不使用蓝牙,因此,如果您的应用程序不使用蓝牙,并且您包含此功能,则用户可能会产生怀疑。

  • <uses-permission android:name="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”将被保留或不被保留。(见 http://developer.android.com/about/dashboards/index.html)

##If 所有其他方法都失败了:

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

变化:

  • 删除了“Android.SECURE_ID”,因为恢复出厂设置可能会导致该值更改
  • 编辑代码以更改 API
  • 更改了伪

请看下面的方法:

/**
 * 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 服务):

在 Google Play 开发者控制台中:

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

实现

许可:

<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

##Important:

广告 ID 旨在完全替换现有的 出于广告目的使用其他标识符(例如使用ANDROID_ID 在 Settings.Secure 中),当 Google Play 服务可用时。例 如果 Google Play 服务不可用,则由 GooglePlayServicesNotAvailableException 被抛出 getAdvertisingIdInfo() 中。

##Warning,用户可以重置:

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

我试图引用我从中获取信息的每个链接。如果您失踪并需要被包括在内,请发表评论!

Google 播放器服务实例 ID

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

评论

7赞 hojjat reyhane 3/18/2015
我在我的应用程序中使用了您的方法来发送评论。我有个坏消息。不幸的是,PsuedoID 并不完全是独一无二的。我的服务器记录了 5 个 ID 的 100 多个和近 30 个 ID 的 30 多个。 重复次数最多的 ID 是“ffffffff-fc8f-6093-ffff-ffffd8”(159 条记录)和“ffffffff-fe99-b334-ffff-ffffef”(154 次)。此外,根据时间和评论,很明显有不同的民族。到目前为止,总记录为 10,000 条。请让我知道为什么会这样。坦克。
3赞 Jared Burrows 3/18/2015
我在 1.5+ 年前写了这个。我不确定为什么它对你来说不是独一无二的。您可以尝试广告 ID。如果没有,您可以提出自己的解决方案。
0赞 gumuruh 4/25/2022
为什么这么难用一个简单的无冲突来获得唯一的 ID?只需使用伪唯一 ID - 软件 + 日期 (hhmmss) 就不会发生冲突。组合时间越长,应用的难度率就越高。
13赞 Mr_and_Mrs_D 9/19/2013 #22

我的两分钱 - 注意,这是针对设备(错误)唯一 ID 的 - 而不是 Android 开发人员博客中讨论的安装ID。

请注意,@emmby提供的解决方案回退到每个应用程序 ID 中,因为 SharedPreferences 不会在进程之间同步(请参阅此处和此处)。所以我完全避免了这一点。

相反,我将获取(设备)ID 的各种策略封装在枚举中 - 更改枚举常量的顺序会影响获取 ID 的各种方式的优先级。返回第一个非 null ID 或抛出异常(根据不赋予 null 含义的良好 Java 实践)。例如,我首先拥有 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);
        }
    }
}
1赞 TootsieRockNRoll 10/21/2013 #23

仅获取一次设备 ID,然后将其存储在数据库或文件中。在这种情况下,如果是应用的首次启动,它会生成一个 ID 并存储它。下次,它将只获取存储在文件中的 ID。

评论

7赞 Ted Hopp 10/21/2013
这个想法是有一个 ID,该 ID 在卸载和重新安装的应用程序中仍然存在。将 ID 保存到数据库无济于事,因为卸载应用时会删除数据库。
8赞 Hertzel Guinness 11/2/2013 #24

Google 现在有一个广告 ID
这也可以使用,但请注意:

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

允许用户在 Google Play 应用中重置其标识符或选择停用基于兴趣的广告。

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

更多信息 @ develper.android

在此处复制粘贴代码

HTH型

0赞 Ciul 1/7/2014 #25

检查 SystemInfo.deviceUniqueIdentifier

文档:http://docs.unity3d.com/Documentation/ScriptReference/SystemInfo-deviceUniqueIdentifier.html

唯一的设备标识符。它保证每个设备都是唯一的(只读)。

iOS:在 iOS7 之前的设备上,它将返回 MAC 地址的哈希值。在 iOS7 设备上,它将是 UIDevice identifierForVendor,或者,如果由于任何原因失败,则为 ASIdentifierManager advertisingIdentifier。

12赞 Jorgesys 1/13/2014 #26

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 推荐的方法,请参阅识别应用程序安装

9赞 Jorgesys 2/6/2015 #27

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

评论

0赞 Holger Jakobs 3/14/2016
我最近发现,SM-G928F / Galaxy S6 edge+类型的客户端设备仅为Android ID提供15位而不是16位十六进制数字。
9赞 Tom 6/20/2015 #28

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 的优点

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

如果使用 GCM,则最终将需要使用此实例 ID,因为需要它来获取 GCM 令牌(替换旧的 GCM 注册 ID)。

缺点/问题

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

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

评论

3赞 idanakav 7/9/2015
instanceID 的另一个显著缺点是,如果用户清除了应用程序的数据,则会为您生成一个新的 instanceID。
0赞 Tom 7/9/2015
有趣的是,我不认为它真的改变了潜在的用例:实例 ID 和 android_id 一样,不适合识别设备。因此,您的服务器将看到用户清除数据,就像用户卸载并重新安装您的应用程序一样 - 这并非不合理。
10赞 Ilan.b 9/1/2015 #29

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

你可以这样做:

在 AndroidManifest.xml 中

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

现在在您的代码中:

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的列表中获得,但这可以在手机之间更改。

8赞 Baskaran Veerabathiran 10/3/2016 #30

Android 设备 mac id 也是一个唯一的 ID。即使设备本身已格式化,它也不会更改。

使用以下代码获取 mac id:

WifiManager manager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
WifiInfo info = manager.getConnectionInfo();
String address = info.getMacAddress();

另外,不要忘记将适当的权限添加到您的 AndroidManifest.xml:

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

评论

4赞 Ted Hopp 10/5/2016
不幸的是,如果当前没有 WiFi 连接,这将不起作用。来自文档(强调后加):“返回有关当前 Wi-Fi 连接的动态信息(如果有处于活动状态)。
3赞 7/4/2017
此外,通过在设备上授予root访问权限,可以欺骗mac地址
0赞 Saddan 5/23/2020
搭载 Android 10(API 级别 29)及更高版本的设备会向所有非设备所有者应用的应用报告随机 MAC 地址。因此,当您使用 android 10 或更高版本时,它不会是唯一的
13赞 ᴛʜᴇᴘᴀᴛᴇʟ 3/8/2017 #31

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

它结合了 3 个 ID 并创建一个 32 位十六进制字符串。它对我来说效果很好。

3 个 ID 是:
伪 ID - 根据物理设备规格
生成 ANDROID_ID - 蓝牙地址 -
蓝牙适配器地址
Settings.Secure.ANDROID_ID

它将返回如下内容:551F27C060712A72730B0A0F734064B1

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

@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赞 Mousa Alfhaily 4/11/2017
UUID 添加到 并将其存储在文件中,将使其成为最唯一的标识符:longIdString uuid = UUID.randomUUID().toString();
3赞 Mousa Alfhaily 4/11/2017
如果所有其他方法都失败了,如果用户的 API 低于 API 9(低于 Gingerbread),则重置了他们的手机或“Secure.ANDROID_ID”。如果返回“null”,则返回的 ID 将完全基于其 Android 设备信息。这是可能发生碰撞的地方。尽量不要使用 DISPLAY、HOST 或 ID - 这些项目可能会发生变化。如果发生碰撞,则会出现数据重叠。来源 : gist.github.com/pedja1/fe69e8a80ed505500caa
2赞 ᴛʜᴇᴘᴀᴛᴇʟ 4/3/2019
@Ninja 由于 BLE mac 地址是唯一的,是的,生成的 ID 将始终是唯一的。但是,如果您真的想确定,我建议在 .将这一行更改为这样: 这保证了生成的 ID 是唯一的。longIdString longId = pseudoId + androidId + btId + UUID.randomUUID().toString();
4赞 bugraoral 4/11/2017 #32

只是提醒大家寻找更多最新信息。在 Android O 中,系统管理这些 ID 的方式发生了一些变化。

https://android-developers.googleblog.com/2017/04/changes-to-device-identifiers-in.html

tl;dr Serial 将需要 PHONE 权限,并且 Android ID 将根据其包名称和签名针对不同的应用程序进行更改。

此外,Google还整理了一份不错的文档,其中提供了有关何时使用硬件和软件ID的建议。

https://developer.android.com/training/articles/user-data-ids.html

4赞 Zin Win Htet 6/19/2017 #33

通常,我为我的应用使用设备唯一 ID。但有时我会使用IMEI。两者都是唯一的数字。

获取 IMEI国际移动设备标识符)

public String getIMEI(Activity activity) {
    TelephonyManager telephonyManager = (TelephonyManager) activity
            .getSystemService(Context.TELEPHONY_SERVICE);
    return telephonyManager.getDeviceId();
}

获取设备唯一 ID

public String getDeviceUniqueID(Activity activity){
    String device_unique_id = Secure.getString(activity.getContentResolver(),
            Secure.ANDROID_ID);
    return device_unique_id;
}

评论

0赞 Vakas 5/18/2022
Android Q 已限制对 IMEI 和序列号的访问。
2赞 Jeffy 9/7/2017 #34
String SERIAL_NUMER = Build.SERIAL;

以字符串形式返回 SERIAL NUMBER,该字符串在每个设备中是唯一的。

4赞 Cheok Yan Cheng 11/8/2017 #35

几年前,我遇到了这个问题,并学会了根据各种答案实现通用解决方案。

我已经在实际产品中使用了通用解决方案好几年了。到目前为止,它为我提供了很好的服务。下面是代码片段,基于提供的各种答案。

请注意,大多数时候将返回 null,因为我们没有明确请求权限。getEmail

private static UniqueId getUniqueId() {
    MyApplication app = MyApplication.instance();

    // Our prefered method of obtaining unique id in the following order.
    // (1) Advertising id
    // (2) Email
    // (2) ANDROID_ID
    // (3) Instance ID - new id value, when reinstall the app.

    ////////////////////////////////////////////////////////////////////////////////////////////
    // ADVERTISING ID
    ////////////////////////////////////////////////////////////////////////////////////////////
    AdvertisingIdClient.Info adInfo = null;
    try {
        adInfo = AdvertisingIdClient.getAdvertisingIdInfo(app);
    } catch (IOException e) {
        Log.e(TAG, "", e);
    } catch (GooglePlayServicesNotAvailableException e) {
        Log.e(TAG, "", e);
    } catch (GooglePlayServicesRepairableException e) {
        Log.e(TAG, "", e);
    }

    if (adInfo != null) {
        String aid = adInfo.getId();
        if (!Utils.isNullOrEmpty(aid)) {
            return UniqueId.newInstance(aid, UniqueId.Type.aid);
        }
    }

    ////////////////////////////////////////////////////////////////////////////////////////////
    // EMAIL
    ////////////////////////////////////////////////////////////////////////////////////////////
    final String email = Utils.getEmail();
    if (!Utils.isNullOrEmpty(email)) {
        return UniqueId.newInstance(email, UniqueId.Type.eid);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////
    // ANDROID ID
    ////////////////////////////////////////////////////////////////////////////////////////////
    final String sid = Settings.Secure.getString(app.getContentResolver(), Settings.Secure.ANDROID_ID);
    if (!Utils.isNullOrEmpty(sid)) {
        return UniqueId.newInstance(sid, UniqueId.Type.sid);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////
    // INSTANCE ID
    ////////////////////////////////////////////////////////////////////////////////////////////
    final String iid = com.google.android.gms.iid.InstanceID.getInstance(MyApplication.instance()).getId();
    if (!Utils.isNullOrEmpty(iid)) {
        return UniqueId.newInstance(iid, UniqueId.Type.iid);
    }

    return null;
}

public final class UniqueId implements Parcelable {
    public enum Type implements Parcelable {
        aid,
        sid,
        iid,
        eid;

        ////////////////////////////////////////////////////////////////////////////
        // Handling Parcelable nicely.

        public static final Parcelable.Creator<Type> CREATOR = new Parcelable.Creator<Type>() {
            public Type createFromParcel(Parcel in) {
                return Type.valueOf(in.readString());
            }

            public Type[] newArray(int size) {
                return new Type[size];
            }
        };

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel parcel, int flags) {
            parcel.writeString(this.name());
        }

        // Handling Parcelable nicely.
        ////////////////////////////////////////////////////////////////////////////
    }

    public static boolean isValid(UniqueId uniqueId) {
        if (uniqueId == null) {
            return false;
        }
        return uniqueId.isValid();
    }

    private boolean isValid() {
        return !org.yccheok.jstock.gui.Utils.isNullOrEmpty(id) && type != null;
    }

    private UniqueId(String id, Type type) {
        if (org.yccheok.jstock.gui.Utils.isNullOrEmpty(id) || type == null) {
            throw new java.lang.IllegalArgumentException();
        }
        this.id = id;
        this.type = type;
    }

    public static UniqueId newInstance(String id, Type type) {
        return new UniqueId(id, type);
    }

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + id.hashCode();
        result = 31 * result + type.hashCode();
        return result;
    }

    @Override
    public boolean equals(Object o) {
        if (o == this) {
            return true;
        }

        if (!(o instanceof UniqueId)) {
            return false;
        }

        UniqueId uniqueId = (UniqueId)o;
        return this.id.equals(uniqueId.id) && this.type == uniqueId.type;
    }

    @Override
    public String toString() {
        return type + ":" + id;
    }

    ////////////////////////////////////////////////////////////////////////////
    // Handling Parcelable nicely.

    public static final Parcelable.Creator<UniqueId> CREATOR = new Parcelable.Creator<UniqueId>() {
        public UniqueId createFromParcel(Parcel in) {
            return new UniqueId(in);
        }

        public UniqueId[] newArray(int size) {
            return new UniqueId[size];
        }
    };

    private UniqueId(Parcel in) {
        this.id = in.readString();
        this.type = in.readParcelable(Type.class.getClassLoader());
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int flags) {
        parcel.writeString(this.id);
        parcel.writeParcelable(this.type, 0);
    }

    // Handling Parcelable nicely.
    ////////////////////////////////////////////////////////////////////////////

    public final String id;
    public final Type type;
}

public static String getEmail() {
    Pattern emailPattern = Patterns.EMAIL_ADDRESS; // API level 8+
    AccountManager accountManager = AccountManager.get(MyApplication.instance());
    Account[] accounts = accountManager.getAccountsByType("com.google");
    for (Account account : accounts) {
        if (emailPattern.matcher(account.name).matches()) {
            String possibleEmail = account.name;
            return possibleEmail;
        }
    }

    accounts = accountManager.getAccounts();
    for (Account account : accounts) {
        if (emailPattern.matcher(account.name).matches()) {
            String possibleEmail = account.name;
            return possibleEmail;
        }
    }

    return null;
} 

评论

0赞 G3n1t0 3/10/2021
但是,当用户重置手机时,这是否有效?在这种情况下,Android ID 将发生变化,广告 ID 和电子邮件也可以重置。
2赞 shivampip 12/14/2017 #36

序列号是通过 android.os.Build.SERIAL 获取的唯一设备 ID。

public static String getSerial() {
    String serial = "";
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
        serial = Build.getSerial();
    }else{ 
        serial = Build.SERIAL;    
    }
    return serial;
}

在调用 getSerial() 之前,请确保您具有READ_PHONE_STATE权限。

注意:- 它不适用于没有电话的设备(如仅限 wifi 的平板电脑)。

评论

3赞 Ted Hopp 12/15/2017
请注意,文档说这将返回硬件序列号“如果可用”,这表明(或)并不总是可用。有关详细信息,请参阅博文 Android O 中设备标识符的更改。另外值得一读:唯一标识符的最佳实践Build.SERIALBuild.getSerial()
4赞 Ege Kuzubasioglu 12/28/2017 #37

不建议使用,因为 deviceId 可以用作第三方手中的跟踪,但这是另一种方式。

@SuppressLint("HardwareIds")
private String getDeviceID() {
    deviceId = Settings.Secure.getString(getApplicationContext().getContentResolver(),
                    Settings.Secure.ANDROID_ID);
    return deviceId;
}

评论

1赞 Jeff Padgett 6/22/2018
Android 正在对以下方面进行一些更改:Settings.Secure.ANDROID_ID;在 Android 8.0(API 级别 26)及更高版本的平台上,一个 64 位数字(以十六进制字符串表示),对于应用签名密钥、用户和设备的每个组合都是唯一的。这意味着Settings.Secure.ANDROID_ID现在返回应用/设备组合唯一的 ID,从而使用户更安全。
8赞 Kiran Maniya 10/5/2018 #38

1.使用电话管理器,它提供唯一的ID(即IMEI)。请参阅示例,

import android.telephony.TelephonyManager;
import android.content.Context;
// ...
TelephonyManager telephonyManager;
telephonyManager = (TelephonyManager) getSystemService(Context.
                TELEPHONY_SERVICE);
/*
* getDeviceId() returns the unique device ID.
* For example,the IMEI for GSM and the MEID or ESN for CDMA phones.
*/
String deviceId = telephonyManager.getDeviceId();
/*
* getSubscriberId() returns the unique subscriber ID,
*/
String subscriberId = telephonyManager.getSubscriberId();

这对您的用户来说,很难证明遵循您制作的应用程序类型是合理的。android.permission.READ_PHONE_STATE

  1. 没有电话服务(如平板电脑)的设备必须报告一个唯一的设备 ID,该 ID 可通过 Android 2.3 Gingerbread 获得。某些具有电话服务的电话也可以定义序列号。与并非所有 Android 设备都有序列号一样,此解决方案也不可靠。android.os.Build.SERIAL

  2. 在设备首次启动时,会生成并存储一个随机值。此值可通过 获得。这是一个 64 位数字,在设备的生命周期内应保持不变。 对于唯一设备标识符来说,这似乎是一个不错的选择,因为它可用于智能手机和平板电脑。若要检索该值,可以使用以下代码:Settings.Secure.ANDROID_IDANDROID_ID

    字符串 androidId = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);

但是,如果在设备上执行恢复出厂设置,则该值可能会更改。制造商的流行手机也存在一个已知错误,其中每个实例都具有相同的 .显然,该解决方案并非 100% 可靠。ANDROID_ID

  1. 使用 UUID。由于大多数应用程序的要求是识别特定安装而不是物理设备,因此如果使用 UUID 类,则获取用户的唯一 ID 是一个很好的解决方案。来自 Google 的 Reto Meier 在 Google I/O 演示中介绍了以下解决方案:
SharedPreferences sharedPrefs = context.getSharedPreferences(
         PREF_UNIQUE_ID, Context.MODE_PRIVATE);
uniqueID = sharedPrefs.getString(PREF_UNIQUE_ID, null);

更新:选项 #1#2 在 android 10 之后不再可用,因为谷歌的隐私更新。由于选项 2 和 3 需要关键权限。

评论

2赞 viper 8/26/2019
哪个手机是每个实例都具有相同ANDROID_ID的手机?
0赞 Kiran Maniya 8/26/2019
请参考官方文档 developer.android.com/reference/android/provider/...
2赞 user924 2/6/2020
DeviceInfoProvider它不是 Android SDK 的一部分
2赞 Martin Zikmund 1/21/2019 #39

为了完整起见,以下是获取 in 和 C# 的方法:IdXamarin.Android

var id = Settings.Secure.GetString(ContentResolver, Settings.Secure.AndroidId);

或者,如果您不在 :Activity

var id = Settings.Secure.GetString(context.ContentResolver, Settings.Secure.AndroidId);

在上下文中传递的位置。context

4赞 Jens Vesti 1/31/2019 #40

为了包含 Android 9,我只有一个想法仍然可以工作,它(可能)不违反任何条款,需要权限,并且可以跨安装和应用程序工作。

涉及服务器的指纹识别应该能够唯一地识别设备。 硬件信息+已安装的应用程序和安装时间的组合应该可以解决问题。 除非卸载并重新安装应用,否则首次安装时间不会更改。但是,必须对设备上的所有应用程序执行此操作,以便无法识别设备(即恢复出厂设置后)。

我是这样去做的:

  1. 提取硬件信息、应用程序包名称和首次安装时间。

这是您从Android中提取所有应用程序的方式(无需权限):

final PackageManager pm = application.getPackageManager();
List<ApplicationInfo> packages = 
pm.getInstalledApplications(PackageManager.GET_META_DATA);

for (ApplicationInfo packageInfo : packages) {
    try {
        Log.d(TAG, "Installed package :" + packageInfo.packageName);
        Log.d(TAG, "Installed :" + pm.getPackageInfo(packageInfo.packageName, 0).firstInstallTime);
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
    }
}
  1. 在将每个软件包名称和安装时间戳组合发送到服务器之前,您可能希望对其进行哈希处理,因为用户在设备上安装的内容可能是也可能不是您的业务。
  2. 有些应用程序(实际上很多)是系统应用程序。它们可能具有相同的安装时间戳,与恢复出厂设置后的最新系统更新相匹配。由于它们具有相同的安装时间戳,因此用户无法安装它们,因此可以将其过滤掉。
  3. 将信息发送到服务器,让它在以前存储的信息中查找最接近的匹配项。在安装和卸载应用程序时,在与以前存储的设备信息进行比较时,需要设置阈值。但我的猜测是,这个阈值可能非常低,因为对于设备来说,任何软件包名称和首次安装时间戳组合都是非常独特的,而且应用程序的安装和卸载频率并不高。拥有多个应用程序只会增加成为唯一应用程序的可能性。
  4. 返回为匹配项生成的唯一 ID,或生成唯一 ID,与设备信息一起存储并返回此新 ID。

铌:这是一种未经测试和未经证实的方法!我相信它会起作用,但我也很确定,如果这种情况流行起来,他们会以一种或另一种方式关闭它。

8赞 Waheed Nazir 5/4/2019 #41

了解 Android 设备中可用的唯一 ID。使用此官方指南。

唯一标识符的最佳做法:

IMEI、Mac 地址、实例 ID、GUID、SSAID、广告 ID、用于验证设备的安全网 API。

https://developer.android.com/training/articles/user-data-ids

4赞 Raj 6/12/2019 #42

这是获得AAID的简单答案,测试于2019年6月正常工作

 AsyncTask<Void, Void, String> task = new AsyncTask<Void, Void, String>() {
        @Override
        protected String doInBackground(Void... params) {
            String token = null;
            Info adInfo = null;
            try {
                adInfo = AdvertisingIdClient.getAdvertisingIdInfo(getApplicationContext());
            } catch (IOException e) {
                // ...
            } catch ( GooglePlayServicesRepairableException e) {
                // ...
            } catch (GooglePlayServicesNotAvailableException e) {
                // ...
            }
            String android_id = adInfo.getId();
            Log.d("DEVICE_ID",android_id);

            return android_id;
        }

        @Override
        protected void onPostExecute(String token) {
            Log.i(TAG, "DEVICE_ID Access token retrieved:" + token);
        }

    };
    task.execute();

在此处详细阅读完整答案:

3赞 Malwinder Singh 7/24/2019 #43

如果添加:

Settings.Secure.getString(context.contentResolver,
    Settings.Secure.ANDROID_ID)

Android Lint 将向您发出以下警告:

不建议使用 getString 获取设备标识符。 检查信息:不建议使用这些设备标识符 除了高价值的欺诈预防和高级电话 用例。对于广告用例,请使用 AdvertisingIdClient$Info#getId 对于分析,请使用 InstanceId#getId。

因此,您应该避免使用它。

Android 开发者文档中所述:

1:避免使用硬件标识符。

在大多数用例中,您可以避免使用硬件标识符,例如 SSAID (Android ID) 和 IMEI、 不限制所需的功能。

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

使用广告 ID 时,请始终尊重用户的选择 关于广告跟踪。此外,请确保标识符不能是 连接到个人身份信息 (PII),并避免 桥接广告 ID 重置。

3:对于所有其他用例,尽可能使用实例 ID 或私有存储的 GUID,但付款欺诈预防和电话除外。

对于绝大多数非广告用例,实例 ID 或 GUID 就足够了。

4:使用适合您的用例的 API,以最大限度地降低隐私风险。

使用 DRM API 进行高价值内容保护和 用于滥用保护的 SafetyNet API。SafetyNet API 是 在不发生事故的情况下确定设备是否为正品的最简单方法 隐私风险。

0赞 Vitaly Zinchenko 8/3/2019 #44

要获取用户 ID,您可以使用 Google Play 许可库。

要下载此库,请打开 SDK Manager => SDK Tools。 下载的库文件的路径为:

path_to_android_sdk_on_your_pc/extras/google/market_licensing/library

将库包含在项目中(只需复制其文件即可)。

接下来,您需要一些接口实现(您可以简单地使用库中的两个文件之一:或)。PolicyServerManagedPolicyStrictPolicy

用户 ID 将在功能中为您提供:processServerResponse()

public void processServerResponse(int response, ResponseData rawData) {
    if(rawData != null) {
        String userId = rawData.userId
        // use/save the value
    }
    // ...
}

接下来,您需要构造 with 策略和调用函数。以示例为例说明如何做到这一点。 位于此文件夹内:LicenseCheckercheckAccess()MainActivity.javaMainActivity.java

path_to_android_sdk_on_your_pc/extras/google/market_licensing/sample/src/com/example/android/market/licensing

不要忘记为您的 AndroidManifest.xml添加CHECK_LICENSE权限。

有关许可库的更多信息:https://developer.android.com/google/play/licensing

1赞 Stephan John 9/11/2019 #45

是的,每个 Android 设备都有一个唯一的序列号,您可以从此代码中获取它。.请注意,它仅在 API 级别 9 中添加,并且可能并非在所有设备上都存在。要在早期平台上获得唯一 ID,您需要读取 MAC 地址或 IMEI 等内容。Build.SERIAL

1赞 Anubhav 9/25/2019 #46
android.telephony.TelephonyManager.getDeviceId()

这将返回唯一标识设备的任何字符串(GSM 上的 IMEI、CDMA 上的 MEID)。

您需要在 AndroidManifest.xml 中拥有以下权限:

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

评论

4赞 Googlian 10/23/2019
它已被弃用
141赞 Nikita Kurtin 11/29/2019 #47

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

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

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

请记住以下几点:

所有基于硬件的标识符(IMEI、MAC、序列号等)对于非谷歌设备(Pixels 和 Nexuses 除外)都是不可靠的,从统计学上讲,这些设备是全球大多数 Android 活跃设备。因此,官方 Android 标识符最佳做法明确指出:

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

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

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

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

所以用更简单的话来说:不要使用这些技术

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

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

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

所以也不要使用它

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

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

对于设备上的应用程序安装来说,它是唯一的,因此当用户卸载应用程序时,它会被清除,因此它不是 100% 可靠的,但它是下一个最好的事情。

注意截至今天,它已被弃用,您应该改用。FirebaseInstanceIdFirebaseInstallations

要使用 将最新的 firebase-messaging 依赖项添加到 gradle 中FirebaseInstallations

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 - 标识符和类似法规依法执行此操作。

评论

0赞 David Schneider 7/21/2020
您能否具体说明您发布的 GDPR 链接的哪一部分实际上提到了哈希 ID 的要求?
1赞 Nikita Kurtin 7/21/2020
@DavidSchneider网络内容本质上是动态的,GDPR只是其中一个例子,请注意,我写了“ GDPR和类似法规 ”,因为有许多本地和全球法规会影响您的产品/系统/领域。在任何情况下,您正在寻找的 GDPR 部分是:身份识别在线标识符数据保护原则
0赞 william gouvea 1/4/2021
proandroiddev.com/......
0赞 DIRTY DAVE 1/20/2022
使用找不到方法。2022年 使用com.google.firebase:firebase-messaging:23.0.0FirebaseInstanceIdFirebaseMessaging.getInstance().getToken()
3赞 Agent_L 2/14/2022
@famfamfam 因为跟踪设备是对用户的隐私和安全威胁。没有合法的案例需要这样做,所以没有理由使它成为可能。Hw ids被证明是错误,谷歌正在慢慢修复它们。
0赞 Jai Khambhayta 5/19/2020 #48

Android 在 Android O 之后限制了与硬件相关的 ID,因此, Android_Id是唯一 ID 的解决方案,但在以下情况下存在问题 反射器设备 它将产生新的android_id来克服这个问题 问题我们可以使用 DRUMID。

val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L)
val drumIDByteArray = MediaDrm(WIDEVINE_UUID).getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID)

val drumID = android.util.Base64.encodeToString(drumIDByteArray,android.util.Base64.DEFAULT)
8赞 Nimesh Patel 6/7/2020 #49

借助以下功能获取 Device 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);
}

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

enter image description here

评论

0赞 porya74 6/15/2020
什么是 %10 和 35+“...”在你的代码中?我的意思是你为什么要使用这种方法来构建唯一 ID?为什么不简单地将这些字符串组合在一起并生成一个唯一的 UUID?这种方法的输出对于世界上所有设备都是完全唯一的吗?
1赞 Nishant 9/22/2021
Build.getRadioVersion() 返回 null
0赞 Nimesh Patel 9/29/2021
@Nishant pelase 在此处分享您的代码。所以我可以在这方面帮助你
-2赞 Abdallah AlTaher 9/24/2020 #50

无论您在尝试连接到 wifi 时是否使用了随机地址,也无论 wifi 是打开还是关闭,您都将使用以下代码获取 wifi mac 地址。

我使用了下面链接中的一种方法,并添加了一个小的修改来获得确切的地址而不是随机的地址:

在Android 6.0中获取MAC地址

public static String getMacAddr() {
StringBuilder res1 = new StringBuilder();
try {
List<NetworkInterface> all =     
Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface nif : all) {    
if (!nif.getName().equalsIgnoreCase("p2p0")) continue;

        byte[] macBytes = nif.getHardwareAddress();
        if (macBytes == null) {
            continue;
        }

        res1 = new StringBuilder();
        for (byte b : macBytes) {
            res1.append(String.format("%02X:",b));
        }

        if (res1.length() > 0) {
            res1.deleteCharAt(res1.length() - 1);
        }
    }
} catch (Exception ex) {
}
return res1.toString();

}

评论

0赞 Sambhav Khandelwal 2/6/2022
这不是问题所在。
-3赞 MD. Shafiul Alam Biplob 10/24/2021 #51

package com.aapbd.appbajarlib.common;

import android.os.Build;

import java.util.Locale;
import java.util.UUID;

public class DeviceID {



    public  static String getDeviceLanguage()
    {

        Locale locale=Locale.getDefault();
        return locale.getDisplayLanguage();

    }

    public  static String getDeviceCountry()
    {

        Locale locale=Locale.getDefault();
        return locale.getDisplayCountry();

    }


    public static String getDeviceName() {
        String manufacturer = Build.MANUFACTURER;
        String model = Build.MODEL;
        if (model.startsWith(manufacturer)) {
            return capitalize(model);
        } else {
            return capitalize(manufacturer) + " " + model;
        }
    }

    public static String getAndroidVersion() {

            String release = Build.VERSION.RELEASE;
            int sdkVersion = Build.VERSION.SDK_INT;
            return sdkVersion + " (" + release +")";

    }

    public static int getAndroidAPILevel() {

        int sdkVersion = Build.VERSION.SDK_INT;
        return sdkVersion;

    }




    private static String capitalize(String s) {
        if (s == null || s.length() == 0) {
            return "";
        }
        char first = s.charAt(0);
        if (Character.isUpperCase(first)) {
            return s;
        } else {
            return Character.toUpperCase(first) + s.substring(1);
        }
    }


    /**
     * 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!
        // http://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!
        // http://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();
    }

}

我正在使用它,它在过去 6 年中一直在工作。

这是库:https://github.com/nillbiplob/AppBajarLIB

1赞 farhad.kargaran 3/8/2022 #52

经过一番寻找。我意识到没有确定的方法可以拥有唯一的 ID。

假设我们希望每个用户只能在一部手机上使用该应用程序。

我所做的是:

当用户在我的应用程序中注册时,我会将当前时间保存为服务器和应用程序数据库中的唯一 ID。

当用户尝试在另一部手机上登录时,我从服务器获取用户信息并意识到该用户已经登录,因为唯一 ID 字段已满,因此向他/她显示他/她已经登录到另一台设备的对话框,无论他/她是否要离开上一个会话, 如果他说是,我将为他创建一个新的唯一 ID,并在服务器上更新唯一 ID 详细信息。

在我自己的应用程序中,每次运行时我都会从服务器获取用户配置文件。如果服务器上存储的唯一 ID 与应用程序数据库中存储的唯一 ID 不同,我将自动注销用户。

评论

0赞 Vakas 5/18/2022
如果用户在其设备上重新安装该应用,该怎么办?这种方法将失败。
0赞 lasec0203 6/8/2022
@Vakas用户使用凭据登录应用,因此重新安装应用不是问题,因为他们必须再次登录。
0赞 Vakas 6/30/2022
@lasec0203 我们有一个金融应用程序,人们试图克隆和绕过证券,因此我们需要检查哪个用户在设备上签名了多少次。在我们的例子中,这种技术失败了。
1赞 AmirMohamamd 9/28/2022 #53

此示例演示了如何在 Android 中获取和存储设备 ID,但我使用的是 Kotlin。

      val textView: TextView = findViewById(R.id.textView)
      val uniqueId: String = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
      textView.text = "Device ID: $uniqueId"
1赞 Bholendra Singh 10/4/2022 #54

生成设备标识符

 private String generateDeviceIdentifier() {
    String uniqueDevicePseudoID = "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(uniqueDevicePseudoID.hashCode(), serial.hashCode()).toString();
    Log.e("DeviceIdentifier ", "\nDeviceIdentifier uuid is : " + uuid);
    return uuid;
}

输出

DeviceIdentifier uuid is : 00000000-36ab-9c3c-0000-0000714a4f37

enter image description here