Android 11 上获取设备序列号的终极指南:从权限申请到源码分析(附避坑清单)

张开发
2026/4/19 10:26:26 15 分钟阅读

分享文章

Android 11 上获取设备序列号的终极指南:从权限申请到源码分析(附避坑清单)
Android 11设备序列号获取全解析从权限机制到合规方案在移动应用开发领域设备序列号(Serial Number)一直扮演着重要角色它被广泛应用于设备识别、授权验证和统计分析等场景。然而随着Android 11的发布许多开发者突然发现原本运行良好的序列号获取代码不再奏效。这背后反映的是Android系统在隐私保护和安全机制上的重大变革。1. Android序列号获取机制的演变历程要理解当前的问题我们需要回溯Android设备标识符的发展历程。在早期版本中获取设备序列号就像访问一个公开属性那样简单String serial Build.SERIAL;这种简单粗暴的方式在Android 8.0(Oreo)迎来了第一次变革。Google引入了更严格的权限控制开发者需要显式申请READ_PHONE_STATE权限才能获取序列号if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { serial Build.getSerial(); // 需要READ_PHONE_STATE权限 }来到Android 10情况变得更加复杂。Google引入了沙盒存储机制(Scoped Storage)同时对设备标识符的访问进行了进一步限制。但真正的地震发生在Android 11此时即使拥有READ_PHONE_STATE权限普通应用也无法获取设备序列号了。关键变化点对比Android版本访问方式所需权限可用性8.0Build.SERIAL无完全开放8.0-10Build.getSerial()READ_PHONE_STATE受限访问≥11Build.getSerial()READ_PRIVILEGED_PHONE_STATE仅系统应用2. 深入源码理解Android 11的限制机制当我们在Android 11上调用Build.getSerial()时实际上触发了一系列系统级检查。让我们通过源码分析这一过程// frameworks/base/core/java/android/os/Build.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; }关键点在于IDeviceIdentifiersPolicyService这个系统服务它实际控制着序列号的访问权限。继续深入DeviceIdentifiersPolicyService.java// frameworks/base/services/core/java/com/android/server/os/DeviceIdentifiersPolicyService.java Override public String getSerialForPackage(String callingPackage, String callingFeatureId) { if (!TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers( mContext, callingPackage, callingFeatureId, getSerial)) { return Build.UNKNOWN; } return SystemProperties.get(ro.serialno, Build.UNKNOWN); }这里揭示了Android 11的核心限制逻辑系统会检查调用者是否具有READ_PRIVILEGED_PHONE_STATE权限该权限是签名级别(signature)的普通应用无法获取没有权限的应用将始终得到UNKNOWN返回值3. 合规替代方案探索既然直接获取序列号的路被堵死我们需要寻找其他合规的替代方案。Google官方推荐使用广告ID或Instance ID作为设备标识符// 使用广告ID作为替代方案 AdvertisingIdClient.Info adInfo AdvertisingIdClient.getAdvertisingIdInfo(context); String advertisingId adInfo.getId(); // 或者使用Instance ID String instanceId InstanceID.getInstance(context).getId();不同标识符的特性对比标识符类型唯一性重置性隐私合规性适用范围设备序列号永久唯一不可重置低已受限广告ID设备级别用户可重置高推荐Instance ID应用级别应用卸载重置高推荐Android ID设备级别恢复出厂设置重置中部分场景对于必须使用硬件序列号的特殊场景如企业设备管理可以考虑以下方案使用设备管理API// 在DeviceAdminReceiver中 Override public void onEnabled(Context context, Intent intent) { DevicePolicyManager dpm (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); String serial dpm.getDeviceId(); }通过USB或ADB获取 对于调试场景可以通过ADB命令获取adb shell getprop ro.serialno4. 实战构建未来兼容的设备识别方案基于以上分析我们设计一个健壮的设备识别方案应该考虑以下要素分层获取策略public String getDeviceIdentifier(Context context) { // 首选广告ID try { AdvertisingIdClient.Info adInfo AdvertisingIdClient .getAdvertisingIdInfo(context); if (adInfo ! null) { return adInfo.getId(); } } catch (Exception e) { // 处理异常 } // 次选Android ID String androidId Settings.Secure.getString( context.getContentResolver(), Settings.Secure.ANDROID_ID); // 最后考虑序列号(仅限有权限的情况) if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { return Build.SERIAL; } else if (checkPermission(context, Manifest.permission.READ_PHONE_STATE)) { return Build.getSerial(); } return generateRandomFallbackId(); // 最终回退方案 }缓存机制将获取到的标识符安全存储在SharedPreferences中添加版本控制和过期机制考虑使用加密存储增强安全性迁移策略// 新旧标识符对照表 public void migrateToNewIdSystem(Context context) { String oldId prefs.getString(legacy_device_id, null); String newId getAdvertisingId(); if (oldId ! null) { // 上报服务器建立映射关系 serverApi.registerIdMapping(oldId, newId); } // 更新本地存储 prefs.edit().putString(current_device_id, newId).apply(); }5. 避坑指南与最佳实践在实现设备识别功能时以下几个关键点值得特别注意权限申请的正确姿势在AndroidManifest.xml中声明uses-permission android:nameandroid.permission.READ_PHONE_STATE /运行时动态申请if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) ! PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_PHONE_STATE}, REQUEST_CODE); }常见问题排查清单检查targetSdkVersion是否≥29确认权限是否在运行时正确获取验证应用签名是否匹配(对特权权限)检查是否在后台线程调用广告IDAPI确认设备是否安装了Google Play服务性能优化建议对广告ID等需要网络连接的API考虑异步获取实现本地缓存机制避免重复查询对关键操作添加适当的超时处理在为企业客户开发定制ROM时如果需要保留序列号访问能力可以考虑以下安全方案实现一个自定义权限permission android:namecom.company.CUSTOM_DEVICE_ID_ACCESS android:protectionLevelsignature /创建扩展的DeviceIdentifiersPolicyServicepublic class CustomDeviceIdentifiersService extends DeviceIdentifiersPolicyService { Override public String getSerialForPackage(String callingPackage, String callingFeatureId) { if (checkCustomPermission(callingPackage)) { return super.getSerialForPackage(callingPackage, callingFeatureId); } return Build.UNKNOWN; } }这种方案既满足了业务需求又遵循了最小权限原则是相对安全的实现方式。

更多文章