如何找到 Android 设备的序列号?

我需要为一个 Android 应用程序使用一个唯一的 ID,我认为该设备的序列号将是一个很好的候选人。如何在应用程序中检索 Android 设备的序列号?

242670 次浏览
String deviceId = Settings.System.getString(getContentResolver(),
Settings.System.ANDROID_ID);

尽管如此,这并不能保证 Android ID 会成为一个唯一标识符。

TelephonyManager tManager = (TelephonyManager)myActivity.getSystemService(Context.TELEPHONY_SERVICE);
String uid = tManager.getDeviceId();

GetSystemService 是 Activity 类中的一个方法。GetDeviceID ()将根据手机使用的无线电(GSM 或 CDMA)返回设备的 MDN 或 MEID。

每个设备必须在这里返回一个唯一的值(假设它是一部手机)。这应该适用于任何具有 sim 槽或 CDMA 收音机的 Android 设备。你只能靠你自己的安卓微波炉了; -)

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 返回设备序列号。

IMEI 很好,但只能在安卓手机上使用。你也应该考虑支持平板电脑或其他没有手机的 Android 设备。

您有一些替代方案,比如: 构建类成员、 BT MAC、 WLAN MAC,甚至更好——所有这些方案的组合。

我在我的博客上的一篇文章中解释了这些细节,见: Http://www.pocketmagic.net/?p=1662

在 Android 开发者的博客上有一篇很好的文章讨论了这个问题

它建议不要使用 TelephonyManager.getDeviceId(),因为它不能在 Android 设备上工作,这些设备不是像平板电脑这样的手机,它需要 READ_PHONE_STATE的许可,并且它不能在所有的手机上可靠地工作。

相反,你可以使用以下方法之一:

  • Mac 地址
  • 序列号
  • 机器人

这篇文章讨论了每种方法的优缺点,值得一读,这样你就能知道哪种方法最适合你使用。

正如戴夫 · 韦伯提到的,涵盖这一点的 Android Developer Blog 有一篇文章

我和谷歌的一些人谈过,希望得到一些关于一些项目的额外说明。以下是我在前面提到的博客文章中没有提到的发现:

  • ANDROID _ ID 是首选的解决方案。在 ANDROID < = 2.1或 > = 2.3的版本中,ANDROID _ ID 是完全可靠的。只有2.2有文章中提到的问题。
  • 2.2中的 ANDROID _ ID 错误影响了几家制造商的几个设备。
  • As far as I've been able to determine, all affected devices have 相同的机器人 ID, which is 9774d56d682e549c. Which is also the same device id reported by the emulator, btw.
  • Google believes that OEMs have patched the issue for many or most of their devices, but I was able to verify that as of the beginning of April 2011, at least, it's still quite easy to find devices that have the broken ANDROID_ID.

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

我发现上面@emmby 发布的示例类是一个很好的起点。但正如其他海报所提到的,它也有一些缺陷。主要原因是它不必要地将 UUID 保存到一个 XML 文件中,然后总是从这个文件中检索它。这使得这个类很容易被破解: 任何拥有植根电话的人都可以编辑 XML 文件,给自己一个新的 UUID。

我已经更新了代码,以便它只在绝对必要的情况下(例如,当使用随机生成的 UUID 时)才能保持为 XML,并按照@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;
}

As @haserman says:

TelephonyManager tManager = (TelephonyManager)myActivity.getSystemService(Context.TELEPHONY_SERVICE);
String uid = tManager.getDeviceId();

But it's necessary including the permission in the manifest file:

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

上述所有方法都存在问题。在谷歌 i/o Reto Meier 发布了一个健壮的答案,如何处理这个问题,应该满足大多数开发人员的需要,跨安装跟踪用户。

This approach will give you an anonymous, secure user ID which will be persistent for the user across different devices (including tablets, based on primary Google account) and across installs on the same device. The basic approach is to generate a random user ID and to store this in the apps shared preferences. You then use Google's backup agent to store the shared preferences linked to the Google account in the cloud.

让我们通过完整的方法。首先,我们需要使用 Android 备份服务为 SharedPreferences 创建一个备份。首先通过这个链接注册你的应用程序: http://developer.android.com/google/backup/signup.html

谷歌会给你一个备份服务密钥,你需要添加到清单。您还需要告诉应用程序使用 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 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);

Finally create a user ID, if it doesn't already exist, and store it in the 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 也将在安装之间持久化。

For more information on this approach see Reto's talk here http://www.google.com/events/io/2011/sessions/android-protips-advanced-topics-for-expert-android-app-developers.html

