Android设备是否有唯一的ID,如果有的话,使用Java访问它的简单方法是什么?
Settings.Secure#ANDROID_ID
返回一个64位十六进制字符串,对于每个用户来说是独一无二的 Android ID。
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
的问题已得到解决,我认为这种方法不再必要。请看 Anthony的回答。
完全公开透明地说,我的应用程序最初使用以下方法,但现在不再使用此方法,我们现在使用emmby的答案中链接到的Android Developer Blog条目中概述的方法(即生成并保存一个UUID#randomUUID()
)。
有很多关于此问题的答案,其中大多数只能“有时”起作用,不幸的是,那样还不够好。
根据我对设备的测试(所有手机,至少一个未激活):
TelephonyManager.getDeviceId()
的值。TelephonyManager.getSimSerialNumber()
的值。getSimSerialNumber()
的 null 值(如预期所示)。ANDROID_ID
的值。ANDROID_ID
和 TelephonyManager.getDeviceId()
返回相同的值(或这些值的派生值)。因此,如果您想要一些特定于设备本身的东西,则 TM.getDeviceId()
应该足够。显然,有些用户比其他用户更偏执,因此对一个或多个这些标识符进行哈希处理可能很有用,以便字符串仍然对设备几乎唯一,但不明确地标识用户的实际设备。例如,使用 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;
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
。如果将其存储在数据库中,返回的字符串长度为36个字符。 - Richard#最近更新:2015年6月2日
在阅读了有关创建唯一ID的每篇Stack Overflow文章、Google开发者博客和Android文档后,我觉得“Pseudo ID”是最好的选择。
#Android的总体分析
###- 对于API >= 9/10(99.5%的Android设备),保证唯一性(包括已获取root权限的设备) ###- 不需要额外的权限
伪代码:
if API >= 9/10: (99.5% of devices)
return unique ID containing serial id (rooted devices may be different)
else
return the unique ID of build information (may overlap data - API < 9)
感谢@stansult发布了这个Stack Overflow问题中的所有选项(链接)。
##选项列表 - 使用它们的原因/不使用它们的原因:
用户电子邮件 - 软件
用户可以更改电子邮件 - 非常不可能
API 5+ <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 - 硬件 (仅适用于手机,需要android.permission.READ_PHONE_STATE
)
大多数用户讨厌它在权限中显示“电话呼叫”。有些用户会因为认为您只是窃取他们的个人信息而给出差评,但实际上您只想追踪设备安装。显然您是在收集数据。
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
Android ID - 硬件 (可能为空,可能在出厂重置时更改,可能在root设备上被更改)
由于它可能为'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时,我们可以仅基于Android API提供给我们的信息创建“伪唯一ID”,而不需要使用额外的权限。这样,我们可以尊重用户并尽力提供良好的用户体验。
使用伪唯一ID,你真正遇到的问题只是可能会有基于相似设备的重复项。您可以调整组合方法以使其更加唯一;然而,一些开发人员需要跟踪设备安装情况,这将起到作用,或者基于相似设备的性能。
##API >= 9:
如果他们的Android设备是API 9或更高版本,则由于“Build.SERIAL”字段,这是保证唯一的。
请记住,从技术上讲,您实际上只错过了大约0.5%的用户(具有API <9)。因此,您可以专注于其余部分:这是99.5%的用户!
##API < 9:
如果用户的Android设备低于API 9;希望他们没有进行出厂设置,他们的“Secure.ANDROID_ID”将被保留或不为“null”。(请参见http://developer.android.com/about/dashboards/index.html)
##如果其他所有方法都失败:
如果所有其他方法都失败,并且用户确实低于API 9(低于Gingerbread),已重置其设备或“Secure.ANDROID_ID”返回“null”,那么返回的ID将仅基于其Android设备信息。这是冲突可能发生的地方。
更改:
请查看下面的方法:
/**
* 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://dev59.com/V3E85IYBdhLWcg3wbS1h#4789483
// 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://dev59.com/V3E85IYBdhLWcg3wbS1h#2853253
// 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();
}
# 新(适用于带有广告和Google Play服务的应用程序):
来自Google Play开发者控制台:
从2014年8月1日开始,Google Play开发者计划政策要求所有新应用程序上传和更新使用广告ID代替任何其他持久标识符用于任何广告目的。了解更多
实现:
权限:
<uses-permission android:name="android.permission.INTERNET" />
代码:
import com.google.android.gms.ads.identifier.AdvertisingIdClient;
import com.google.android.gms.ads.identifier.AdvertisingIdClient.Info;
import com.google.android.gms.common.GooglePlayServicesAvailabilityException;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import java.io.IOException;
...
// Do not call this function from the main thread. Otherwise,
// an IllegalStateException will be thrown.
public void getIdThread() {
Info adInfo = null;
try {
adInfo = AdvertisingIdClient.getAdvertisingIdInfo(mContext);
} catch (IOException exception) {
// Unrecoverable error connecting to Google Play services (e.g.,
// the old version of the service doesn't support getting AdvertisingId).
} catch (GooglePlayServicesAvailabilityException exception) {
// Encountered a recoverable error connecting to Google Play services.
} catch (GooglePlayServicesNotAvailableException exception) {
// Google Play services is not available entirely.
}
final String id = adInfo.getId();
final boolean isLAT = adInfo.isLimitAdTrackingEnabled();
}
来源/文档:
http://developer.android.com/google/play-services/id.html http://developer.android.com/reference/com/google/android/gms/ads/identifier/AdvertisingIdClient.html
##重要提示:
当Google Play服务可用时,广告ID将完全取代其他广告目的的标识符的现有使用方式(例如在Settings.Secure中使用ANDROID_ID)。getAdvertisingIdInfo()抛出GooglePlayServicesNotAvailableException表示Google Play服务不可用。
##警告,用户可以重置:
http://en.kioskea.net/faq/34732-android-reset-your-advertising-id
我已经尽力引用了我参考的每个链接。如果您被遗漏了并且需要包含在内,请评论!
正如 Dave Webb 所提到的,在 Android Developer Blog 的一篇文章中介绍了相关内容。他们首选跟踪应用程序的安装情况而不是设备,这对大多数用例都有效。该博客文章将向您展示使其工作所需的必要代码,建议您查看。
但是,该博客文章继续讨论了解决方案,如果您需要设备标识符而不是应用程序安装标识符。我与 Google 的某位人员交谈,以获取有关需要这样做时几个项目的其他澄清。以下是我在上述博客文章中没有提到的有关设备标识符的发现:
根据 Google 的建议,我实现了一个类,将使用适当的 ANDROID_ID 作为种子生成每个设备的唯一 UUID,必要时回退到 TelephonyManager.getDeviceId(),如果失败,则退回到在应用重新启动时保留的随机生成的唯一 UUID(但不是应用重新安装)。
请注意,对于必须回退到设备 ID 的设备,唯一 ID将跨越出厂设置重置而保持不变。这是需要注意的事项。如果您需要确保恢复出厂设置会重置您的唯一 ID,则可能希望直接退回随机 UUID 而不是设备 ID。
再次说明,此代码是针对设备 ID 而非应用程序安装 ID。对于大多数情况,应用程序安装 ID 可能是您要寻找的内容。但是,如果确实需要设备 ID,则以下代码可能适用于您。
import android.content.Context;
import android.content.SharedPreferences;
import android.provider.Settings.Secure;
import android.telephony.TelephonyManager;
import java.io.UnsupportedEncodingException;
import java.util.UUID;
public class DeviceUuidFactory {
protected static final String PREFS_FILE = "device_id.xml";
protected static final String PREFS_DEVICE_ID = "device_id";
protected volatile static UUID uuid;
public DeviceUuidFactory(Context context) {
if (uuid == null) {
synchronized (DeviceUuidFactory.class) {
if (uuid == null) {
final SharedPreferences prefs = context
.getSharedPreferences(PREFS_FILE, 0);
final String id = prefs.getString(PREFS_DEVICE_ID, null);
if (id != null) {
// Use the ids previously computed and stored in the
// prefs file
uuid = UUID.fromString(id);
} else {
final String androidId = Secure.getString(
context.getContentResolver(), Secure.ANDROID_ID);
// Use the Android ID unless it's broken, in which case
// fallback on deviceId,
// unless it's not available, then fallback on a random
// number which we store to a prefs file
try {
if (!"9774d56d682e549c".equals(androidId)) {
uuid = UUID.nameUUIDFromBytes(androidId
.getBytes("utf8"));
} else {
final String deviceId = (
(TelephonyManager) context
.getSystemService(Context.TELEPHONY_SERVICE))
.getDeviceId();
uuid = deviceId != null ? UUID
.nameUUIDFromBytes(deviceId
.getBytes("utf8")) : UUID
.randomUUID();
}
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
// Write the value out to the prefs file
prefs.edit()
.putString(PREFS_DEVICE_ID, uuid.toString())
.commit();
}
}
}
}
}
/**
* Returns a unique UUID for the current android device. As with all UUIDs,
* this unique ID is "very highly likely" to be unique across all Android
* devices. Much more so than ANDROID_ID is.
*
* The UUID is generated by using ANDROID_ID as the base key if appropriate,
* falling back on TelephonyManager.getDeviceID() if ANDROID_ID is known to
* be incorrect, and finally falling back on a random UUID that's persisted
* to SharedPreferences if getDeviceID() does not return a usable value.
*
* In some rare circumstances, this ID may change. In particular, if the
* device is factory reset a new device ID may be generated. In addition, if
* a user upgrades their phone from certain buggy implementations of Android
* 2.2 to a newer, non-buggy version of Android, the device ID may change.
* Or, if a user uninstalls your app on a device that has neither a proper
* Android ID nor a Device ID, this ID may change on reinstallation.
*
* Note that if the code falls back on using TelephonyManager.getDeviceId(),
* the resulting ID will NOT change after a factory reset. Something to be
* aware of.
*
* Works around a bug in Android 2.2 for many devices when using ANDROID_ID
* directly.
*
* @see http://code.google.com/p/android/issues/detail?id=10603
*
* @return a UUID that may be used to uniquely identify your device for most
* purposes.
*/
public UUID getDeviceUuid() {
return uuid;
}
}
以下是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 的演讲中介绍),您应该拥有一个标识,它与用户相关联,并在设备被擦除甚至替换后仍然存在。我计划在以后的分析中使用它(换句话说,我还没有完成这部分的工作:)。
CVE-2018-9489
影响上述所有基于WIFI的技术。这不仅使这些标识符不可靠,而且在许多情况下也无法访问。因此,简单地说:不要使用这些技术。这里的许多其他答案建议使用AdvertisingIdClient
,但这也是不兼容的,因为它的设计仅用于广告配置文件。这也在官方参考中说明。
仅在用户画像或广告使用情况下使用广告ID
它不仅对设备识别不可靠,而且您还必须遵循有关广告跟踪的用户隐私政策,该政策明确规定用户可以随时重置或阻止它。FirebaseInstanceId
已弃用,您应改用FirebaseInstallations
。FirebaseInstallations
,请将最新的firebase-messaging依赖项添加到gradle中。implementation 'com.google.firebase:firebase-messaging:23.0.0'
使用以下代码获取Firebase ID:
FirebaseInstallations.getInstance().getId().addOnCompleteListener(task -> {
if (task.isSuccessful()) {
String firebaseIdentifier = task.getResult();
// Do what you need with firebaseIdentifier
}
});
另外,您可能需要考虑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.permission.ACCESS_WIFI_STATE
权限。 - ohhorob02:00:00:00:00:00
。 - Behrouz.M这里有相当有用的信息(点击此处).
它涵盖了五种不同的ID类型:
android.permission.READ_PHONE_STATE
权限)android.permission.ACCESS_WIFI_STATE
权限)android.permission.BLUETOOTH
权限)官方的Android开发者博客现在有一篇完整的文章专门讨论这个主题,识别应用程序安装。
http://developer.android.com/google/backup/signup.html
注册您的应用程序。<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's talk。
有关如何实现备份代理的完整详细信息,请参阅数据备份。我特别推荐底部的测试部分,因为备份不会立即发生,所以要进行测试,您必须强制进行备份。
ANDROID_ID
,请务必阅读这个答案和这个漏洞。 - Dheeraj Vepakomma