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

115

我需要在Android应用程序中使用唯一标识符,而我认为设备的序列号是一个好的选择。如何在我的应用程序中检索Android设备的序列号?


2
不要忘记在你的清单文件中添加 android:name="android.permission.READ_PHONE_STATE"。 - Michael SIlveus
1
请查看以下链接:https://dev59.com/V3E85IYBdhLWcg3wbS1h#3102499 - Seva Alekseyev
如果您想获取一个不需要任何权限的唯一ID,您可以使用这个库来生成每个设备的唯一ID,使用Identity.getDeviceId(context),或者通过Identity.getInstallationId(context)获取您的应用程序安装标识符。 - caw
18个回答

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

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

假设设备为手机,则每个设备必须在此处返回唯一的值。这适用于任何带有sim卡插槽或CDMA收音机的Android设备。对于那些搭载Android的微波炉,你得自己想办法啦 ;-)


24
在androidManifest.xml文件中添加<uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>权限后,@Hasemam现在可以正常运行了。 - Paresh Mayani
23
官方Android开发者博客上有一些关于使用此标识符的建议:http://android-developers.blogspot.com/2011/03/identifying-app-installations.html。 - David Snabel-Caunt
8
除了搭载Android系统的微波炉之外,那Android平板电脑呢? :) - ajacian81
21
避免使用这种方法,它可以在手机上运行但不能在没有手机芯片的设备上运行(例如平板电脑)。从2.3版本开始,您可以使用android.os.Build.SERIAL,但请查看@DavidCaunt建议的开发者博客。 - John Mitchell
问题是序列号而不是IMEI,这是错误的答案。正确答案可能是:android.os.Build.class.getField("SERIAL").get(null).toString(); - Sinan Dizdarević
显示剩余2条评论

72

正如Dave Webb所提到的,Android开发者博客有一篇文章介绍了这个问题。

我与谷歌的一位员工交流,以获取一些额外的澄清。这是我发现的,在上述博客文章中没有提到的内容:

  • ANDROID_ID是首选解决方案。在Android版本<=2.1或>=2.3上,ANDROID_ID非常可靠。只有2.2存在上述问题。
  • 多家制造商的多款设备都受到2.2中ANDROID_ID的错误影响。
  • 据我所知,所有受影响的设备都具有相同的ANDROID_ID,即9774d56d682e549c。顺便说一下,这也是模拟器报告的相同设备ID。
  • 谷歌认为OEM已经为他们的许多或大多数设备修补了这个问题,但我至少可以验证,截至2011年4月初,仍然很容易找到具有损坏的ANDROID_ID的设备。

根据谷歌的建议,我实现了一个类,它将使用ANDROID_ID作为必要的种子为每个设备生成唯一的UUID,在必要时退回到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;
    }
}

1
这个应用需要哪些权限才能使用? - Dave L.
1
@ef2011 这是双重检查锁定模式:http://en.wikipedia.org/wiki/Double-checked_locking - emmby
3
谢谢发布。但是,有什么方法可以阻止拥有root手机的人只需编辑device_id.xml以输入他们选择的新UUID来规避“免费试用”检查吗?如果该类只在必须采用随机ID方法时将值存储在首选项文件中,那不是更好吗?否则,在应用程序运行之间没有必要保留它;重新生成更安全。 - Carlos P
1
TelephonyManager怎么样,平板电脑上有吗?它会为ID提供空值吗?还是你的代码会进入异常?你在平板电脑上测试过吗? - Peterdk
1
为什么你没有将这个类声明为final,同时也没有将其所有方法都声明为static,并将其构造函数声明为private呢?此外,使用该类的不同应用程序在回退情况下可能会获得不同的ID - 它们读取和写入它们本地副本的PREFS_FILE。此外,@Peterdk是正确的 - 在具有id“9774d56d682e549c”的平板电脑上可能会出现NPE错误 - 对吗? - Mr_and_Mrs_D
显示剩余3条评论

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


7
这只是给我与android.os.Build.SERIAL相同的值。 - josephus
我是否错了,或者说这个序列号在所有使用特定自定义ROM的设备中都是相同的?我的设备序列号(在Eclipse设备启动器中)显示为01234567890ABC,这是一款带有自定义ROM的手机。 - Peterdk
@Peterdk,我使用的是Cyanogen-9系统,两种方法(即答案中提到的pre andy-9和andy-9上可用的更简单的方法)都报告了正确的序列号(与制造商贴纸上的相同)。但这可能取决于特定的定制ROM版本。您使用的是哪个ROM /版本? - morgwai

16
String deviceId = Settings.System.getString(getContentResolver(),
                                Settings.System.ANDROID_ID);

尽管如此,Android ID并不保证是唯一的标识符。