有关如何实现备份代理的详细信息,请参阅开发者网站: http://developer.android.com/guide/subject/data/backup.html”rel = “ nofollow”> http://developer.android.com/guide/topics/data/backup.html 我特别推荐底部关于测试的部分,因为备份不会立即发生,所以要进行测试,必须强制执行备份。

另一种方法是在没有任何权限的 App 中使用/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

To do this in java one would just use a FileInputStream to open the iSerial file and read out the characters. Just be sure you wrap it in an exception handler because not all devices have this file.

至少已知下列设备具有该文件的全球可读性:

  • Galaxy Nexus
  • Nexus S
  • 摩托罗拉 Xoom 3g
  • 东芝 AT300
  • HTC One V
  • 迷你 MK802
  • Samsung Galaxy S II

You can also see my blog post here: http://insitusec.blogspot.com/2013/01/leaking-android-hardware-serial-number.html where I discuss what other files are available for info.

对于一个简单的数字,这是唯一的设备和常数的生命周期(除非工厂重置或黑客) ,使用 设置: Secure.ANDROID _ ID

String id = Secure.getString(getContentResolver(), Secure.ANDROID_ID);

To use the device serial number (the one shown in "System Settings / About / Status") if available and fall back to Android ID:

String serialNumber = Build.SERIAL != Build.UNKNOWN ? Build.SERIAL : Secure.getString(getContentResolver(), Secure.ANDROID_ID);

Unique device ID of Android OS Device as String.

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

but I strngly recommend this method suggested by Google::

Identifying App Installations

no answer here mentions a perfect, fail-proof ID that is both PERSISTENT through system updates and exists in ALL devices开始(主要是因为 Google 没有单独的解决方案) ,我决定发布一个方法,这个方法通过组合两个可用的标识符,并在运行时在它们之间进行选择。

Before code, 3 facts:

  1. TelephonyManager.getDeviceId() (a.k.a.IMEI) will not work well or at all for non-GSM, 3G, LTE, etc. devices, but 当存在相关硬件时,将始终返回唯一的 ID, even when no SIM is inserted or even when no SIM slot exists (some OEM's have done this).

  2. Since Gingerbread (Android 2.3) android.os.Build.SERIAL must exist on any device that doesn't provide IMEI, i.e., doesn't have the aforementioned hardware present, as per Android policy.

  3. 由于事实(2。) ,这两个唯一标识符中的至少一个将始终存在和系列 可以在同一时间存在的 IMEI 是。

Note: Fact (1.) and (2.) are 基于谷歌的声明

解决方案

有了上面的事实,我们总是可以通过检查是否有 imei 绑定的硬件来获得一个唯一标识符,当它不存在的时候,我们可以回到 SERIAL,因为我们不能检查现有的 SERIAL 是否有效。下面的静态类提供了两种方法来检查这种存在,并使用 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 存在需求。

P.S.: Do please note that OEM's have managed to bug out SERIAL against Google policy (multiple devices with same SERIAL), and Google as stated there is at least one known case in a big OEM (not disclosed and I don't know which brand it is either, I'm guessing Samsung).

免责声明 : 这回答了获得唯一设备 ID 的最初问题,但 OP 声明他需要 APP 的唯一 ID,从而引入了歧义。即使在这种情况下 Android _ ID 会更好,它不会工作后,比如说,一个应用程序的钛备份通过2个不同的 ROM 安装(甚至可以是相同的 ROM)。我的解决方案保持了独立于闪存或工厂重置的持久性,并且只有在通过黑客/硬件模块进行 IMEI 或串行篡改时才会失败。

我知道这个问题很老了,但它可以在一行代码中完成

String deviceID = Build.SERIAL;

是的。这是一个设备硬件序列号,它是唯一的。因此,在 api 级别2.3及以上,您可以使用 Android.os. Build.ANDROID _ ID来获得它。对于低于2.3 API 级别,使用 TelephonyManager.getDeviceID ()

你可以读这个 http://android-developers.blogspot.in/2011/03/identifying-app-installations.html

Build.SERIAL is the simplest way to go, although not entirely reliable as it 可以是空的 or sometimes return a different value (证明1, 证据2) than what you can see in your device's settings.

取决于设备的制造商和 Android 版本,有几种方法可以得到这个数字,所以我决定编译我能在一个单独的 大意中找到的所有可能的解决方案。这里有一个简化版本:

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

从 Android 10开始,应用程序必须拥有 READ _ PRIVILEGED _ PHONE _ STATE 特权权限才能访问设备的不可重置标识符,包括 IMEI 和序列号。

受影响的方法包括:

Build 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上工作,在 Samsung Galaxy s7 s9 Xcover 上进行测试