在移动应用开发领域,设备唯一标识符的获取一直是开发者需要面对的技术挑战。随着Android 11的发布,Google进一步收紧了设备标识符的访问权限,特别是设备序列号(Serial Number)的获取方式发生了重大变化。本文将带您深入理解这一变更背后的技术原理,并提供一套完整的解决方案。
Android 11对设备标识符的访问实施了更严格的限制,这是Google持续推进用户隐私保护政策的一部分。在之前的Android版本中,开发者可以通过Build.getSerial()方法相对容易地获取设备序列号,只需声明READ_PHONE_STATE权限即可。然而,在Android 11上,这一方法不再奏效,即使正确申请了权限,返回的结果也往往是"UNKNOWN"。
这种变化源于Android权限系统的深层调整:
提示:在Android 11上,设备序列号被视为敏感标识符,普通应用已无法直接获取,这是隐私保护政策的一部分。
要解决序列号获取问题,首先需要理解Android权限系统的发展历程。Android的权限模型经历了多次重大变革:
| Android版本 | 权限模型特点 | 序列号访问方式 |
|---|---|---|
| 4.4及之前 | 安装时一次性授权 | 无特别限制 |
| 5.0-9.0 | 运行时权限请求 | Build.getSerial()+READ_PHONE_STATE |
| 10.0 | 引入Scoped Storage | 开始限制访问 |
| 11.0+ | 强化隐私保护 | 需要特权权限 |
在代码层面,Android 11对Build.getSerial()方法的实现做了关键修改:
java复制@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public static String getSerial() {
IDeviceIdentifiersPolicyService service = IDeviceIdentifiersPolicyService.Stub
.asInterface(ServiceManager.getService(Context.DEVICE_IDENTIFIERS_SERVICE));
try {
Application application = ActivityThread.currentApplication();
String callingPackage = application != null ? application.getPackageName() : null;
return service.getSerialForPackage(callingPackage, null);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
return UNKNOWN;
}
从源码可见,现在需要READ_PRIVILEGED_PHONE_STATE权限才能获取序列号,这是一个仅系统应用才能获得的签名级别权限。
既然直接获取序列号的方式在Android 11上受限,开发者需要考虑替代方案。以下是几种可行的技术路线:
Settings.Secure.ANDROID_ID提供设备唯一标识符对于必须获取真实序列号的特殊场景(如企业设备管理),可以考虑以下方法:
kotlin复制fun getDeviceSerial(context: Context): String {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
telephonyManager.serial
} catch (e: SecurityException) {
// 处理权限异常
"UNKNOWN"
}
} else {
Build.SERIAL
}
}
需要注意的是,即使这种方法在某些设备上可能有效,也不保证在所有Android 11设备上都可行。
要真正理解为什么Build.getSerial()在Android 11上失效,我们需要深入分析AOSP源码。关键检查发生在DeviceIdentifiersPolicyService中:
java复制@Override
public @Nullable String getSerialForPackage(String callingPackage, String callingFeatureId)
throws RemoteException {
if (!TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(
mContext, callingPackage, callingFeatureId, "getSerial")) {
return Build.UNKNOWN;
}
return SystemProperties.get("ro.serialno", Build.UNKNOWN);
}
权限检查的核心逻辑在TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers方法中,它会验证调用者是否满足以下条件之一:
READ_PRIVILEGED_PHONE_STATE权限对于普通应用,这些条件几乎都不可能满足,因此会返回UNKNOWN。
在企业应用场景中,确实可能需要获取设备真实序列号进行设备管理。以下是几种合规的方案:
设备管理API:
系统级集成:
OEM合作:
实现示例(设备管理员应用):
xml复制<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
java复制DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
ComponentName adminComponent = new ComponentName(this, MyDeviceAdminReceiver.class);
if (dpm.isDeviceOwnerApp(getPackageName())) {
String serial = Build.getSerial(); // 现在可以获取真实序列号
}
在开发过程中,验证权限问题至关重要。以下是一些实用的adb命令,可以帮助调试序列号获取问题:
bash复制# 检查应用已获得的权限
adb shell dumpsys package <your.package.name> | grep permission
# 模拟权限授予
adb shell pm grant <your.package.name> android.permission.READ_PHONE_STATE
# 获取设备序列号(需要root)
adb shell getprop ro.serialno
# 检查特权权限状态
adb shell cmd deviceid get serial
通过这些命令,开发者可以在不修改代码的情况下,快速验证各种权限场景下的行为差异。
在实际项目中,我们往往会遇到各种边缘情况。例如,某些OEM厂商可能会修改默认的权限检查行为,或者提供自己的API来获取设备信息。这种情况下,最好的做法是:
Android设备标识符的获取策略仍在不断演变,随着Android 12、13的发布,Google引入了更多隐私保护措施。作为开发者,我们需要持续关注这些变化,确保应用既满足功能需求,又符合最新的隐私保护标准。