@Paresh Mayani,如果不看代码,很难确定出现了什么问题。我唯一的假设是getContentResolver返回了null。但值得一提的是,开个问题并发布你的代码可能会有所帮助。 - Anthony Forloney
4
这个ID来自与手机相关的谷歌账户。模拟器通常没有这个ID。真实的手机也可能没有。此外,它被记录为“可以在出厂设置重置后更改”,并且可以在已 root 的手机上随时任意更改。使用时需自行承担风险。没有好的替代方案-其他尝试性设备ID要么不普遍可用,要么不唯一,或两者兼而有之。查看其他答案以获取这个可悲故事的其余部分。 - Seva Alekseyev

15

Android开发者博客上有一篇关于此问题的优秀文章

它建议不要使用TelephonyManager.getDeviceId(),因为它不能在非手机设备(如平板电脑)上工作,需要READ_PHONE_STATE权限,并且在所有手机上都不可靠。

相反,您可以使用以下之一:

  • Mac地址
  • 序列号
  • ANDROID_ID

这篇文章讨论了每个选项的利弊,值得一读,以便确定哪个选项最适合您的用途。


+1,嗨Dave,感谢您的澄清。因为我正在开发一款平板电脑应用程序,需要获取Android设备的唯一ID,所以我应该使用什么来获取唯一的Android平板电脑设备? - Paresh Mayani

12

如果需要一个简单的、与设备唯一且终身不变(除非进行出厂设置或破解)的数字,请使用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);

直截了当的回答!! - danny
Build.SERİAL在Java中已被弃用。 - Eyyüp Alkış

7

IMEI码很好,但仅适用于带有电话的Android设备。您应该考虑支持平板电脑或其他没有电话的Android设备。

您有一些替代方案,例如:构建类成员、蓝牙MAC地址、 WLAN MAC地址,或者更好的方法是将所有这些组合起来。

我在我的博客上解释了这些细节,请参见: http://www.pocketmagic.net/?p=1662


6

由于没有一个完美、经过系统更新后仍然持久存在于所有设备中的ID,这里没有提到任何答案(主要是因为谷歌没有一个独立的解决方案),因此我决定发布一种方法,通过组合两个可用的标识符和在运行时选择它们之间的检查来实现最佳效果。

在代码之前,有三个事实:

  1. TelephonyManager.getDeviceId()(即IMEI)对于非GSM、3G、LTE等设备将无法正常工作或根本不起作用,但是,当相关硬件存在时,它将始终返回唯一的ID,即使没有插入SIM卡甚至没有SIM卡槽存在(某些OEM已经这样做了)。

  2. 自Gingerbread(Android 2.3)以来,android.os.Build.SERIAL在任何不提供IMEI的设备上都必须存在,即没有前述硬件存在,根据Android政策。

  3. 由于事实(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出现需求。
P.S.请注意,OEM已通过Google政策使SERIAL出现错误(具有相同SERIAL的多个设备),而Google已经声明在一个大型OEM中至少存在一个已知案例(未透露品牌,我也不知道是哪个品牌,我猜测是三星)。
免责声明:这回答了获取唯一设备ID的原始问题,但OP通过声明需要APP的唯一ID引入了歧义。即使对于这种情况,Android_ID更好,但是它将在通过2个不同的ROM安装(甚至可以是相同的ROM)的Titanium Backup之后停止工作。我的解决方案保持独立于刷新或出厂重置的持久性,并且仅在通过黑客/硬件修改进行IMEI或SERIAL篡改时才会失败。

5
以上方法都存在问题。在Google i/o大会上,Reto Meier提出了一个稳健的解决方案,可以满足大多数开发者跨设备追踪用户的需求。
该方法将为您提供一个匿名、安全的用户ID,该ID将在不同设备(包括基于主要Google帐户的平板电脑)和同一设备上的不同安装中持久存在。基本方法是生成一个随机的用户ID,并将其存储在应用程序的共享首选项中。然后,使用Google的备份代理将与Google帐户链接的共享首选项存储在云中。
现在让我们详细介绍这个方法。首先,我们需要使用Android Backup服务为我们的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尚未存在,则创建一个用户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 我特别推荐底部的测试部分,因为备份不会立即发生,所以要进行测试必须强制备份。

2
另一种方法是在没有任何权限的应用程序中使用 /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文件并读取字符。但是请确保将其包装在异常处理程序中,因为并不是所有设备都有此文件。
至少已知以下设备具有此文件的全球可读性:
- Galaxy Nexus - Nexus S - Motorola Xoom 3g - Toshiba AT300 - HTC One V - Mini MK802 - Samsung Galaxy S II
您还可以查看我的博客文章:http://insitusec.blogspot.com/2013/01/leaking-android-hardware-serial-number.html,其中我讨论了其他可用于获取信息的文件。

感谢您发布答案!请务必仔细阅读有关自我推广的FAQ。还请注意,每次链接到您自己的网站/产品时,必须发布免责声明。 - Andrew Barber

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接