安卓: 如何以编程方式访问在AVD管理器中显示的设备序列号(API版本8)

66

我该如何以编程的方式访问下图中所示的值?

在此输入图片描述


可能是如何查找Android设备的序列号?的重复问题。 - George Stocker
6个回答

119

这是硬件序列号,获取方式如下:

  • Android Q(>= SDK 29)需要使用android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE权限。只有系统应用可以请求此权限。如果调用包是设备或个人资料所有者,则READ_PHONE_STATE 权限就足够了。

  • Android 8及更高版本(>= SDK 26)使用android.os.Build.getSerial()方法来获取,需要危险权限READ_PHONE_STATE。使用android.os.Build.SERIAL将返回android.os.Build.UNKNOWN

  • Android 7.1及更早版本(<= SDK 25)和早期的android.os.Build.SERIAL返回有效的序列号。

每个设备的序列号都是唯一的。如果您正在寻找获取/使用唯一设备 ID 的可能性,可以阅读 此处内容

对于不需要权限的反射解决方案,请参见此答案


5
为什么你说重置出厂设置后会改变?我知道这对于Settings.Secure.ANDROID_ID是正确的,但我没有听说过Build.Serial会发生这种情况。 - Tom
1
Tom,你是对的!我混淆了ANDROID_ID和SERIAL。我编辑了我的答案。 - thaussma
2
这通常是制造商在设备本身上物理打印的序列号,还是仅为软件序列号? - guidod
未来会有任何变化吗? - Pratik Butani
2
请注意,从Android 8(API26)开始,Build.SERIAL将不再起作用。获取序列号的唯一方法是调用Build.getSerial()方法,该方法需要READ_PHONE_STATE权限。 - alex_z
构建序列号何时更改?更改硬件、根设备或进行出厂重置是否会更改它? - geosmart

69

适用于Android 7.1(SDK 25)及以下版本

在Android 7.1之前,您可以使用以下方式获取:

Build.SERIAL

自Android 8(SDK 26)起

在Android 8(SDK 26)及以上版本中,该字段将返回UNKNOWN,必须使用以下方式进行访问:

Build.getSerial()

这需要危险权限android.permission.READ_PHONE_STATE

从Android Q(SDK 29)开始

自Android Q以来,使用Build.getSerial()变得更加复杂,需要:

android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE(只能由系统应用程序获取),或者调用包是设备或配置文件所有者并具有READ_PHONE_STATE权限。这意味着大多数应用程序将无法使用此功能。请参见Google的Android Q公告

请参见Android SDK参考文档


唯一设备标识的最佳实践

如果您只需要一个唯一标识符,最好避免使用硬件标识符,因为出于隐私原因,Google 不断努力使其更难以访问。您可以生成一个 UUID.randomUUID().toString(); 并将其保存在例如共享首选项中,第一次需要访问时。或者,您可以使用 ANDROID_ID,它是一个 8 字节长的十六进制字符串,对于设备、用户和(仅限 Android 8+)应用程序安装是唯一的。有关该主题的更多信息,请参见唯一标识符的最佳实践


能否请给出负评的人解释一下,为什么他/她认为这是不正确的? - Patrick
我可能误操作投了反对票,现在这显然是正确答案。 我无法取消我的反对票,因为在回答被编辑之前它已经被锁定了。 - peceps
如果手机被恢复出厂设置或者获取了逃逸权限,那么从UUID.randomUUID().toString()和ANDROID_ID获取的ID会改变吗? - TJCLK
UUID.randomUUID().toString() 总是不同的 -> 这就是为什么你必须持久化,但恢复出厂设置当然会删除它。ANDROID_ID 没有真正定义,但预计也会不同。 - Patrick

42

Build.SERIAL 可能为空,有时会返回不同于您在设备设置中看到的值(证据1证据2)。

如果您想要一个更完整和强大的解决方案,我已经将我能找到的每种可能的解决方案编译成了一个单独的代码片段。这是它的简化版本:

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版本上进行测试时。欢迎贡献。


它终于解决了我的问题,被接受的答案应该是这个。 - Hussain KMR Behestee
我认为这是这里最好的答案。请注意,如果我在我的Android Studio中使用此代码并运行了两个模拟器实例,则它将返回相同的ID。 - panoet
你在使用模拟器时,是否应该得到 null 值? - Jasper de Vries

1

我喜欢我的解决方案:

/**
 * Checks some permission
 *
 * @return true - If have any of the permissions sent by parameter
 * @return true - If you do not have any of the permissions sent by parameter
 */
fun Context.hasPermissionSome(vararg permissions: String): Boolean {
    permissions.forEach { permission ->
        if (hasPermission(permission)) {
            return true
        }
    }
    return false
}

@CheckResult
fun validateValue(value: String?, valueToCompare: String? = null): Boolean {
    if (!value.isNullOrEmpty() && value != Build.UNKNOWN && value != valueToCompare)
        return true

    return false
}

