是否有唯一的Android设备ID?

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

1215176 次浏览

#0将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

更新:在最近的Android版本中,ANDROID_ID的许多问题已经解决,我相信这种方法不再是必要的。请看看安东尼的回答

完全披露:我的应用程序最初使用以下方法,但不再使用这种方法,我们现在使用Emmby的回答链接到的Android开发者博客条目中概述的方法(即生成和保存#0)。


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

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

  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;

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

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

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

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

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

注:从Android 6. x,它返回一致的假mac地址:02:00:00:00:00:00

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

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

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

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

使用:

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

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

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

我的设备没有手机收音机。

我认为这是为一个独特的ID构建骨架的肯定方式…

伪唯一ID,适用于所有Android设备有些设备没有手机(例如平板电脑),或者出于某种原因,您不想包含READ_PHONE_STATE权限。您仍然可以阅读ROM版本、制造商名称、CPU类型和其他硬件详细信息,如果您想将ID用于串行密钥检查或其他常规目的,这些详细信息非常适合。以这种方式计算的ID不会是唯一的:可以找到两个具有相同ID的设备(基于相同的硬件和ROM映像),但实际应用程序中的变化可以忽略不计。为此,你可以使用Build类:

String m_szDevIDShort = "35" + //we make this look like a valid IMEIBuild.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(15位)相同的大小ID。这里还有其他可能性,看看这些字符串。返回类似于355715565309247的内容。不需要特殊权限,使这种方法非常方便。


(额外信息:上面给出的技术是从口袋魔术上的一篇文章中复制的。

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

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

然而,如果你需要设备标识符而不是应用程序安装标识符,博客文章继续讨论解决方案。我和谷歌的某人谈过,以便在你需要这样做的情况下得到一些额外的澄清。这是我发现的关于上述博客文章中没有提到的设备标识符:

  • ANDROID_ID是首选设备标识符。ANDROID_ID在Android<=2.1或>=2.3版本上非常可靠。只有2.2存在帖子中提到的问题。
  • 几个制造商的几个设备受到2.2中ANDROID_IDbug的影响。
  • 据我所知,所有受影响的设备都有同样的ANDROID_ID,即9774d56d682e549c。顺便说一句,这也是模拟器报告的相同设备ID。
  • 谷歌认为,OEM已经为他们的许多或大部分设备修补了这个问题,但我能够证实,至少在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 fileuuid = 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 filetry {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 fileprefs.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;}}

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

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

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

有关如何为安装应用程序的每个Android设备获取唯一标识符的详细说明,请参阅官方Android Developers Blog帖子识别应用程序安装

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

我个人认为这是可以接受的,但并不理想。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();}}

以下是Reto Meger在今年的谷歌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,并在设备被擦除甚至更换后保留下来。我计划在未来的分析中使用它(换句话说,我还没有做那一点:)。

有一些有用的信息这里

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

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

有很多不同的方法可以解决这些ANDROID_ID问题(有时可能是null,或者特定型号的设备总是返回相同的ID),其优点和缺点:

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

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

谷歌I/O上,Reto梅尔发布了一个强大的答案,说明如何实现这一点,这应该满足大多数开发人员跨安装跟踪用户的需求。Anthony Nolan在他的回答中展示了方向,但我想我应该写出完整的方法,这样其他人就可以很容易地看到如何做到这一点(我花了一段时间才弄清楚细节)。

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

让我们来看看完整的方法。首先,我们需要使用Android备份服务为我们的SharedPre的创建一个备份。首先通过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>

然后,您需要创建备份代理,并告诉它使用helper代理来共享首选项:

public class MyBackupAgent extends BackupAgentHelper {// The name of the SharedPreferences filestatic final String PREFS = "user_preferences";
// A key to uniquely identify the set of backup datastatic final String PREFS_BACKUP_KEY = "prefs";
// Allocate a helper and add it to the backup agent@Overridepublic void onCreate() {SharedPreferencesBackupHelper helper = new SharedPreferencesBackupHelper(this,          PREFS);addHelper(PREFS_BACKUP_KEY, helper);}}

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

BackupManager backupManager = new BackupManager(context);

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

  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 changesBackupManager mBackupManager = new BackupManager(context);mBackupManager.dataChanged();}}
return uniqueID;}

即使用户移动设备,此User_ID现在也将在安装中持久化。

有关此方法的更多信息,请参阅雷托说的

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

另一种方法是在没有任何权限的应用程序中使用/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 iSerialuser@creep:~$ adb shell cat /sys/class/android_usb/android0/iSerial0A3CXXXXXXXXXX5

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

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

  • GalaxyNexus
  • nexuss
  • 摩托罗拉Xoom 3G
  • 东芝AT300
  • htconev
  • 迷你MK802
  • 三星Galaxy S II

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

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

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

当设备没有电话功能时,我使用以下代码来获取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);

这是我如何生成唯一的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;}

#最后更新时间:6/2/15


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

主要问题:硬件vs软件

硬件

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

