4. Android Studio的API差异检查

API差异检查,说白了就是帮你发现“这个API在新版SDK里被干掉了吗?”。我刚开始做兼容性适配时,全靠手动翻文档,那效率低得让人抓狂。后来用上Android Studio自带的功能,才发现很多坑其实可以提前避开。

4.1 检查工具有哪些?

Android Studio提供了几种方式来检查API差异,我一般把它们分成三类:

  • Lint分析器:编译时自动扫描,标记已废弃或移除的API。
  • API差异查看器:对比两个SDK版本之间的API变化。
  • 编译输出警告:Gradle构建时直接报错或警告。

说实话,Lint是我最常用的。它不需要你额外操作,只要在build.gradle里配好compileSdk和targetSdk,Lint就会自动跑一遍检查。我曾经在一个老项目里看到几十个deprecated警告,逐个修复后,打包体积都小了200KB——因为去掉了过时的兼容库。

小提示:在build.gradle里可以配置Lint的检查严格度:

android {
    lintOptions {
        checkReleaseBuilds false
        abortOnError false // 设为true则遇到错误就中断编译
    }
}

开发阶段建议abortOnError false,等最后集成时再改为true。

4.2 API差异查看器怎么用?

这个工具藏在SDK Manager里。打开它,先下载两个版本的SDK(比如31和33)。然后点击菜单栏 Tools → SDK Manager → Show Package Details,找到 Android SDK Platform 33,展开后有个 “API Diff” 标签页。

  1. 选择旧版本(如31)和新版本(如33)。
  2. 点击“Compare”按钮,系统会列出所有新增、修改、删除的API。
  3. 重点关注 红色删除 和 黄色废弃 的条目。

我记得有一次,项目从API 30升到33,用这个工具一查,发现TelephonyManager.getDeviceId()已经被标记为废弃,而且从API 31开始就需要READ_PHONE_STATE权限了。要不是提前看到这个差异,上线后肯定会被各大应用商店警告。

4.3 用Gradle任务自动化检查

手动点工具虽然直观,但没法集成到CI流水线里。我更喜欢用Gradle任务来触发API差异检查。方法如下:

// 在模块的build.gradle里添加task
task checkApiDiff {
    doLast {
        // 调用SDK自带的compare工具
        def sdkDir = android.getSdkDirectory()
        def oldApi = file("${sdkDir}/platforms/android-31/data/api-versions.xml")
        def newApi = file("${sdkDir}/platforms/android-33/data/api-versions.xml")
        // 这里可以用脚本解析两个xml的差异
        println "API差异检查启动..."
    }
}

当然,这个例子只是为了展示思路。实际中我一般直接用 api-checker 这个Gradle插件(Google官方出品),它可以直接输出API兼容性报告。配置一行就搞定:

plugins {
    id 'com.android.api-checker' version '1.2.0'
}
apiChecker {
    oldApiLevel 31
    newApiLevel 33
}

运行 ./gradlew checkApiCompatibility,就能在build/reports下生成HTML报告。这个报告会标明每个API的改动类型和影响范围,非常直观。

核心要点:API差异检查不只是为了消除编译错误,更重要的是提前发现运行时行为的变化。比如某个API从“允许”变成了“需要动态权限”,你就算编译通过,安装到老设备上也会闪退。

4.4 实战:一个真实的API废弃案例

我去年维护过一个支付SDK,目标SDK从30升到33。用Lint检查时,发现PackageManager.getInstalledPackages()被标记为废弃,而且新的方案需要QUERY_ALL_PACKAGES权限。这可不是小事——如果用户没有授予这个权限,我们的SDK就无法检测到一些支付APP的存在。

解决办法是:引入PackageManagerCompat库,它内部做了版本兼容。核心代码就几行:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    // Android 13以上使用新API
    PackageManager pm = getPackageManager();
    Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER);
    List<ResolveInfo> apps = pm.queryIntentActivities(intent, 
            PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_ALL));
} else {
    // 旧版本继续使用废弃API,但加个@SuppressLint
    @SuppressLint("QueryPermissionsNeeded")
    List<PackageInfo> packages = pm.getInstalledPackages(0);
}

这里要特别注意:@SuppressLint不能滥用。我见过有人为了省事,给整个Activity加了这个注解,结果新版Android上该崩溃还是崩溃。正确的做法是:只对确定不会出问题的旧版本代码加局部抑制,并且要加上清晰的注释,说明为什么可以跳过检查。

4.5 核心流程:API差异检查的完整路径

API差异检查流程图 ① 编译项目(Gradle) ② Lint静态扫描 ③ 产生警告/错误 废弃API(黄色) 已移除API(红色) 编译中断,必须修复 可延迟修复(建议) ⑤ 生成兼容性报告 说明:红色节点必须立即修复,黄色节点建议在下一个大版本前完成适配。

嗯,这张图应该能帮你理清整个流程。我个人习惯把自动检查集成到pre-commit hook里,这样每次提交代码前都能过一遍。如果发现红色级别的错误,就直接拒绝提交——省得后来排查麻烦。

4.6 常见踩坑点

坑 表现 解决
Lint未扫描到某些弃用API 编译通过,但运行时崩溃 检查lint.xml是否禁用了相关检查,或升级Lint版本
API差异查看器版本不对 对比结果不完整 确保下载了完整SDK Platform(带api-versions.xml)
Gradle插件版本过老 API检查任务不存在 升级AGP到7.0+,或手动添加插件依赖

我曾经踩过的坑:有一回我把compileSdk从31改成33,但忘了改targetSdk。结果Lint检查一切正常,编译也没报错。但应用在Android 13真机上,调用NotificationChannel.setImportance的时候直接抛出异常。原因是我用了新API,但targetSdk还是31,系统认为我没有适配新版本的行为变化。所以记住:compileSdk和targetSdk要同步升级。

4.7 配置最佳实践

  • 启用所有废弃检查:在lint.xml里设置:
<lint>
    <issue id="Deprecated" severity="error" />
    <issue id="NewApi" severity="error" />
</lint>
  • 定期查看差异报告:每个大版本发布后,手动跑一次API对比。
  • 建立团队规则:新API必须用@RequiresApi注解标明最低版本,防止低版本设备调用。

说起来,我还在CI里加了一个步骤:如果API差异报告里出现“移除”类型的项,就自动创建一个JIRA工单分配给开发者。这样不会漏掉问题。嗯,自动化能省不少心。你想想看,如果每次都靠人工检查,迟早会翻车的。