当前位置: 首页 > news >正文

01-18-08 废弃API的处理方式

01-18-08 废弃API的处理方式

API废弃概述

API废弃(Deprecation)是软件演进的必然过程,Android通过渐进式废弃策略保证生态系统平稳过渡。

废弃原因

1. 安全问题 - 存在安全漏洞 - 无法修复的设计缺陷 2. 性能问题 - 低效的实现 - 资源浪费 3. 设计改进 - 更好的替代方案 - 接口简化 4. 平台演进 - 新特性支持 - 架构调整

@Deprecated注解

基本用法

/** * @Deprecated注解使用 * frameworks/base/core/java/android/app/ActivityManager.java */publicclassActivityManager{/** * Android 1.0 原始API * Android 5.0 废弃 * * @deprecated 从API 21开始废弃,使用{@link #getRunningAppProcesses()}替代 */@DeprecatedpublicList<RunningTaskInfo>getRunningTasks(intmaxNum)throwsSecurityException{try{returngetService().getTasks(maxNum);}catch(RemoteExceptione){throwe.rethrowFromSystemServer();}}/** * 推荐的替代方法 */publicList<RunningAppProcessInfo>getRunningAppProcesses(){try{returngetService().getRunningAppProcesses();}catch(RemoteExceptione){throwe.rethrowFromSystemServer();}}}

Kotlin @Deprecated