/**
 * @see: https://developer.android.com/about/versions/10/privacy/changes#data-ids
 *
 * Retrieve SerialNumber with system commands:
 *   adb shell getprop ro.serialno
 *   adb shell getprop ro.boot.serialno
 *   adb shell getprop ril.serialnumber
 * @return device Serial Number obtained with SystemProperties
 */
@SuppressLint("PrivateApi")
private fun serialNumberBySysProp(): String? {
    var sn: String? = null

    val readPhonePermissions = arrayOf(
        Manifest.permission.READ_PHONE_STATE,
        "android.permission.READ_PRIVILEGED_PHONE_STATE"
    )

    if (!this.context.hasPermissionSome(*readPhonePermissions))
        return sn

    try {
        val systemProps = Class.forName("android.os.SystemProperties")
        val getProp = systemProps.getMethod("get", String::class.java)
        
        // 1ª tentativa
        sn = getProp("ril.serialnumber") as String?
        if (validateValue(sn)) {
            return sn
        }

        // 2ª tentativa
        sn = getProp("ro.serialno") as String?
        if (validateValue(sn)) {
            return sn
        }

        // 3ª tentativa
        sn = getProp("ro.boot.serialno") as String?
        if (validateValue(sn)) {
            return sn
        }

        // 4ª tentativa
        sn = getProp("sys.serialnumber") as String?
        if (validateValue(sn)) {
            return sn
        }

    } catch (e: Exception) { }

    return if (validateValue(sn))
        sn
    else
        null
}

/**
 * @return device Serial Number obtained with Build Class
 */
@SuppressLint("HardwareIds")
private fun serialNumberByBuild(): String? {
    var sn: String? = null
    MediaDrm.PROPERTY_DEVICE_UNIQUE_ID
    if (!this.context.hasPermissionSome(
            Manifest.permission.READ_PHONE_STATE,
            "android.permission.READ_PRIVILEGED_PHONE_STATE"
        ))
        return sn

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        sn = Build.getSerial()
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        sn = Build.getSerial()
    } else if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {
        sn = Build.getSerial()

        if (!validateValue(sn))
            sn = Build.SERIAL
    }

    return if (validateValue(sn))
        sn
    else
        null
}

val serialNumber: String
    @SuppressLint("HardwareIds", "PrivateApi", "MissingPermission", "StaticFieldLeak")
    get() {
        var serial: String = serialNumberByBuild() ?: ""

        if (!validateValue(serial)) {
            serial = serialNumberBySysProp() ?: ""
        }

        return serial
    }

在 AndroidManifest.xml 文件中添加这些权限。
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />

1

从 Android P 开始,在 AndroidManifest 中定义 READ_PHONE_STATE 权限将不会起作用。我们必须实际请求权限。下面的代码适用于我:

@RequiresApi(api = Build.VERSION_CODES.P)
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_PHONE_STATE}, 101);
    }
}

@RequiresApi(api = Build.VERSION_CODES.O)
@Override
protected void onResume() {
    super.onResume();
    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
        return;
    }
    Log.d(TAG,Build.getSerial());
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    switch (requestCode) {
        case 101:
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
                return;
            }
        } else {
            //not granted
        }
        break;
        default:
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

请在AndroidManifest.xml中添加以下权限:
<uses-permission android:name = "android.permission.INTERNET"/>
<uses-permission android:name = "android.permission.READ_PHONE_STATE" />

-1

如果你想要在新版本的安卓系统和安卓模拟器上运行,这可能会有帮助:

注意:不要忘记给予必要的权限。(在没有系统级别权限的情况下,可能无法在安卓11和12上运行)

fun getSerialNumber(): String {
    var serialNumber: String?
    try {
        val c = Class.forName("android.os.SystemProperties")
        val get = c.getMethod("get", String::class.java)
        serialNumber = get.invoke(c, "gsm.sn1") as String
        if (serialNumber == "") serialNumber = get.invoke(c, "ril.serialnumber") as String
        if (serialNumber == "") serialNumber = get.invoke(c, "ro.serialno") as String
        if (serialNumber == "") serialNumber = get.invoke(c, "sys.serialnumber") as String
        if (serialNumber == "") serialNumber = get.invoke(c, "ro.boot.serialno") as String
        if (serialNumber == "") serialNumber = get.invoke(c, "ro.kernel.androidboot.serialno") as String
        if (serialNumber == "") {
            serialNumber = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                Build.getSerial()
            } else {
                Build.SERIAL
            }
        }

        // If none of the methods above worked
        if (serialNumber == "") serialNumber = null
    } catch (e: Exception) {
        e.printStackTrace()
        serialNumber = null
    }
    if (!serialNumber.isNullOrEmpty() && serialNumber != Build.UNKNOWN) {
        return serialNumber
    }
    return "TEMP12345678"
}

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