软件

  • 用户可以擦除/更改他们的ROM,如果他们是root
  • 您可以跨平台(iOS、Android、Windows和Web)跟踪用户
  • 最好的希望跟踪个人用户与他们的同意只是让他们登录(使用OAuth无缝连接)

#与Android的整体细分

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

Psuedo代码:

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发布我们所有的选择(在这个堆栈溢出问题中)。

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

  • 用户电子邮件-软件

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

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

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

  • 用户电话号码-软件

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

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

  • IMEI-硬件(只有手机,需要#0

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

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

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

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

  • 如果您有一个具有出厂重置设备的用户,则该值可能已在根目录设备上更改或更改,因此如果您正在跟踪用户安装,可能会有重复项。

  • WLAN MAC地址-硬件(需要#0

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

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

  • 蓝牙MAC地址-硬件(带蓝牙的设备,需要#0

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

  • <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将被保留或不为空。

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

如果所有其他都失败了,如果用户的API低于9(低于姜饼),重置了他们的设备,或者“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 dataString 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 entryString serial = null;try {serial = android.os.Build.class.getField("SERIAL").get(null).toString();
// Go ahead and return the serial for api => 9return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();} catch (Exception exception) {// String needs to be initializedserial = "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 identifierreturn new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();}

#新(对于带有广告和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.htmlhttp://developer.android.com/reference/com/google/android/gms/ads/identifier/AdvertisingIdClient.html

##重要:

广告ID完全取代现有的将其他标识符用于广告目的(例如使用ANDROID_ID在Setings. Secure中)当Google Play服务可用时。案例Google Play服务不可用的位置由被抛出的GooglePlayServicesNot可用性异常获取广告ID信息()。

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

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

我试图引用我从中获取信息的每个链接。如果您缺少并且需要包含在内,请评论!

谷歌播放器服务Instance ID

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

我的两分钱-NB这是一个设备唯一ID-而不是安装Android开发者博客中讨论的。

值得注意的是,@emmby提供的解决方案回退到每个应用程序ID中,因为共享首选项没有跨进程同步(参见这里这里)。所以我完全避免了这一点。

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

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 : hashpublic 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 {
@OverrideString 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 {
@OverrideString 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 {
@OverrideString 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 {
@OverrideString 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 extendsDeviceIDException {
private static final long serialVersionUID = -8940090896069484955L;
public DeviceIDNotUniqueException() {super(ANDROID_ID_BUG_MSG);}}}

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

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

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

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

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

更多信息@develper.android

复制粘贴代码

HTH

检查SystemInfo.device唯一标识符

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

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

iOS:在iOS7之前的设备上,它将返回MAC地址的哈希值。在iOS7设备上,它将是UIDevice标识符ForVendor,或者,如果由于任何原因失败,则为ASIdfierManager广告标识符。

Android OS设备的唯一设备ID作为String,使用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建议的方法,请参阅识别应用程序安装

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

谷歌实例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 contextString 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。这意味着上面的调用是一个阻塞调用——在我不科学的测试中,如果设备在线,需要1-3秒,如果离线,需要0.5-1.0秒(大概这是它在放弃并生成随机ID之前等待的时间)。这在北美使用Android 5.1.1和GPS 7.5在Nexus 5上进行了测试。

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

对于特定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 ADDRESSinterface.getHardwareAddress();}

在每个Android设备中,他们至少有一个“wlan0”接口女巫是WI-FI芯片。此代码即使在未打开WI-FI时也有效。

附言。他们是一堆其他接口,你会从包含MACS的列表中获得,但这可以在手机之间改变。

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

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

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

3个ID是:
伪ID-它是基于物理设备规范生成的
ANDROID_ID-Settings.Secure.ANDROID_ID
蓝牙地址-蓝牙适配器地址

它将返回如下内容:551 F 27 C 060712 A 72730 B 0 A 0 F 734064 B 1

注意:您可以随时向longId字符串添加更多ID。例如,Serial#. 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 bytesbyte md5Bytes[] = messageDigest.digest();
// creating a hex stringString 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 stringidentifier += Integer.toHexString(b);}
// hex string to uppercaseidentifier = identifier.toUpperCase();return identifier;} catch (Exception e) {Log.e("TAG", e.toString());}return "";}

只是提醒每个阅读寻找更多最新信息的人。使用Android O,系统管理这些ID的方式发生了一些变化。

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

tl; dr Serial将需要PHONE权限,Android ID将根据其包名和签名为不同的应用程序更改。

谷歌还整理了一份很好的文档,提供了有关何时使用硬件和软件ID的建议。

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

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

IMEI国际移动设备标识符

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

得到设备唯一标识

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

将SERIAL NUMBER作为在每个设备中唯一的字符串返回。

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

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

请注意,getEmail在大多数情况下会返回null,因为我们没有明确请求权限。

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];}};
@Overridepublic int describeContents() {return 0;}
@Overridepublic 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);}
@Overridepublic int hashCode() {int result = 17;result = 31 * result + id.hashCode();result = 31 * result + type.hashCode();return result;}
@Overridepublic 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;}
@Overridepublic 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());}
@Overridepublic int describeContents() {return 0;}
@Overridepublic 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;}

