[英]Is there a unique Android device ID?
Android 设备是否有唯一 ID,如果有,使用 Java 访问它的简单方法是什么?
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
更新:从最近的 Android 版本开始, ANDROID_ID
许多问题已经解决,我相信这种方法不再需要了。 请看看安东尼的回答。
完全披露:我的应用程序最初使用以下方法但不再使用这种方法,我们现在使用Android 开发者博客条目中概述的方法, emmby 的答案链接到(即,生成和保存UUID#randomUUID()
)。
这个问题有很多答案,其中大部分只在“某些”时间有效,不幸的是,这还不够好。
根据我对设备的测试(所有手机,至少其中一部未激活):
TelephonyManager.getDeviceId()
的值TelephonyManager.getSimSerialNumber()
的值getSimSerialNumber()
返回 null(如预期)ANDROID_ID
的值ANDROID_ID
和TelephonyManager.getDeviceId()
返回相同的值(或相同值的派生)。 所以,如果你想要独一无二的设备本身,一些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;
#上次更新:6/2/15
在阅读了每一篇关于创建唯一 ID 的 Stack Overflow 帖子、Google 开发者博客和 Android 文档后,我觉得“伪 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' 并更改它的值,但这意味着它不再是唯一的。
如果您的用户具有恢复出厂设置的设备,则该值可能已在有根设备上更改或更改,因此如果您正在跟踪用户安装,则可能会有重复的条目。
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”将被保留或不为“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://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 开发者计划政策要求上传和更新全新的应用程序以使用广告 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)。 Google Play 服务不可用的情况由 getAdvertisingIdInfo() 抛出的 GooglePlayServicesNotAvailableException 指示。
##警告,用户可以重置:
http://en.kioskea.net/faq/34732-android-reset-your-advertising-id
我试图引用我从中获取信息的每个链接。 如有遗漏需要补充,请留言!
正如 Dave Webb 所提到的, Android Developer Blog 有一篇文章介绍了这一点。 他们首选的解决方案是跟踪应用安装而不是设备,这对大多数用例都适用。 这篇博文将向您展示实现该功能所需的代码,我建议您查看一下。
但是,如果您需要设备标识符而不是应用程序安装标识符,博客文章继续讨论解决方案。 我与 Google 的某人进行了交谈,以便在您需要时对一些项目进行额外说明。 以下是我发现的有关上述博客文章中未提及的设备标识符的信息:
根据谷歌的建议,我实现了一个将为每个设备生成唯一 UUID 的类,在适当的情况下使用 ANDROID_ID 作为种子,必要时回退到 TelephonyManager.getDeviceId(),如果失败,则求助于随机生成的唯一 UUID这在应用程序重新启动(但不是应用程序重新安装)中持续存在。
请注意,对于必须回退到设备 ID 的设备,唯一 ID将在出厂重置后保持不变。 这是需要注意的。 如果您需要确保恢复出厂设置会重置您的唯一 ID,您可能需要考虑直接回退到随机 UUID 而不是设备 ID。
同样,此代码用于设备 ID,而不是应用安装 ID。 在大多数情况下,应用安装 ID 可能就是您要寻找的。 但是,如果您确实需要设备 ID,那么以下代码可能适合您。
import android.content.Context;
import android.content.SharedPreferences;
import android.provider.Settings.Secure;
import android.telephony.TelephonyManager;
import java.io.UnsupportedEncodingException;
import java.util.UUID;
public class DeviceUuidFactory {
protected static final String PREFS_FILE = "device_id.xml";
protected static final String PREFS_DEVICE_ID = "device_id";
protected volatile static UUID uuid;
public DeviceUuidFactory(Context context) {
if (uuid == null) {
synchronized (DeviceUuidFactory.class) {
if (uuid == null) {
final SharedPreferences prefs = context
.getSharedPreferences(PREFS_FILE, 0);
final String id = prefs.getString(PREFS_DEVICE_ID, null);
if (id != null) {
// Use the ids previously computed and stored in the
// prefs file
uuid = UUID.fromString(id);
} else {
final String androidId = Secure.getString(
context.getContentResolver(), Secure.ANDROID_ID);
// Use the Android ID unless it's broken, in which case
// fallback on deviceId,
// unless it's not available, then fallback on a random
// number which we store to a prefs file
try {
if (!"9774d56d682e549c".equals(androidId)) {
uuid = UUID.nameUUIDFromBytes(androidId
.getBytes("utf8"));
} else {
final String deviceId = (
(TelephonyManager) context
.getSystemService(Context.TELEPHONY_SERVICE))
.getDeviceId();
uuid = deviceId != null ? UUID
.nameUUIDFromBytes(deviceId
.getBytes("utf8")) : UUID
.randomUUID();
}
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
// Write the value out to the prefs file
prefs.edit()
.putString(PREFS_DEVICE_ID, uuid.toString())
.commit();
}
}
}
}
}
/**
* Returns a unique UUID for the current android device. As with all UUIDs,
* this unique ID is "very highly likely" to be unique across all Android
* devices. Much more so than ANDROID_ID is.
*
* The UUID is generated by using ANDROID_ID as the base key if appropriate,
* falling back on TelephonyManager.getDeviceID() if ANDROID_ID is known to
* be incorrect, and finally falling back on a random UUID that's persisted
* to SharedPreferences if getDeviceID() does not return a usable value.
*
* In some rare circumstances, this ID may change. In particular, if the
* device is factory reset a new device ID may be generated. In addition, if
* a user upgrades their phone from certain buggy implementations of Android
* 2.2 to a newer, non-buggy version of Android, the device ID may change.
* Or, if a user uninstalls your app on a device that has neither a proper
* Android ID nor a Device ID, this ID may change on reinstallation.
*
* Note that if the code falls back on using TelephonyManager.getDeviceId(),
* the resulting ID will NOT change after a factory reset. Something to be
* aware of.
*
* Works around a bug in Android 2.2 for many devices when using ANDROID_ID
* directly.
*
* @see http://code.google.com/p/android/issues/detail?id=10603
*
* @return a UUID that may be used to uniquely identify your device for most
* purposes.
*/
public UUID getDeviceUuid() {
return uuid;
}
}
这是 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。我计划使用这个在未来的分析中(换句话说,我还没有做过那一点:)。
您还可以考虑 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
有相当有用的信息在这里。
它涵盖五种不同的 ID 类型:
android.permission.READ_PHONE_STATE
)android.permission.ACCESS_WIFI_STATE
)android.permission.BLUETOOTH
)这是一个简单的问题,没有简单的答案。
更重要的是,这里所有现有的答案是否过时或不可靠。
因此,如果您正在寻找 2020 年的解决方案。
以下是一些需要注意的事项:
所有基于硬件的标识符(SSAID、IMEI、MAC 等)对于非谷歌的设备(Pixels 和 Nexuses 除外的所有设备)都不可靠,这些设备占全球活跃设备的 50% 以上。 因此,官方Android 标识符最佳实践明确指出:
避免使用硬件标识符,例如 SSAID(Android ID)、IMEI、MAC 地址等...
这使得上面的大多数答案无效。 同样由于不同的android安全更新,其中一些需要更新和更严格的运行时权限,用户可以简单地拒绝。
例如, CVE-2018-9489
会影响上述所有基于 WIFI 的技术。
这使得这些标识符不仅不可靠,而且在许多情况下也无法访问。
所以简单地说:不要使用这些技术。
这里的许多其他答案都建议使用AdvertisingIdClient
,这也是不兼容的,因为它的设计应仅用于广告分析。 官方参考资料中也有说明
仅将广告 ID 用于用户分析或广告用例
它不仅对设备识别不可靠,而且您还必须遵守有关广告跟踪政策的用户隐私,其中明确规定用户可以随时重置或阻止它。
所以也不要使用它。
由于您无法拥有所需的静态全局唯一且可靠的设备标识符。 Android的官方参考建议:
对于所有其他用例,尽可能使用 FirebaseInstanceId 或私人存储的 GUID ,支付欺诈预防和电话除外。
它对于设备上的应用程序安装来说是独一无二的,所以当用户卸载应用程序时 - 它会被清除,所以它不是 100% 可靠的,但它是次优的。
要使用FirebaseInstanceId
请将最新的 firebase -messaging 依赖项添加到您的 gradle 中
implementation 'com.google.firebase:firebase-messaging:20.2.4'
并在后台线程中使用以下代码:
String reliableIdentifier = FirebaseInstanceId.getInstance().getId();
如果您需要在远程服务器上存储设备标识,则不要按原样存储(纯文本),而是使用 salt进行哈希存储。
今天,这不仅是最佳实践,您实际上还必须根据GDPR - 标识符和类似规定依法执行。
官方 Android 开发者博客现在有一篇关于这个主题的完整文章, 识别应用程序安装。
在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>
然后,您需要创建备份代理并告诉它使用共享首选项的帮助代理:
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);
}
}
要完成备份,您需要在主活动中创建一个 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 的演讲。
有关如何实施备份代理的完整详细信息,请参阅 数据备份。 我特别推荐底部的测试部分,因为备份不会立即发生,因此要进行测试,您必须强制备份。
我认为这肯定是为唯一 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上的一篇文章中复制的。)
以下代码使用隐藏的 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) {
}
使用下面的代码,您可以以字符串的形式获取 Android 操作系统设备的唯一设备 ID。
deviceId = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID);
API 级别 9 (Android 2.3 - Gingerbread) 中的Build
类中添加了一个Serial字段。 文档说它代表硬件序列号。 因此它应该是唯一的,如果它存在于设备上。
我不知道 API 级别 >= 9 的所有设备是否实际支持(= 非空)。
我要补充的一件事 - 我有一种独特的情况。
使用:
deviceId = Secure.getString(this.getContext().getContentResolver(), Secure.ANDROID_ID);
事实证明,即使我的 Viewsonic G Tablet 报告的 DeviceID 不是 Null,但每个 G Tablet 报告的数字都是相同的。
使玩“口袋帝国”变得有趣,它使您可以根据“唯一”DeviceID 即时访问某人的帐户。
我的设备没有蜂窝无线电。
有关如何为安装应用程序的每个 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();
}
}
在类文件中添加以下代码:
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" />
有很多不同的方法可以解决这些ANDROID_ID
问题(有时可能为null
或特定型号的设备总是返回相同的 ID),各有利弊:
我自己更喜欢为 Android 使用现有的 OpenUDID 实现(请参阅https://github.com/ylechelle/OpenUDID )(请参阅https://github.com/vieux/OpenUDID )。 很容易集成并利用ANDROID_ID
与上述问题的回退。
TelephonyManager
和ANDROID_ID
作为字符串的 Android 操作系统设备的唯一设备 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 建议的方法,请参阅识别应用安装。
这里有 30 多个答案,有些是相同的,有些是独一无二的。 这个答案是基于其中的几个答案。 其中之一是@Lenn Dolling 的回答。
它结合了 3 个 ID 并创建了一个 32 位的十六进制字符串。 它对我来说非常有效。
3个ID是:
Pseudo-ID - 它是根据物理设备规格生成的
ANDROID_ID - Settings.Secure.ANDROID_ID
蓝牙地址- 蓝牙适配器地址
它将返回如下内容: 551F27C060712A72730B0A0F734064B1
注意:您始终可以向longId
字符串添加更多 ID。 例如,序列号。 wifi适配器地址。 串号。 通过这种方式,您可以使每台设备更加独特。
@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 "";
}
IMEI怎么样。 这对于 Android 或其他移动设备来说是独一无二的。
这是我生成唯一 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;
}
我的两分钱 - 注意这是一个设备(错误)唯一 ID - 不是Android 开发人员博客中讨论的安装ID 。
请注意,@emmby 提供的解决方案回退到每个应用程序 ID,因为 SharedPreferences 不会跨进程同步(请参阅此处和此处)。 所以我完全避免了这个。
相反,我在枚举中封装了获取(设备)ID 的各种策略——更改枚举常量的顺序会影响获取 ID 的各种方式的优先级。 返回第一个非空 ID 或抛出异常(根据不赋予 null 含义的良好 Java 实践)。 因此,例如,我首先使用 TELEPHONY - 但一个不错的默认选择是 ANDROID_ID测试版:
import android.Manifest.permission;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.wifi.WifiManager;
import android.provider.Settings.Secure;
import android.telephony.TelephonyManager;
import android.util.Log;
// TODO : hash
public final class DeviceIdentifier {
private DeviceIdentifier() {}
/** @see http://code.google.com/p/android/issues/detail?id=10603 */
private static final String ANDROID_ID_BUG_MSG = "The device suffers from "
+ "the Android ID bug - its ID is the emulator ID : "
+ IDs.BUGGY_ANDROID_ID;
private static volatile String uuid; // volatile needed - see EJ item 71
// need lazy initialization to get a context
/**
* Returns a unique identifier for this device. The first (in the order the
* enums constants as defined in the IDs enum) non null identifier is
* returned or a DeviceIDException is thrown. A DeviceIDException is also
* thrown if ignoreBuggyAndroidID is false and the device has the Android ID
* bug
*
* @param ctx
* an Android constant (to retrieve system services)
* @param ignoreBuggyAndroidID
* if false, on a device with the android ID bug, the buggy
* android ID is not returned instead a DeviceIDException is
* thrown
* @return a *device* ID - null is never returned, instead a
* DeviceIDException is thrown
* @throws DeviceIDException
* if none of the enum methods manages to return a device ID
*/
public static String getDeviceIdentifier(Context ctx,
boolean ignoreBuggyAndroidID) throws DeviceIDException {
String result = uuid;
if (result == null) {
synchronized (DeviceIdentifier.class) {
result = uuid;
if (result == null) {
for (IDs id : IDs.values()) {
try {
result = uuid = id.getId(ctx);
} catch (DeviceIDNotUniqueException e) {
if (!ignoreBuggyAndroidID)
throw new DeviceIDException(e);
}
if (result != null) return result;
}
throw new DeviceIDException();
}
}
}
return result;
}
private static enum IDs {
TELEPHONY_ID {
@Override
String getId(Context ctx) {
// TODO : add a SIM based mechanism ? tm.getSimSerialNumber();
final TelephonyManager tm = (TelephonyManager) ctx
.getSystemService(Context.TELEPHONY_SERVICE);
if (tm == null) {
w("Telephony Manager not available");
return null;
}
assertPermission(ctx, permission.READ_PHONE_STATE);
return tm.getDeviceId();
}
},
ANDROID_ID {
@Override
String getId(Context ctx) throws DeviceIDException {
// no permission needed !
final String andoidId = Secure.getString(
ctx.getContentResolver(),
android.provider.Settings.Secure.ANDROID_ID);
if (BUGGY_ANDROID_ID.equals(andoidId)) {
e(ANDROID_ID_BUG_MSG);
throw new DeviceIDNotUniqueException();
}
return andoidId;
}
},
WIFI_MAC {
@Override
String getId(Context ctx) {
WifiManager wm = (WifiManager) ctx
.getSystemService(Context.WIFI_SERVICE);
if (wm == null) {
w("Wifi Manager not available");
return null;
}
assertPermission(ctx, permission.ACCESS_WIFI_STATE); // I guess
// getMacAddress() has no java doc !!!
return wm.getConnectionInfo().getMacAddress();
}
},
BLUETOOTH_MAC {
@Override
String getId(Context ctx) {
BluetoothAdapter ba = BluetoothAdapter.getDefaultAdapter();
if (ba == null) {
w("Bluetooth Adapter not available");
return null;
}
assertPermission(ctx, permission.BLUETOOTH);
return ba.getAddress();
}
}
// TODO PSEUDO_ID
// http://www.pocketmagic.net/2011/02/android-unique-device-id/
;
static final String BUGGY_ANDROID_ID = "9774d56d682e549c";
private final static String TAG = IDs.class.getSimpleName();
abstract String getId(Context ctx) throws DeviceIDException;
private static void w(String msg) {
Log.w(TAG, msg);
}
private static void e(String msg) {
Log.e(TAG, msg);
}
}
private static void assertPermission(Context ctx, String perm) {
final int checkPermission = ctx.getPackageManager().checkPermission(
perm, ctx.getPackageName());
if (checkPermission != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Permission " + perm + " is required");
}
}
// =========================================================================
// Exceptions
// =========================================================================
public static class DeviceIDException extends Exception {
private static final long serialVersionUID = -8083699995384519417L;
private static final String NO_ANDROID_ID = "Could not retrieve a "
+ "device ID";
public DeviceIDException(Throwable throwable) {
super(NO_ANDROID_ID, throwable);
}
public DeviceIDException(String detailMessage) {
super(detailMessage);
}
public DeviceIDException() {
super(NO_ANDROID_ID);
}
}
public static final class DeviceIDNotUniqueException extends
DeviceIDException {
private static final long serialVersionUID = -8940090896069484955L;
public DeviceIDNotUniqueException() {
super(ANDROID_ID_BUG_MSG);
}
}
}
另一种方法是在没有任何权限的情况下在应用程序中使用/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 文件并读出字符即可。 请确保将其包装在异常处理程序中,因为并非所有设备都有此文件。
至少已知以下设备具有该文件世界可读:
您还可以查看我的博客文章Leaking Android hardware serial number to unprivileged apps ,在那里我讨论了哪些其他文件可用于信息。
我使用以下代码获取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);
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;
}
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
谷歌似乎打算使用这个 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 ADDRESS
interface.getHardwareAddress();
}
在每个 Android 设备中,它们至少有一个“wlan0”接口,即 WI-FI 芯片。 即使未打开 WI-FI,此代码也能工作。
PS 他们是一堆其他接口,您将从包含 MACS 的列表中获得但是这可以在手机之间改变。
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
给您的用户,这很难证明您所创建的应用程序类型是合理的。
自 Android 2.3 Gingerbread 起,没有电话服务的设备(如平板电脑)必须报告可通过android.os.Build.SERIAL
获得的唯一设备 ID。 某些具有电话服务的电话还可以定义序列号。 与并非所有 Android 设备都有序列号一样,此解决方案并不可靠。
在设备首次启动时,会生成并存储一个随机值。 此值可通过Settings.Secure.ANDROID_ID
。 它是一个 64 位数字,应该在设备的生命周期内保持不变。 ANDROID_ID
似乎是唯一设备标识符的不错选择,因为它可用于智能手机和平板电脑。 要检索该值,您可以使用以下代码,
String androidId = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
但是,如果在设备上执行恢复出厂设置,该值可能会更改。 制造商的流行手机也存在一个已知错误,其中每个实例都具有相同的ANDROID_ID
。 显然,该解决方案并非 100% 可靠。
SharedPreferences sharedPrefs = context.getSharedPreferences(
PREF_UNIQUE_ID, Context.MODE_PRIVATE);
uniqueID = sharedPrefs.getString(PREF_UNIQUE_ID, null);
更新:选项#1和#2在 android 10 之后不再可用,因为谷歌的隐私更新。 因为选项 2 和 3 需要关键权限。
了解 Android 设备中可用的唯一 ID。 使用此官方指南。
唯一标识符的最佳实践:
IMEI、Mac 地址、实例 ID、GUID、SSAID、广告 ID、用于验证设备的 Safety Net API。
https://developer.android.com/training/articles/user-data-ids
Android 设备是否有唯一 ID,如果有,使用 Java 访问它的简单方法是什么?
更具体地说,是Settings.Secure.ANDROID_ID
。 这是设备首次启动时生成并存储的64位数量。 擦拭设备时将其重置。
对于唯一的设备标识符, ANDROID_ID
似乎是一个不错的选择。 有缺点:首先,它在2.2之前的Android版本(“Froyo”).
上并非100%可靠(“Froyo”).
而且,在一家主要制造商的流行手机中,至少有一个广为人知的错误,其中每个实例都具有相同的ANDROID_ID。
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"/>
只是为每个阅读寻找更多最新信息的人提个醒。 在 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
通常,我为我的应用程序使用设备唯一 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;
}
几年前我遇到过这个问题,并且已经学会了基于各种答案来实现一个通用的解决方案。
我已经在实际产品中使用通用解决方案多年。 到目前为止,它对我很有用。 这是基于各种提供的答案的代码片段。
请注意,大多数情况下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];
}
};
@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;
}
不推荐,因为 deviceId 可以在 3rd 方手中用作跟踪,但这是另一种方式。
@SuppressLint("HardwareIds")
private String getDeviceID() {
deviceId = Settings.Secure.getString(getApplicationContext().getContentResolver(),
Settings.Secure.ANDROID_ID);
return deviceId;
}
这是获得 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();
在这里详细阅读完整答案:
借助以下功能获取设备 UUID、型号和品牌名称及其版本号。
在 Android 10 中完美运行,无需允许读取手机状态权限。
代码片段:
private void fetchDeviceInfo() {
String uniquePseudoID = "35" +
Build.BOARD.length() % 10 +
Build.BRAND.length() % 10 +
Build.DEVICE.length() % 10 +
Build.DISPLAY.length() % 10 +
Build.HOST.length() % 10 +
Build.ID.length() % 10 +
Build.MANUFACTURER.length() % 10 +
Build.MODEL.length() % 10 +
Build.PRODUCT.length() % 10 +
Build.TAGS.length() % 10 +
Build.TYPE.length() % 10 +
Build.USER.length() % 10;
String serial = Build.getRadioVersion();
String uuid=new UUID(uniquePseudoID.hashCode(), serial.hashCode()).toString();
String brand=Build.BRAND;
String modelno=Build.MODEL;
String version=Build.VERSION.RELEASE;
Log.e(TAG, "fetchDeviceInfo: \n "+
"\n uuid is : "+uuid+
"\n brand is: "+brand+
"\n model is: "+modelno+
"\n version is: "+version);
}
调用上面的函数并检查上面代码的输出。 请在 android studio 中查看您的日志猫。 它看起来像下面:
为了包含Android 9,我只有一个仍然可以工作的想法(可能)不违反任何条款,需要权限,并且可以跨安装和应用程序工作。
涉及服务器的指纹识别应该能够唯一地识别设备。 硬件信息 + 已安装的应用程序和安装时间的组合应该可以解决问题。 除非卸载并再次安装应用程序,否则首次安装时间不会改变。 但这必须对设备上的所有应用程序进行,以便无法识别设备(即在恢复出厂设置后)。
这就是我将如何去做:
这是从 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();
}
}
注意:这是一种未经测试和未经证实的方法! 我相信它会起作用,但我也很确定,如果这种情况流行起来,他们会以一种或另一种方式关闭它。
如果添加:
Settings.Secure.getString(context.contentResolver,
Settings.Secure.ANDROID_ID)
Android Lint 会给你以下警告:
不推荐使用 getString 来获取设备标识符。 检查信息:除了用于高价值欺诈预防和高级电话用例外,不建议使用这些设备标识符。 对于广告用例,请使用 AdvertisingIdClient$Info#getId,对于分析,请使用 InstanceId#getId。
所以,你应该避免使用它。
1:避免使用硬件标识符。
在大多数用例中,您可以避免使用硬件标识符,例如 SSAID (Android ID) 和 IMEI,而不限制所需的功能。
2:仅将广告 ID 用于用户分析或广告用例。
使用广告 ID 时,请始终尊重用户对广告跟踪的选择。 此外,请确保标识符无法连接到个人身份信息 (PII),并避免桥接广告 ID 重置。
3:对于所有其他用例,尽可能使用实例 ID 或私有存储的 GUID,支付欺诈预防和电话除外。
对于绝大多数非广告用例,实例 ID 或 GUID 就足够了。
4:使用适合您的用例的 API 以最大程度地降低隐私风险。
使用 DRM API 进行高价值内容保护,使用 SafetyNet API 进行滥用保护。 SafetyNet API 是在不产生隐私风险的情况下确定设备是否为真品的最简单方法。
String SERIAL_NUMER = Build.SERIAL;
将序列号作为在每个设备中唯一的字符串返回。
序列号是一个唯一的设备 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 的平板电脑)。
为了完整Xamarin.Android
,您可以通过以下方式在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
是传入的上下文。
android.telephony.TelephonyManager.getDeviceId()
这将返回唯一标识设备的任何字符串(GSM 上的 IMEI,CDMA 上的 MEID)。
您需要在 AndroidManifest.xml 中获得以下权限:
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
是的,每个 Android 设备都有一个唯一的序列号,您可以通过此代码获取它。 Build.SERIAL
。 请注意,它仅在 API 级别 9 中添加,并且可能不会出现在所有设备上。 要在早期平台上获得唯一 ID,您需要读取 MAC 地址或 IMEI 之类的信息。
经过一番寻找。 我意识到没有确定的方法来拥有一个唯一的 ID。
假设我们希望每个用户只能在一部手机上使用该应用程序。
我要做的是:
当用户在我的应用程序中注册时,我将当前时间保存为服务器和应用程序数据库中的唯一 ID。
当用户尝试在另一部手机上登录时,我从服务器获取用户信息并意识到该用户已经登录,因为唯一 ID 字段已满,因此向他/她显示他/她已经登录的对话框在另一个设备上,他是否想离开之前的 session,如果他说是,我将为他创建一个新的唯一 ID,并在服务器上更新唯一 ID 详细信息。
在我自己的应用程序中,每次运行时我都会从服务器获取用户配置文件。 如果服务器上存储的唯一 ID 与应用程序数据库中存储的唯一 ID 不同,我会自动注销用户。
仅获取一次设备 ID,然后将其存储在数据库或文件中。 在这种情况下,如果是应用程序的第一次启动,它会生成一个 ID 并存储它。 下一次,它将只获取存储在文件中的 ID。
检查 SystemInfo.deviceUniqueIdentifier
文档: http : //docs.unity3d.com/Documentation/ScriptReference/SystemInfo-deviceUniqueIdentifier.html
唯一的设备标识符。 它保证对每个设备都是唯一的(只读)。
iOS:在 iOS7 之前的设备上,它将返回 MAC 地址的哈希值。 在 iOS7 设备上,它将是 UIDevice identifierForVendor,或者,如果由于任何原因失败,则是 ASIdentifierManager AdvertisingIdentifier。
要获取用户 ID,您可以使用 Google Play 许可库。
要下载此库,请打开 SDK Manager => SDK Tools。 下载库文件的路径是:
path_to_android_sdk_on_your_pc/extras/google/market_licensing/library
在您的项目中包含该库(您可以简单地复制其文件)。
接下来,您需要一些Policy
接口的实现(您可以简单地使用库中的两个文件之一: ServerManagedPolicy
或StrictPolicy
)。
将在processServerResponse()
函数中为您提供用户 ID:
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/extras/google/market_licensing/sample/src/com/example/android/market/licensing
不要忘记将 CHECK_LICENSE 权限添加到您的 AndroidManifest.xml。
有关许可库的更多信息: https : //developer.android.com/google/play/licensing
Android 在Android O 之后限制了硬件相关的Id,因此Android_Id 是唯一id 的解决方案,但是当reflector 设备时会生成新的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)
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
此示例演示如何在 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"
生成设备标识符
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;
}
Output
DeviceIdentifier uuid is : 00000000-36ab-9c3c-0000-0000714a4f37
您将通过使用以下代码获得 wifi mac 地址,无论您尝试连接 wifi 时是否使用了随机地址,也无论 wifi 是打开还是关闭。
我使用了下面链接中的一种方法,并添加了一个小的修改来获取确切地址而不是随机地址:
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();
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.