我需要为 Android 应用程序使用唯一 ID,并且我认为设备的序列号是一个不错的选择。如何在我的应用程序中检索 Android 设备的序列号?
18 回答
TelephonyManager tManager = (TelephonyManager)myActivity.getSystemService(Context.TELEPHONY_SERVICE);
String uid = tManager.getDeviceId();
getSystemService 是来自 Activity 类的方法。getDeviceID() 将根据手机使用的无线电(GSM 或 CDMA)返回设备的 MDN 或 MEID。
每个设备必须在此处返回一个唯一值(假设它是一部手机)。这应该适用于任何带有 sim 插槽或 CDMA 无线电的 Android 设备。您可以自己使用 Android 供电的微波炉 ;-)
正如 Dave Webb 提到的,Android 开发者博客有一篇文章涵盖了这一点。
我与 Google 的某个人进行了交谈,以对一些项目进行进一步的澄清。以下是我发现的上述博客文章中未提及的内容:
- ANDROID_ID 是首选解决方案。ANDROID_ID 在 Android <=2.1 或 >=2.3 的版本上非常可靠。只有2.2有帖子中提到的问题。
- 几家制造商的几款设备受到 2.2 中的 ANDROID_ID 错误的影响。
- 据我所知,所有受影响的设备都具有相同的 ANDROID_ID,即9774d56d682e549c。这也是模拟器报告的相同设备ID,顺便说一句。
- Google 认为 OEM 已经为他们的许多或大部分设备修复了这个问题,但我能够验证,至少在 2011 年 4 月开始时,仍然很容易找到 ANDROID_ID 损坏的设备。
根据 Google 的建议,我实现了一个类,该类将为每个设备生成唯一的 UUID,在适当的情况下使用 ANDROID_ID 作为种子,必要时使用 TelephonyManager.getDeviceId(),如果失败,则使用随机生成的唯一 UUID这在应用程序重新启动(但不是应用程序重新安装)中持续存在。
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 static volatile 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;
}
}
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 API 返回设备序列号。
String deviceId = Settings.System.getString(getContentResolver(),
Settings.System.ANDROID_ID);
但是,不能保证 Android ID 将是唯一标识符。
Android 开发者博客上有一篇很棒的文章讨论了这个。
它建议不要使用TelephonyManager.getDeviceId()
,因为它不适用于非手机(如平板电脑)的 Android 设备,它需要READ_PHONE_STATE
许可,并且不能在所有手机上可靠地工作。
相反,您可以使用以下方法之一:
- MAC地址
- 序列号
- ANDROID_ID
这篇文章讨论了每种方法的优缺点,值得一读,这样您就可以找出最适合您使用的方法。
对于设备唯一且在其生命周期内保持不变的简单数字(除非恢复出厂设置或黑客攻击),请使用Settings.Secure.ANDROID_ID。
String id = Secure.getString(getContentResolver(), Secure.ANDROID_ID);
要使用设备序列号(“系统设置/关于/状态”中显示的序列号)(如果可用)并回退到 Android ID:
String serialNumber = Build.SERIAL != Build.UNKNOWN ? Build.SERIAL : Secure.getString(getContentResolver(), Secure.ANDROID_ID);
IMEI 很好,但仅适用于带有手机的 Android 设备。您还应该考虑支持没有手机的平板电脑或其他 Android 设备。
您有一些选择,例如:构建类成员、BT MAC、WLAN MAC,甚至更好——所有这些的组合。
我已经在我的博客文章中解释了这些细节,请参阅: http ://www.pocketmagic.net/?p=1662
由于这里没有答案提到一个完美的、防故障的 ID,它既可以通过系统更新持久存在,也存在于所有设备中(主要是因为 Google 没有单独的解决方案),所以我决定发布一个方法下一个最好的方法是组合两个可用的标识符,并在运行时检查它们之间的选择。
在代码之前,三个事实:
TelephonyManager.getDeviceId()
(akaIMEI) 不适用于非 GSM、3G、LTE 等设备,但在存在相关硬件时始终返回唯一 ID,即使没有插入 SIM 卡或不存在 SIM 卡插槽也是如此(一些 OEM 已经这样做了)。由于 Gingerbread (Android 2.3)
android.os.Build.SERIAL
必须存在于任何不提供 IMEI 的设备上,也就是说,根据 Android 政策,不存在上述硬件。由于事实 (2.),这两个唯一标识符中的至少一个将始终存在,并且 SERIAL可以与 IMEI 同时存在。
注意:事实 (1.) 和 (2.)基于 Google 声明
解决方案
根据上述事实,通过检查是否存在绑定 IMEI 的硬件,始终可以拥有唯一标识符,如果没有,则回退到 SERIAL,因为无法检查现有的 SERIAL 是否有效。以下静态类提供了 2 种用于检查此类存在并使用 IMEI 或 SERIAL 的方法:
import java.lang.reflect.Method;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.util.Log;
public class IDManagement {
public static String getCleartextID_SIMCHECK (Context mContext){
String ret = "";
TelephonyManager telMgr = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
if(isSIMAvailable(mContext,telMgr)){
Log.i("DEVICE UNIQUE IDENTIFIER",telMgr.getDeviceId());
return telMgr.getDeviceId();
}
else{
Log.i("DEVICE UNIQUE IDENTIFIER", Settings.Secure.ANDROID_ID);
// return Settings.Secure.ANDROID_ID;
return android.os.Build.SERIAL;
}
}
public static String getCleartextID_HARDCHECK (Context mContext){
String ret = "";
TelephonyManager telMgr = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
if(telMgr != null && hasTelephony(mContext)){
Log.i("DEVICE UNIQUE IDENTIFIER",telMgr.getDeviceId() + "");
return telMgr.getDeviceId();
}
else{
Log.i("DEVICE UNIQUE IDENTIFIER", Settings.Secure.ANDROID_ID);
// return Settings.Secure.ANDROID_ID;
return android.os.Build.SERIAL;
}
}
public static boolean isSIMAvailable(Context mContext,
TelephonyManager telMgr){
int simState = telMgr.getSimState();
switch (simState) {
case TelephonyManager.SIM_STATE_ABSENT:
return false;
case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
return false;
case TelephonyManager.SIM_STATE_PIN_REQUIRED:
return false;
case TelephonyManager.SIM_STATE_PUK_REQUIRED:
return false;
case TelephonyManager.SIM_STATE_READY:
return true;
case TelephonyManager.SIM_STATE_UNKNOWN:
return false;
default:
return false;
}
}
static public boolean hasTelephony(Context mContext)
{
TelephonyManager tm = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
if (tm == null)
return false;
//devices below are phones only
if (Build.VERSION.SDK_INT < 5)
return true;
PackageManager pm = mContext.getPackageManager();
if (pm == null)
return false;
boolean retval = false;
try
{
Class<?> [] parameters = new Class[1];
parameters[0] = String.class;
Method method = pm.getClass().getMethod("hasSystemFeature", parameters);
Object [] parm = new Object[1];
parm[0] = "android.hardware.telephony";
Object retValue = method.invoke(pm, parm);
if (retValue instanceof Boolean)
retval = ((Boolean) retValue).booleanValue();
else
retval = false;
}
catch (Exception e)
{
retval = false;
}
return retval;
}
}
我会建议使用getCleartextID_HARDCHECK
. 如果反射没有停留在您的环境中,请改用该getCleartextID_SIMCHECK
方法,但要考虑到它应该适合您特定的 SIM 存在需求。
PS:请注意,OEM 已经设法根据 Google 政策(多个设备具有相同的 SERIAL)解决了 SERIAL,并且 Google 如所述,在大型 OEM 中至少有一个已知案例(未披露,我不知道哪个品牌要么是,我猜是三星)。
免责声明:这回答了获取唯一设备 ID 的原始问题,但 OP 通过声明他需要一个 APP 的唯一 ID 引入了歧义。即使对于这种情况,Android_ID 会更好,但在通过 2 个不同的 ROM 安装(甚至可以是相同的 ROM)对应用程序进行 Titanium 备份之后,它也不会工作。我的解决方案保持独立于闪存或出厂重置的持久性,并且仅在通过黑客/硬件模块发生 IMEI 或 SERIAL 篡改时才会失败。
以上所有方法都存在问题。在 Google i/o 上,Reto Meier 发布了一个关于如何解决这个问题的可靠答案,它应该满足大多数开发人员在跨安装跟踪用户的需求。
这种方法将为您提供一个匿名的、安全的用户 ID,该用户 ID 将在不同设备(包括平板电脑,基于主要 Google 帐户)和同一设备上的安装中保持不变。基本方法是生成随机用户 ID 并将其存储在应用程序共享首选项中。然后,您使用 Google 的备份代理将链接到云中的 Google 帐户的共享首选项存储起来。
让我们通过完整的方法。首先,我们需要使用 Android 备份服务为 SharedPreferences 创建备份。首先通过此链接注册您的应用程序:http: //developer.android.com/google/backup/signup.html
Google 将为您提供一个备份服务密钥,您需要将其添加到清单中。您还需要告诉应用程序使用 BackupAgent,如下所示:
<application android:label="MyApplication"
android:backupAgent="MyBackupAgent">
...
<meta-data android:name="com.google.android.backup.api_key"
android:value="your_backup_service_key" />
</application>
然后您需要创建备份代理并告诉它使用辅助代理来共享首选项:
public class MyBackupAgent extends BackupAgentHelper {
// The name of the SharedPreferences file
static final String PREFS = "user_preferences";
// A key to uniquely identify the set of backup data
static final String PREFS_BACKUP_KEY = "prefs";
// Allocate a helper and add it to the backup agent
@Override
public void onCreate() {
SharedPreferencesBackupHelper helper = new SharedPreferencesBackupHelper(this, PREFS);
addHelper(PREFS_BACKUP_KEY, helper);
}
}
要完成备份,您需要在主 Activity 中创建 BackupManager 的实例:
BackupManager backupManager = new BackupManager(context);
最后创建一个用户 ID,如果它不存在,并将其存储在 SharedPreferences 中:
public static String getUserID(Context context) {
private static String uniqueID = null;
private static final String PREF_UNIQUE_ID = "PREF_UNIQUE_ID";
if (uniqueID == null) {
SharedPreferences sharedPrefs = context.getSharedPreferences(
MyBackupAgent.PREFS, Context.MODE_PRIVATE);
uniqueID = sharedPrefs.getString(PREF_UNIQUE_ID, null);
if (uniqueID == null) {
uniqueID = UUID.randomUUID().toString();
Editor editor = sharedPrefs.edit();
editor.putString(PREF_UNIQUE_ID, uniqueID);
editor.commit();
//backup the changes
BackupManager mBackupManager = new BackupManager(context);
mBackupManager.dataChanged();
}
}
return uniqueID;
}
即使用户切换设备,此 User_ID 现在将在安装中保持不变。
有关此方法的更多信息,请参阅此处的 Reto 演讲http://www.google.com/events/io/2011/sessions/android-protips-advanced-topics-for-expert-android-app-developers.html
有关如何实现备份代理的完整详细信息,请参见此处的开发人员站点:http: //developer.android.com/guide/topics/data/backup.html 我特别推荐底部的测试部分,就像备份一样不会立即发生,因此要进行测试,您必须强制备份。
另一种方法是在没有任何权限的应用程序中使用 /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 文件并读出字符。只需确保将其包装在异常处理程序中,因为并非所有设备都有此文件。
至少已知以下设备具有此文件的世界可读性:
- 银河连结
- 连结小号
- 摩托罗拉 Xoom 3g
- 东芝AT300
- 宏达一V
- 迷你MK802
- 三星盖乐世 S II
您还可以在此处查看我的博客文章:http: //insitusec.blogspot.com/2013/01/leaking-android-hardware-serial-number.html在这里我讨论了哪些其他文件可供参考。
正如@haserman 所说:
TelephonyManager tManager = (TelephonyManager)myActivity.getSystemService(Context.TELEPHONY_SERVICE);
String uid = tManager.getDeviceId();
但必须在清单文件中包含权限:
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
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);
}
但我强烈推荐谷歌建议的这种方法::
Build.SERIAL
是最简单的方法,但并不完全可靠,因为它可能为空或有时返回与您在设备设置中看到的值不同的值(证明 1、证明 2 )。
根据设备的制造商和 Android 版本,有多种方法可以获取该数字,因此我决定在一个gist中编译我能找到的所有可能的解决方案。这是它的简化版本:
public static String getSerialNumber() {
String serialNumber;
try {
Class<?> c = Class.forName("android.os.SystemProperties");
Method get = c.getMethod("get", String.class);
serialNumber = (String) get.invoke(c, "gsm.sn1");
if (serialNumber.equals(""))
serialNumber = (String) get.invoke(c, "ril.serialnumber");
if (serialNumber.equals(""))
serialNumber = (String) get.invoke(c, "ro.serialno");
if (serialNumber.equals(""))
serialNumber = (String) get.invoke(c, "sys.serialnumber");
if (serialNumber.equals(""))
serialNumber = Build.SERIAL;
// If none of the methods above worked
if (serialNumber.equals(""))
serialNumber = null;
} catch (Exception e) {
e.printStackTrace();
serialNumber = null;
}
return serialNumber;
}
我知道这个问题很老,但可以在一行代码中完成
String deviceID = Build.SERIAL;
从 Android 10 开始,应用必须具有 READ_PRIVILEGED_PHONE_STATE 特权权限才能访问设备的不可重置标识符,其中包括 IMEI 和序列号。
受影响的方法包括:
构建 getSerial() TelephonyManager getImei() getDeviceId() getMeid() getSimSerialNumber() getSubscriberId()
READ_PRIVILEGED_PHONE_STATE 仅适用于平台
public static String getManufacturerSerialNumber() {
String serial = null;
try {
Class<?> c = Class.forName("android.os.SystemProperties");
Method get = c.getMethod("get", String.class, String.class);
serial = (String) get.invoke(c, "ril.serialnumber", "unknown");
} catch (Exception ignored) {}
return serial;
}
适用于 API 29 和 30,在三星 Galaxy s7 s9 Xcover 上测试
我发现上面@emmby 发布的示例类是一个很好的起点。但正如其他海报所提到的,它有几个缺陷。主要的一个是它不必要地将 UUID 持久化到一个 XML 文件中,然后总是从这个文件中检索它。这使该类容易被破解:任何拥有根手机的人都可以编辑 XML 文件以给自己一个新的 UUID。
我已经更新了代码,以便它仅在绝对必要的情况下保留为 XML(即,当使用随机生成的 UUID 时)并根据@Brill Pappin 的回答重构了逻辑:
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 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) || (androidId == null) ) {
final String deviceId = ((TelephonyManager) context.getSystemService( Context.TELEPHONY_SERVICE )).getDeviceId();
if (deviceId != null)
{
uuid = UUID.nameUUIDFromBytes(deviceId.getBytes("utf8"));
}
else
{
uuid = UUID.randomUUID();
// Write the value out to the prefs file so it persists
prefs.edit().putString(PREFS_DEVICE_ID, uuid.toString() ).commit();
}
}
else
{
uuid = UUID.nameUUIDFromBytes(androidId.getBytes("utf8"));
}
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
/**
* 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;
}
是的。它是一个设备硬件序列号,它是唯一的。因此,在 api 级别 2.3 及更高版本上,您可以使用android.os.Build.ANDROID_ID来获取它。对于 2.3 以下的 API 级别,请使用TelephonyManager.getDeviceID()。
您可以阅读此http://android-developers.blogspot.in/2011/03/identifying-app-installations.html