序列号是唯一的设备ID,可通过android.os.Build.SERIAL.

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只平板电脑)。

不推荐,因为deviceId可以在第三方手中用作跟踪,但这是另一种方式。

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

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姜饼以来可以通过android.os.Build.SERIAL获得。一些具有电话服务的手机也可以定义序列号。像不是所有的Android设备都有序列号一样,此解决方案不可靠。

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

    String androidId=Settings.Secure.getString(getContentResolver(),//获取内容解析资源Settings.Secure.ANDROID_ID);

但是,如果在设备上执行出厂重置,该值可能会发生变化。还有一个已知的bug,来自制造商的流行手机的每个实例都具有相同的ANDROID_ID。显然,解决方案不是100%可靠。

  1. 使用UUID。由于大多数应用程序的要求是识别特定的安装而不是物理设备,因此如果使用UUID类,获取用户的唯一ID是一个很好的解决方案。以下解决方案由谷歌的雷托·迈耶在谷歌I/O演示中提出,
SharedPreferences sharedPrefs = context.getSharedPreferences(PREF_UNIQUE_ID, Context.MODE_PRIVATE);uniqueID = sharedPrefs.getString(PREF_UNIQUE_ID, null);

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

为了完整性,以下是如何在Xamarin.Android和C#中获取Id

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

如果你不在Activity中:

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

其中context是上下文中传递的。

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

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

我是这样做的:

  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。

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

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

唯一标识符的最佳实践:

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

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

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

 AsyncTask<Void, Void, String> task = new AsyncTask<Void, Void, String>() {@Overrideprotected 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;}
@Overrideprotected void onPostExecute(String token) {Log.i(TAG, "DEVICE_ID Access token retrieved:" + token);}
};task.execute();

详细阅读完整答案这里

如果您添加: 

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

Android Lint会给你以下警告:

不建议使用getString获取设备标识符。检查信息:不建议使用这些设备标识符除了高价值的欺诈预防和先进的电话用例。对于广告用例,使用广告标识客户端$Info#getId和分析,使用实例ID#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是最简单的方法来确定设备是否是真实的,而不会产生隐私风险

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

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

path_to_android_sdk_on_your_pc/临时演员/谷歌/market_licensing/库

在项目中包含库(您可以简单地复制其文件)。

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

用户ID将在processServerResponse()函数中为您提供:

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

接下来,您需要使用策略构造LicenseChecker并调用checkAccess()函数。使用MainActivity.java作为如何做到这一点的示例。MainActivity.java位于此文件夹中:

path_to_android_sdk_on_your_pc/临时演员/谷歌/market_licensing/样品/s rc/com/例子/Android/市场/许可

不要忘记将CHECK_LICENSE权限添加到您的AndroidManifest.xml.

更多关于授权库:https://developer.android.com/google/play/licensing

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

android.telephony.TelephonyManager.getDeviceId()

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

您需要在AndroidManifest.xml中获得以下权限:

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

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

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

所以如果您正在寻找2020年之后的解决方案

这里有几件事要记住:

所有基于硬件的标识符(IMEI、MAC、序列号等)对于非谷歌设备(Pixels和Nexuse除外)都是不可靠的,统计上它们是0。因此官方Android标识符最佳实践明确指出:

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

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

例如CVE-2018-9489影响上面提到的所有基于WIFI的技术。

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

简单地说:不要用那些技巧

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

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

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

所以也别用它

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

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

它对于设备上的应用程序安装是独一无二的,因此当用户卸载应用程序时,它就会消失,因此它不是100%可靠,但它是下一个最好的东西。

说明从今天开始,FirebaseInstanceId已被弃用,您应该使用FirebaseInstallations代替。

要使用FirebaseInstallations,请将最新的Firebase消息传递依赖项添加到您的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}});

如果您需要在远程服务器上存储设备标识,请不要按原样(纯文本)存储它,而是盐杂碎

今天,这不仅是一个最佳实践,你实际上必须根据欧盟数据保护法-标识符和类似的法规通过法律来做。

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)

使用下面的函数获取DEVICEUUID,型号与品牌名称及其版本号

在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中查看您的日志猫。它看起来像下面这样:

在此处输入图片描述

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

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

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

}

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 dataString 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 entryString serial = null;try {serial = android.os.Build.class.getField("SERIAL").get(null).toString();
// Go ahead and return the serial for api => 9return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();} catch (Exception exception) {// String needs to be initializedserial = "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 identifierreturn new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();}
}

我正在使用这个,它在过去的6年里一直在工作。

这里是图书馆:https://github.com/nillbiplob/AppBajarLIB

经过大量的搜索,我意识到没有确定的方法来获得唯一的ID。

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

我所做的是:

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

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

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

此示例演示如何在Android中获取和存储设备ID,但我使用静态编程语言。

      val textView: TextView = findViewById(R.id.textView)val uniqueId: String = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)textView.text = "Device ID: $uniqueId"

生成设备标识符

 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

输入图片描述