/** * Kotlin废弃注解 */classExample{/** * 废弃方法 - 提供替代方案 */@Deprecated(message="Use newMethod() instead",replaceWith=ReplaceWith("newMethod()"),level=DeprecationLevel.WARNING)funoldMethod(){// 旧实现}/** * 新方法 */funnewMethod(){// 新实现}/** * 强制废弃 - ERROR级别 */@Deprecated(message="This method is no longer supported",level=DeprecationLevel.ERROR)funobsoleteMethod(){throwUnsupportedOperationException("Method is obsolete")}/** * 隐藏废弃 - HIDDEN级别 */@Deprecated(message="Internal use only",level=DeprecationLevel.HIDDEN)funinternalMethod(){// 仅内部使用}}/** * DeprecationLevel级别: * - WARNING: 编译警告,代码仍可用 * - ERROR: 编译错误,但可通过@Suppress抑制 * - HIDDEN: 完全隐藏,无法使用 */

渐进式废弃策略

废弃时间线

阶段0: 正常API(API N) ├─ API稳定 ├─ 文档完善 └─ 广泛使用 阶段1: 标记废弃(API N+1) ├─ 添加@Deprecated注解 ├─ 更新JavaDoc说明废弃原因 ├─ 提供替代方案 └─ IDE显示删除线 阶段2: 行为限制(API N+3) ├─ targetSdk >= N+3: 限制功能或返回默认值 ├─ targetSdk < N+3: 保持原有行为(兼容性) └─ 运行时日志警告 阶段3: 移除API(API N+6+) ├─ 完全移除代码 ├─ 抛出UnsupportedOperationException └─ 更新兼容性文档 典型时间跨度:6-8年(约3-4个Android大版本)

实际案例:getExternalStorageDirectory

/** * 存储API废弃演进 * frameworks/base/core/java/android/os/Environment.java */publicclassEnvironment{/** * 阶段1: Android 10 (API 29) 标记废弃 * @deprecated 使用Context#getExternalFilesDir(String)替代 */@Deprecated@NonNullpublicstaticFilegetExternalStorageDirectory(){returnsCurrentUser.getExternalDirs()[0];}/** * 阶段2: Android 10 行为变更 * targetSdk >= 29: 启用分区存储 */privatestaticFilegetExternalStorageDirectoryInternal(){ApplicationInfoappInfo=ActivityThread.currentApplication().getApplicationInfo();if(appInfo.targetSdkVersion>=Build.VERSION_CODES.Q){// 分区存储模式:限制访问Log.w(TAG,"getExternalStorageDirectory() is deprecated, "+"use getExternalFilesDir() instead");// 返回应用私有目录returnsCurrentUser.getExternalDirs()[0];}else{// 兼容模式:返回公共存储returngetDataDirectory();}}}

HttpClient废弃案例

/** * Apache HttpClient废弃 * Android 6.0 (API 23) 移除 */// Android 5.1及以下:可用importorg.apache.http.client.HttpClient;importorg.apache.http.impl.client.DefaultHttpClient;HttpClientclient=newDefaultHttpClient();// Android 6.0及以上:移除// 编译错误:Cannot resolve symbol 'DefaultHttpClient'// 替代方案1:HttpURLConnectionURLurl=newURL("https://example.com");HttpURLConnectionurlConnection=(HttpURLConnection)url.openConnection();try{InputStreamin=urlConnection.getInputStream();// 读取数据}finally{urlConnection.disconnect();}// 替代方案2:OkHttp(推荐)OkHttpClientclient=newOkHttpClient();Requestrequest=newRequest.Builder().url("https://example.com").build();Responseresponse=client.newCall(request).execute();

targetSdkVersion行为限制

废弃API的targetSdk检查

/** * 基于targetSdk的行为调整 * frameworks/base/core/java/android/app/ApplicationPackageManager.java */publicclassApplicationPackageManagerextendsPackageManager{/** * 获取已安装应用列表 * Android 11 (API 30): 包可见性限制 */@OverridepublicList<ApplicationInfo>getInstalledApplications(intflags){ApplicationInfoappInfo=mContext.getApplicationInfo();// targetSdk >= 30: 包可见性过滤if(appInfo.targetSdkVersion>=Build.VERSION_CODES.R){// 只返回可见的应用returngetInstalledApplicationsFiltered(flags);}else{// targetSdk < 30: 返回所有应用(兼容性)returngetInstalledApplicationsUnfiltered(flags);}}/** * 获取应用签名 * Android 9.0 (API 28): GET_SIGNATURES废弃 */@OverridepublicPackageInfogetPackageInfo(StringpackageName,intflags)throwsNameNotFoundException{ApplicationInfoappInfo=mContext.getApplicationInfo();// 检查是否使用废弃的GET_SIGNATURESif((flags&GET_SIGNATURES)!=0){if(appInfo.targetSdkVersion>=Build.VERSION_CODES.P){// targetSdk >= 28: 记录警告Log.w(TAG,"Use GET_SIGNING_CERTIFICATES instead of GET_SIGNATURES");}}returngetPackageInfoAsUser(packageName,flags,getUserId());}}

Notification API废弃

/** * Notification API演进 * frameworks/base/core/java/android/app/Notification.java */publicclassNotificationimplementsParcelable{/** * Android 4.1之前的方式(废弃) * @deprecated 使用{@link Builder}替代 */@DeprecatedpublicNotification(inticon,CharSequencetickerText,longwhen){this.icon=icon;this.tickerText=tickerText;this.when=when;}/** * Android 4.1+ 推荐方式:Builder模式 */publicstaticclassBuilder{privateContextmContext;privateNotificationmN;publicBuilder(Contextcontext){this(context,null);}/** * Android 8.0+ 必须指定Channel */publicBuilder(Contextcontext,StringchannelId){mContext=context;mN=newNotification();// Android 8.0+: 检查targetSdkApplicationInfoappInfo=context.getApplicationInfo();if(appInfo.targetSdkVersion>=Build.VERSION_CODES.O){// 必须设置Channelif(TextUtils.isEmpty(channelId)){thrownewIllegalArgumentException("Must specify notification channel for targetSdk >= 26");}}mN.channelId=channelId;}publicBuildersetSmallIcon(inticon){mN.icon=icon;returnthis;}publicBuildersetContentTitle(CharSequencetitle){mN.extras.putCharSequence(EXTRA_TITLE,title);returnthis;}publicNotificationbuild(){returnmN;}}}

废弃API的文档处理

JavaDoc废弃标记

/** * JavaDoc废弃文档示例 */publicclassApiExample{/** * 获取设备ID * * <p>此方法在Android 10 (API 29)中废弃,出于隐私保护考虑。 * * <p><strong>废弃原因:</strong> * <ul> * <li>可被用于跟踪用户</li> * <li>无法重置</li> * <li>违反用户隐私</li> * </ul> * * <p><strong>替代方案:</strong> * <ul> * <li>使用{@link java.util.UUID#randomUUID()}生成应用级唯一ID</li> * <li>使用{@link android.provider.Settings.Secure#ANDROID_ID}获取设备ID(可重置)</li> * <li>使用{@link com.google.android.gms.ads.identifier.AdvertisingIdClient}获取广告ID</li> * </ul> * * <p><strong>迁移指南:</strong> * <pre>{@code * // 旧代码(已废弃) * String deviceId = telephonyManager.getDeviceId(); * * // 新代码(推荐) * String androidId = Settings.Secure.getString( * context.getContentResolver(), * Settings.Secure.ANDROID_ID * ); * }</pre> * * @return 设备ID * @throws SecurityException 如果应用没有READ_PHONE_STATE权限 * @deprecated 从API 29开始废弃,使用替代方案 * @see android.provider.Settings.Secure#ANDROID_ID */@Deprecated@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)publicStringgetDeviceId(){ApplicationInfoappInfo=mContext.getApplicationInfo();if(appInfo.targetSdkVersion>=Build.VERSION_CODES.Q){// API 29+: 抛出异常thrownewSecurityException("getDeviceId is no longer supported for targetSdk >= 29");}// API 28-: 返回实际值(兼容性)returngetDeviceIdInternal();}}

API差异文档

Android API差异文档(API 29 → API 30) ==== 废弃的API ==== 1. android.telephony.TelephonyManager - getDeviceId() [废弃] 原因:隐私保护 替代:Settings.Secure.ANDROID_ID - getSimSerialNumber() [废弃] 原因:隐私保护 替代:无替代方案(需用户授权) 2. android.os.Environment - getExternalStorageDirectory() [废弃] 原因:分区存储 替代:Context.getExternalFilesDir() 3. android.content.pm.PackageManager - getInstalledApplications() [行为变更] 原因:包可见性 影响:targetSdk >= 30 只返回可见应用 迁移:声明<queries>标签 ==== 新增的API ==== 4. android.window.WindowMetrics + getBounds() [新增] 用途:获取窗口边界 替代:Display.getSize()(已废弃) ==== 行为变更 ==== 5. 后台位置权限 - 需要单独请求ACCESS_BACKGROUND_LOCATION - 不能与前台位置权限一起请求

Lint检查规则

自定义Lint检测废弃API

/** * 自定义Lint检测器:检测废弃API使用 */classDeprecatedApiDetector:Detector(),SourceCodeScanner{overridefungetApplicableMethodNames():List<String>{returnlistOf("getDeviceId","getExternalStorageDirectory","getRunningTasks")}overridefunvisitMethodCall(context:JavaContext,node:UCallExpression,method:PsiMethod){valannotation=method.getAnnotation("java.lang.Deprecated")if(annotation!=null){// 检查targetSdkvalproject=context.projectvaltargetSdk=project.targetSdkif(targetSdk>=29){context.report(DEPRECATED_API,node,context.getLocation(node),"Deprecated API should not be used with targetSdk >= 29",createQuickFix(method))}}}privatefuncreateQuickFix(method:PsiMethod):LintFix?{// 根据废弃的方法提供快速修复returnwhen(method.name){"getDeviceId"->{LintFix.create().replace().text(method.name).with("Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID)").build()}else->null}}companionobject{valDEPRECATED_API=Issue.create(id="DeprecatedApi",briefDescription="Using deprecated API",explanation="This API has been deprecated and should not be used",category=Category.CORRECTNESS,priority=6,severity=Severity.WARNING,implementation=Implementation(DeprecatedApiDetector::class.java,Scope.JAVA_FILE_SCOPE))}}

废弃API的运行时处理

异常抛出

/** * 废弃API运行时异常 */publicclassTelephonyManager{/** * 获取设备ID - 废弃API */@Deprecated@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)publicStringgetDeviceId(){ApplicationInfoappInfo=mContext.getApplicationInfo();if(appInfo.targetSdkVersion>=Build.VERSION_CODES.Q){// targetSdk >= 29: 抛出异常thrownewUnsupportedOperationException("This method is no longer supported. "+"See https://developer.android.com/training/articles/user-data-ids");}// targetSdk < 29: 记录警告Log.w(TAG,"getDeviceId() is deprecated, "+"see https://developer.android.com/training/articles/user-data-ids");// 返回实际值(兼容性)returngetDeviceIdInternal();}}

日志警告

/** * 废弃API调用日志 * frameworks/base/core/java/android/util/Log.java */publicfinalclassLog{/** * 记录废弃API调用 */publicstaticvoidwarnDeprecatedApi(Stringapi,Stringreplacement){ApplicationInfoappInfo=ActivityThread.currentApplication().getApplicationInfo();Stringmessage=String.format("Application %s (targetSdk=%d) calls deprecated API: %s, "+"use %s instead",appInfo.packageName,appInfo.targetSdkVersion,api,replacement);Log.w("DeprecatedApi",message);// StrictMode检测if(StrictMode.getVmPolicy().penaltyListener!=null){StrictMode.onDeprecatedApiUsed(api);}}}

StrictMode检测

/** * StrictMode检测废弃API */objectDeprecatedApiMonitor{/** * 启用废弃API检测 */funenableDetection(){if(BuildConfig.DEBUG){StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder().detectAll().penaltyLog().penaltyListener(Executors.newSingleThreadExecutor()){violation->Log.e("StrictMode","VM policy violation",violation)// 检测废弃API使用if(violationisDeprecatedApiViolation){handleDeprecatedApiViolation(violation)}}.build())}}privatefunhandleDeprecatedApiViolation(violation:DeprecatedApiViolation){valmessage=""" Deprecated API used: API:${violation.apiName}Stack:${violation.stackTrace}Replacement:${violation.replacement}""".trimIndent()Log.w("DeprecatedApi",message)// 上报到监控系统reportToAnalytics(violation)}privatefunreportToAnalytics(violation:DeprecatedApiViolation){// 上报到Firebase/Sentry等}}

总结

废弃API处理原则

  1. 渐进式废弃:分阶段执行(标记→限制→移除)
  2. 充分通知:JavaDoc、Release Notes、开发者文档
  3. 提供替代:明确指出替代方案和迁移路径
  4. 兼容性保证:targetSdk < N 保持原有行为
  5. 运行时提示:日志警告、异常信息
  6. 工具支持:Lint检查、IDE提示

废弃时间线

API N: 发布新API API N+1: 标记旧API为@Deprecated API N+3: targetSdk >= N+3 限制功能 API N+6+: 完全移除旧API 时间跨度: 6-8年

最佳实践

  • 及时更新targetSdk
  • 关注API废弃通知
  • 使用Lint检测废弃API
  • 测试不同Android版本
  • 提供降级方案

Android 16 (API 36) 废弃API变化

Android 16 新增废弃API

后台启动Activity完全废弃

// Android 16:targetSdk >= 36 时,以下方法受限// startActivity() 从后台调用将抛出异常@Deprecated(message="从后台启动Activity在Android 16被完全限制",level=DeprecationLevel.WARNING)funstartFromBackground(context:Context,intent:Intent){if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.BAKLAVA){// 使用 PendingIntent 或通知启动valpendingIntent=PendingIntent.getActivity(context,0,intent,PendingIntent.FLAG_UPDATE_CURRENTorPendingIntent.FLAG_IMMUTABLE)// 通过通知栏触发}}

旧版前台服务API废弃

// Android 16:不带类型的前台服务完全废弃@Deprecated(message="使用 startForeground(type, notification) 替代",replaceWith=ReplaceWith("startForeground(ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC, notification)"),level=DeprecationLevel.ERROR// targetSdk 36 直接编译错误)funstartForegroundServiceLegacy(){// 旧方式:不再允许}// 新方式funstartForegroundServiceModern(){startForeground(ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,notification)}

StrictMode 废弃API检测增强

// Android 16:StrictMode 对废弃API的检测更严格StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder().detectAll().penaltyLog()// Android 16 新增:废弃API使用可配置为崩溃.penaltyDeathOnDeprecatedApiUsage()// 调试模式.build())

关键要点:API废弃是Android演进的必然,通过渐进式策略和targetSdk检查确保生态系统平稳过渡

http://www.jsqmd.com/news/643002/

相关文章:

  • springboot基于SpringBoot的养老中心管理系统_i9o9c8r5
  • GMSSH 是什么?一款面向 AI 时代的可视化服务器运维系统
  • 陕西省 4 月软件开发岗位与政府岗位就业信息
  • 优峰技术:中心波长可调滤波器在光通信测试中的应用与选型
  • 微博相册批量下载工具:3步实现多线程高效下载
  • Java高频面试题:03
  • Gazebo仿真机器人和相机时Gazebo ROS Control 插件偶发性加载失败bug分析
  • 前端开发必看:除了转义,你的React/Vue项目真的防住XSS了吗?
  • springboot基于SpringBoot的足球俱乐部管理系统设计与实现_5b388h04_zl040
  • CSS如何创建响应式导航栏菜单_结合Flexbox与媒体查询
  • 利用GraphvizOnline快速生成深度学习模型模块的交互式流程图
  • C++入门基础知识
  • 配置 PyCharm(汉化版操作指南)
  • 并发问题排查
  • java基于SpringBoot的校园设备维护报修系统_rwh2qh1u
  • 此数学博导等编《数学分析讲义》 有非常低级的概念性错误
  • 搭建CMD编译C语言环境
  • 从零搭建AMESim与Matlab/Simulink联合仿真环境(2024版软件配置详解)
  • 安防场景的技术架构:从“被动监控”到“主动防御”的演进之路
  • springboot基于微信小程序的智慧社区娱乐服务管理平台_jm78648u_zz042
  • 深入浅出——用Excel硬核拆解多层感知机(MLP)的数学原理
  • mac的node版本安装及升降级
  • AI Harness(AI驾驭/AI约束框架)
  • 2025终极网盘下载加速方案:八大平台直链解析工具完整指南
  • 从AFDB到本地:手把手教你用ColabFold和Foldseek搞定蛋白质结构预测与搜索
  • 从源码到运行:手把手编译CPU版vLLM适配Qwen2
  • 基于JavaWeb电影院订票购票系统设计与实现+万字文档
  • 七牛云多语言文件上传路径配置实战指南
  • 用Matlab手把手搭建LQG控制器:从四分之一车模型到随机路面仿真(附避坑指南)
  • 深入解析SN65HVD230、SN65HVD231、SN65HVD232在低功耗设计中的关键差异与应用选型