01-17-02 向后兼容的设计哲学什么是向后兼容向后兼容Backward Compatibility新版本系统能够运行为旧版本系统开发的应用。目标Android 14可以运行为Android 5.0开发的应用为什么需要向后兼容问题场景应用开发于2015年targetSdkVersion 22 用户设备升级到Android 14API 34 应用是否还能正常运行答案必须能运行否则用户升级系统后大量应用崩溃。Google的承诺Android commits to app compatibility应用一次开发多年可用系统升级不影响已有应用除非明确标记Breaking Change向后兼容的实现机制1. targetSdkVersion行为开关源码中大量使用targetSdkVersion判断// ActivityManagerService.javaif(app.targetSdkVersionBuild.VERSION_CODES.M){// 为旧应用提供旧行为grantAllPermissionsAtInstallTime(app);}else{// 新应用使用运行时权限requireRuntimePermissions(app);}实际案例权限系统// Android 6.0引入运行时权限但不影响旧应用publicclassPermissionManager{publicvoidgrantPermissions(PackageInfopkg){if(pkg.targetSdkVersionBuild.VERSION_CODES.M){// targetSdk 23安装时授予所有权限旧行为for(Stringperm:pkg.requestedPermissions){grantPermission(pkg.packageName,perm);}}else{// targetSdk 23运行时申请危险权限新行为for(Stringperm:pkg.requestedPermissions){if(isNormalPermission(perm)){grantPermission(pkg.packageName,perm);}// 危险权限需要运行时申请}}}}实际案例文件访问// Android 7.0禁止file:// URI但不影响旧应用publicclassStrictMode{publicstaticvoidcheckFileUriExposed(Uriuri){if(uri.getScheme().equals(file)){// 获取调用者的targetSdkVersioninttargetSdkgetCallingPackageTargetSdk();if(targetSdkBuild.VERSION_CODES.N){// targetSdk 24抛出异常thrownewFileUriExposedException(file:// Uri exposed beyond app);}else{// targetSdk 24只打印警告不崩溃Log.w(TAG,file:// Uri exposed, but app targets old SDK);}}}}2. 默认值保持不变新功能默认关闭需要显式启用// Android 9引入明文HTTP限制// AndroidManifest.xmlapplication android:usesCleartextTraffictrue!--显式允许HTTP--/application// 源码实现publicclassNetworkSecurityPolicy{publicbooleanisCleartextTrafficPermitted(){if(targetSdkVersionBuild.VERSION_CODES.P){// 旧应用默认允许HTTPreturntrue;}else{// 新应用默认禁止除非显式配置returnapplicationInfo.usesCleartextTraffic;}}}3. API保持稳定已发布的Public API永不删除// 即使功能废弃API也保留DeprecatedpublicStringgetDeviceId(){// Android 10返回null// 但不会删除方法避免旧应用崩溃if(Build.VERSION.SDK_INTBuild.VERSION_CODES.Q){if(!hasPermission(READ_PRIVILEGED_PHONE_STATE)){returnnull;}}returnmDeviceId;}4. 兼容层Shim为旧API提供新实现// Android 6.0删除Apache HTTP Client// 但在Android 9又重新打包为可选库// android.jar不再包含org.apache.http.*// 但提供独立的org.apache.http.legacy.jar// AndroidManifest.xmluses-library android:nameorg.apache.http.legacyandroid:requiredfalse/向后兼容的代价1. 代码复杂度增加// ActivityManagerService.java中大量的版本判断if(targetSdkLOLLIPOP){// Android 5.0以前的行为}elseif(targetSdkMARSHMALLOW){// Android 6.0以前的行为}elseif(targetSdkNOUGAT){// Android 7.0以前的行为}elseif(targetSdkOREO){// Android 8.0以前的行为}elseif(targetSdkPIE){// Android 9以前的行为}elseif(targetSdkQ){// Android 10以前的行为}else{// 最新行为}2. 性能损耗// 每次调用都需要检查版本publicvoidcheckPermission(Stringpermission){inttargetSdkgetTargetSdkVersion();// 额外开销if(targetSdkBuild.VERSION_CODES.M){// 旧逻辑}else{// 新逻辑}}3. 安全隐患// targetSdk 23的应用可以绕过运行时权限// 潜在隐私风险if(targetSdkBuild.VERSION_CODES.M){grantAllPermissions()// 安全隐患}向后兼容的边界什么会破坏兼容性删除Public API// [未通过] 不允许publicclassBuild{// public static String SERIAL; // 删除会导致旧应用崩溃}修改Public API签名// [未通过] 不允许// 旧版本publicvoidsetData(Stringdata){}// 新版本// public void setData(int data) { } // 修改参数类型会导致编译失败修改Public API行为// [未通过] 需要非常谨慎publicintgetVersion(){// 旧版本返回1// return 1;// 新版本返回2可能导致旧应用逻辑错误return2;}允许的Breaking Change只有在targetSdkVersion 新版本时才生效// [通过] 允许if(targetSdkBuild.VERSION_CODES.Q){// 新行为限制后台位置访问enforceBackgroundLocationRestriction();}else{// 旧行为允许后台位置访问}实战设计向后兼容的API案例1添加新参数// [未通过] 错误直接修改现有方法// fun loadImage(url: String)// fun loadImage(url: String, cache: Boolean) // 重载可能导致调用歧义// [通过] 正确使用重载默认参数funloadImage(url:String){loadImage(url,cachetrue)}funloadImage(url:String,cache:Boolean){// 实现}// [通过] 更好使用Builder模式classImageLoader{funload(url:String)ImageRequest(url)classImageRequest(valurl:String){varcachetruefuncache(enable:Boolean)apply{cacheenable}funexecute(){/* 加载图片 */}}}案例2废弃旧API// [通过] 正确的废弃流程classLocationManager{// 第1步标记为Deprecated提供替代方案Deprecated(messageUse getLastKnownLocation(LocationRequest) instead,replaceWithReplaceWith(getLastKnownLocation(LocationRequest())))fungetLastKnownLocation(provider:String):Location?{// 保留实现确保旧代码可用returngetLastKnownLocation(LocationRequest(provider))}// 第2步提供新APIfungetLastKnownLocation(request:LocationRequest):Location?{// 新实现}// 第3步若干版本后旧API可能仍保留只是功能受限// 绝不直接删除}案例3行为变更// [通过] 使用targetSdkVersion控制行为classNetworkManager{funsendRequest(url:String){// 检查明文HTTPif(url.startsWith(http://)){valtargetSdkcontext.applicationInfo.targetSdkVersionif(targetSdkBuild.VERSION_CODES.P){// targetSdk 28禁止明文HTTPif(!isHttpPermitted()){throwSecurityException(Cleartext HTTP not permitted)}}else{// targetSdk 28允许明文HTTP但打印警告Log.w(TAG,Cleartext HTTP traffic not recommended)}}// 发送请求executeRequest(url)}}Google的兼容性测试CTSCompatibility Test Suite# 测试设备是否兼容Android规范# 确保同一API Level的不同设备行为一致adb shell am instrument-w\-eclass android.app.cts.ActivityTest\android.app.cts/androidx.test.runner.AndroidJUnitRunnerApp Compatibility Testing# Google测试新Android版本对Top应用的兼容性# 发现问题会通知开发者或调整系统行为开发者最佳实践1. 及时更新targetSdkVersionandroid { defaultConfig { // [未通过] 不要一直用旧版本 // targetSdkVersion 22 // [通过] 尽量保持最新 targetSdkVersion 34 } }原因享受新特性和性能优化满足Google Play上架要求获得更好的用户体验2. 使用AndroidXdependencies { // [通过] 使用AndroidX库自动处理兼容性 implementation androidx.core:core-ktx:1.12.0 implementation androidx.appcompat:appcompat:1.6.1 }3. 添加版本检查// [通过] 使用新API前检查版本if(Build.VERSION.SDK_INTBuild.VERSION_CODES.O){createNotificationChannel()}4. 使用Lint检查./gradlew lint# 检测未加版本判断的新API调用总结向后兼容核心原则targetSdkVersion作为行为开关旧应用保持旧行为Public API永不删除保证旧代码可编译可运行新功能默认关闭需要显式启用提供兼容层为废弃功能提供替代方案兼容性边界允许基于targetSdkVersion的行为变更不允许删除或修改Public API谨慎修改API语义开发建议应用及时更新targetSdkVersion使用AndroidXSDK使用Deprecated标记提供迁移指南系统用targetSdkVersion控制Breaking Change关键要点向后兼容是Android生态成功的基石理解其实现机制有助于设计更好的API