资讯动态

android-33 编译链路升级与 targetSdk 适配排查

发布时间:2026/10/27 21:02:41 来源:尧图企业网站定制
简介面向需要补齐 Android 33 平台环境的应用开发者这份 SDK 平台包对应 API 33Android 13可用于在 Android Studio 等 IDE 中编译、调试和验证面向该版本的应用解决本地 SDK 缺少对应 platforms 版本时的环境搭建问题。压缩包共 2000 个文件以 1974 个 XML 为主另有 22 个 HTML 和 4 个 TXT整体约 60.84MBXML 多承载平台资源、配置与接口描述HTML、TXT 偏文档补充目录组织便于按平台模块检索。已有 855 人学习下载适合需要离线配置或对照平台文件结构的开发者。包内提供该版本平台的核心文件与文档线索便于快速补齐编译依赖、核对 API 级别兼容性并为后续适配 Android 33 特性提供参考。1. android-33 不是“再装个包”它决定你整条编译链路的版本下限上周帮同事看一个构建失败日志最后一行是Failed to find target with hash string android-33但前面几十行报的是 AGP 版本警告和 JDK 版本警告——三个问题其实来自一个根因他们以为 android sdk android-33 只是 SDK Manager 里勾选一个包的事。实际不是。android-33指的是 Android SDK Platform 33也就是 API Level 33 对应的编译目标平台包里最核心的是那份android.jar外加可选的系统镜像和source.properties真正决定你能编译出什么的是它真正决定用户手机会怎么对待你的应用的却是另一个叫targetSdk的开关。维护存量 App、要接 Android 13 的通知权限与媒体权限拆分、被应用商店的 targetSdk 门槛卡住的人都得先把这个版本号背后的三条链路理清否则就会出现“本地能跑、CI 挂了”“编译通过、一装到新机器就静默失效”这类问题。下面按我自己走过一遍的顺序拆先讲清三个版本号各管哪一段再落安装、改配置、排查最后讲怎么把它固定住。2. compileSdk 33、targetSdk 33、minSdk三个版本号各管一段链路2.1 三个版本号分别约束什么很多人升版本时的动作是“把 build.gradle 里所有 30 改成 33”改完编译过了就以为完事。这三个字段的作用域完全不重叠混着改最容易出事。compileSdk管的是编译期。它决定android.jar用哪一份也就决定了你代码里能不能写出Build.VERSION_CODES.TIRAMISU、能不能调用 API 33 才有的方法以及 lint 拿哪套规则检查你。它不影响运行你把compileSdk提到 33 而targetSdk留在 30应用在 Android 13 手机上依然按 Android 10 的兼容策略跑新行为一个都不会触发。这个组合在过渡期非常有用想先用新 API 但还没精力处理行为变更就先只抬compileSdk。minSdk管的是安装门槛和运行时下限。设成 21那 Android 5.0 以下的机器在应用商店里根本看不到你的 App。它同时约束你写代码的方式——凡是用到 API 23 以上才有的方法都得套一层Build.VERSION.SDK_INT判断或者交给RequiresApi标注。minSdk不跟着一起涨你就得一直背着这些兼容分支。targetSdk管的是系统怎么“看待”你。它是三者里唯一会真的改变运行时行为的字段系统会拿它跟当前系统版本比对低于系统版本就开一批兼容开关等于或高于就按新规则严格执行。通知权限、后台启动限制、存储访问方式全部由它触发。所以升级的真正工作量从来不在compileSdk而在targetSdk。一个常见组合是compileSdk 33、targetSdk 33、minSdk 21。注意一个方向性错误targetSdk不能高于compileSdk你把targetSdk设成 33 而compileSdk还是 32AGP 会直接报错或给出强烈警告因为编译器手上没有 33 的android.jar它无法校验你声明的东西是否成立。2.2 升到 33 之前必须先对齐的工具链矩阵android-33这个 platform 包本身只是个“素材”真正干活的是 AGPAndroid Gradle Plugin、Gradle、JDK 和 Build-Tools。这四个里任何一个版本不够表现都不是“报个清楚的错”而是一堆看起来无关的次级错误。我一般会按下面这张表先对一遍表里的组合是我实际跑通过的具体可选版本仍要以后续官方发布说明为准。组件支持 compileSdk 33 的常见区间关键约束AGP7.4 及以上7.4 之前会在编译期提示compileSdk 33未测试8.x 起要求namespaceGradle7.5 及以上配 AGP 7.4AGP 8.x 需要 Gradle 8.0 以上JDK11 起步AGP 8.x 要求 17JDK 版本不够时报的是Unsupported class file major versionBuild-Tools33.0.x与 compileSdk 大版本对齐最省事Android SDK Platformplatforms;android-33就是标题里那个包必须有这里有个容易被忽略的点AGP 版本决定了它“认识”的最高 compileSdk。AGP 7.3 配compileSdk 33不是编译不过而是会在每次构建开头刷一条警告接着某些 lint 检查会给出误报因为它的 API 数据库里根本没有 33 的条目。踩过这个坑的人应该记得那种感觉代码明明没问题lint 却指着一个 API 说“这个方法不存在”。所以升级顺序应该是先升 JDK11 或 17再升 Gradle wrapper再升 AGP最后改compileSdk。反着做会反复回滚血泪经验。2.3 targetSdk 33 到底改了什么运行时行为compileSdk抬到 33 不会让你崩溃targetSdk抬到 33 会。Android 13 有一批行为变更只在targetSdk 33时才生效这是升级的主要工作量也是最多人翻车的地方。通知权限。POST_NOTIFICATIONS在 API 33 之前是默认授予的普通权限从 33 开始变成需要运行时申请的权限。你的应用如果完全没写申请逻辑在 Android 13 机器上装完就是一条通知都发不出来——而且不崩、不报错、不抛异常只是安静地什么都不显示。这种“黑匣子”式的失效最难查。媒体权限拆分。以前读相册用READ_EXTERNAL_STORAGE一把梭33 起拆成了READ_MEDIA_IMAGES、READ_MEDIA_VIDEO、READ_MEDIA_AUDIO。你在 Manifest 里继续声明旧的READ_EXTERNAL_STORAGE在 33 设备上授予了也拿不到相册数据ContentResolver查出来是空游标。PendingIntent 可变性。Android 12 起就要求显式指定FLAG_IMMUTABLE或FLAG_MUTABLE33 上某些场景下不带 flag 会直接抛IllegalArgumentException。很多老代码在通知、桌面小组件、闹钟里传 PendingIntent一升版本就集中爆。附近设备权限。NEARBY_WIFI_DEVICES从 33 开始独立出来之前借用位置权限做 Wi-Fi 相关操作的做法在新版本上语义对不上。还有一条是 Android 12 留下的遗留问题但 33 上依然要处理所有带intent-filter的组件必须显式写android:exported。这个不是 33 的新规但每次升 targetSdk 都会有人被同一个崩溃拦一次因为编译器只给警告装到 Android 12 以上设备上启动即ActivityNotFoundException。把这几条列出来不是让你背而是让你明白targetSdk从 30 抬到 33等于一次性跨了三个大版本的行为变更。想少踩坑就一个一个版本抬抬完在对应系统镜像上过一遍主流程别一次跨三级。3. 把 android-33 装进本地和 CIsdkmanager 命令行与目录结构3.1 用 sdkmanager 精确安装 platform 33 与配套 build-toolsIDE 里的 SDK Manager 图形界面点起来很直观但 CI 容器里没有界面而且图形界面不告诉你它到底装了哪些包。所以真正需要掌握的是sdkmanager它在cmdline-tools目录下。# 1) 设定 SDK 根目录后续所有路径都基于它 export ANDROID_HOME$HOME/Android/Sdk export PATH$ANDROID_HOME/cmdline-tools/latest/bin:$PATH # 2) 先看远端到底有哪些 33 相关的包别凭记忆写版本号 sdkmanager --list | grep -E platforms;android-33|build-tools;33 # 3) 精确安装。包名里带分号必须整体加引号否则 shell 会把分号当命令分隔符 sdkmanager platforms;android-33 build-tools;33.0.2 platform-tools这三步的逻辑是先定根目录再列清单最后安装。第二步不要省——不同时间点能装到的 Build-Tools 小版本是不一样的硬编码一个不存在的版本号会得到Warning: Failed to find package build-tools;33.0.9然后它继续往下跑最后报一个毫不相关的编译错误。第三步的引号是硬性要求。platforms;android-33里的分号在 bash 里是语句分隔符不加引号会被拆成sdkmanager platforms和android-33两条命令第一条报缺少目标第二条报 command not found。这几乎是每个人第一次用 sdkmanager 都会遇到的问题。装完之后验证一下# 目录里应该有 android.jar、source.properties、data 等 ls $ANDROID_HOME/platforms/android-33 # 确认本地可见的 platform 版本 sdkmanager --list_installed | grep platformsandroid.jar在不在是这个包装没装好的唯一判据。source.properties里记录了AndroidVersion.ApiLevel33AGP 就是读这个文件来确认“我要的 android-33 存在”。3.2 目录结构与 licensesCI 上总是卡在授权$ANDROID_HOME下有四个目录值得记住platforms/放着 android-33 这类平台包、build-tools/放 aapt2、d8、zipalign 这些构建工具、platform-tools/adb、fastboot、licenses/每家 SDK 组件的授权哈希文件。licenses/是 CI 上最常见的卡点。本地开发时你在图形界面点过“Accept”授权文件已经写进~/.android或$ANDROID_HOME/licenses容器每次都是干净的没有这个文件sdkmanager就会停在交互式提示上等一个永远不会来的y最后超时失败日志里只有一句干巴巴的Warning: Failed to install。# 非交互接受全部授权必须在安装 platform 包之前执行 yes | sdkmanager --licenses # 确认授权文件落盘CI 里可以把这个目录做成缓存 ls $ANDROID_HOME/licenses执行顺序很关键先--licenses再--install。反过来也能成只是中间会卡一次交互。CI 脚本里我一般把这个顺序写死并且把licenses目录做进缓存层能省掉每次构建十几秒的网络往返。3.3 Gradle 自动下载 SDK 的开关与它的边界Gradle 其实能自己补装缺失的 platform 包开关是gradle.properties里的android.builder.sdkDownload默认是开的。它的工作方式是构建时发现android-33缺失且授权文件已存在就自己去下载。这个机制能救急但别依赖它。三个边界要清楚第一它只在授权文件已存在时生效容器里没有licenses/它一样卡住第二它只管 platform 和 build-tools不补cmdline-tools本身所以你连sdkmanager都没有的镜像里它无能为力第三内网或受限网络环境下它的下载地址经常不可达报错信息埋在 Gradle 的--info日志里不开详细日志根本看不见。我一般的做法是本地开发靠自动下载图省事CI 里显式sdkmanager --install把版本号写死。理由是 CI 需要可重复性自动下载会拿到“当时最新”的 Build-Tools某天上游发了新版本你的构建产物就悄悄变了。4. 从 compileSdk 30 升到 33 的最小改动清单4.1 build.gradle 里要改的字段与 namespace 迁移真正要动的字段就五个但每个都有连带影响。下面这段是我常用的最小改动模板Kotlin DSLGroovy 写法把去掉即可// app/build.gradle.kts android { // AGP 8.x 起必填替代 AndroidManifest 里的 package 属性 namespace com.example.app compileSdk 33 // 决定 android.jar 版本与 lint 规则 defaultConfig { applicationId com.example.app minSdk 21 // 安装门槛不跟着涨就保留 targetSdk 33 // 行为开关必须 compileSdk versionCode 1 versionName 1.0.0 } compileOptions { // 与所用 JDK 对齐AGP 8.x 建议 17 sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } kotlinOptions { jvmTarget 11 // 必须和上面两个保持一致否则报 jar 版本冲突 } }参数逐个说namespace是从 AGP 8 开始强制要求的如果你还用 AGP 7.4可以先不写但 Manifest 里的package属性要留着compileSdk直接决定你能不能引用 33 的 APItargetSdk是这次升级的实质内容sourceCompatibility和jvmTarget必须相同不一致时会出现那种“编译过了但运行时NoSuchMethodError”的问题——因为主代码和依赖的字节码版本对不上。如果项目里还有buildFeatures { buildConfig true }这类开关AGP 8 之后BuildConfig默认关闭需要显式打开否则原本用BuildConfig.DEBUG的地方会编译失败。4.2 AndroidManifest 必须补的 exported 与权限声明Manifest 这块升级到 33 最容易漏的是三处exported、通知权限、媒体权限。manifest xmlns:androidhttp://schemas.android.com/apk/res/android !-- Android 13 起通知是运行时权限先声明再申请 -- uses-permission android:nameandroid.permission.POST_NOTIFICATIONS / !-- 33 起媒体权限按类型拆分读图用这个不要再用 READ_EXTERNAL_STORAGE -- uses-permission android:nameandroid.permission.READ_MEDIA_IMAGES / application android:labelDemo !-- 带 intent-filter 的组件必须显式声明 exported否则 Android 12 启动即崩 -- activity android:name.MainActivity android:exportedtrue intent-filter action android:nameandroid.intent.action.MAIN / category android:nameandroid.intent.category.LAUNCHER / /intent-filter /activity !-- 只给本应用用的组件显式关掉比默认值更安全 -- service android:name.SyncService android:exportedfalse / /application /manifestexported的取值逻辑要想清楚带LAUNCHER的入口 Activity 必须true否则桌面点不开只被自己startActivity的页面必须是false写true等于把内部页面暴露给任意应用拉起。升级时我一般把所有组件过一遍按“有没有外部调用方”决定而不是一律true图省事。READ_EXTERNAL_STORAGE在 33 上不要直接删掉如果minSdk还很低低版本设备仍需要它。正确做法是同时声明新旧两套权限运行时按Build.VERSION.SDK_INT分支申请。这一点后面 4.3 的代码里体现。4.3 Android 13 通知权限与媒体权限的运行时申请声明只是第一步真正生效靠运行时申请。下面这段是我现在项目里在用的写法用registerForActivityResult而不是老的onRequestPermissionsResult避免回调分发错乱。// 通知权限的申请器必须在 Activity/Fragment 创建阶段注册 private val notificationPermLauncher registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted - if (!granted) { // 用户拒绝了不要立刻再弹走应用内引导页说明为什么要通知 showNotificationRationale() } } private fun ensureNotificationPermission() { // 33 以下没有这个权限直接返回避免无意义的申请弹窗 if (Build.VERSION.SDK_INT Build.VERSION_CODES.TIRAMISU) return val granted ContextCompat.checkSelfPermission( this, Manifest.permission.POST_NOTIFICATIONS ) PackageManager.PERMISSION_GRANTED if (!granted) { notificationPermLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) } } // 媒体权限按版本分支33 起用 READ_MEDIA_IMAGES以下用 READ_EXTERNAL_STORAGE private fun mediaPermission(): String if (Build.VERSION.SDK_INT Build.VERSION_CODES.TIRAMISU) { Manifest.permission.READ_MEDIA_IMAGES } else { Manifest.permission.READ_EXTERNAL_STORAGE }逻辑上有两个点值得强调。第一Build.VERSION.SDK_INT TIRAMISU的提前返回不能省。33 以下的系统上申请一个不存在的权限checkSelfPermission会一直返回DENIED用户还看不到任何弹窗于是你的代码永远走“未授权”分支。第二mediaPermission()这种按版本返回权限名的写法比在业务代码里到处写if干净得多调用方只管拿名字去申请。PendingIntent 那处改动单独拎出来因为它不属于权限但同批爆// Android 12 强制要求显式可变性33 上部分场景缺失会直接抛异常 val pendingIntent PendingIntent.getActivity( context, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT )FLAG_IMMUTABLE表示这个 PendingIntent 交给别的进程后内容不可改绝大多数通知、闹钟场景都该用它。只有当你的确需要接收方填充 extras 时才用FLAG_MUTABLE并且要配合setPackage限制接收方否则等于开了个口子。改完这三处compileSdk和targetSdk都到 33 的基本盘就有了。剩下的就是排查。5. 升级 android-33 的排查清单五类翻车现场与定位手法5.1 报 Failed to find target with hash string android-33现象本地构建正常CI 上./gradlew assembleDebug直接失败日志末尾就是这一句前面还夹着几条无关的警告。原因platforms/android-33目录不存在且 Gradle 的自动下载没能补上。最常见的是容器里没有licenses/目录自动下载被授权挡住其次是 SDK 根目录没通过ANDROID_HOME或local.properties里的sdk.dir正确指向。解决先sdkmanager --list_installed | grep platforms确认本地有没有没有就yes | sdkmanager --licenses后sdkmanager platforms;android-33显式安装。CI 里把这两条写进镜像构建阶段不要放到每次构建的脚本里跑。5.2 升级后一启动就崩日志是 ActivityNotFoundException现象编译完全通过装到 Android 12 以上的机器上点图标直接闪退堆栈指向 launcher 的Intent解析失败。原因MainActivity带了intent-filter却没写android:exportedAndroid 12 起系统直接拒绝启动这类组件。AGP 在合并 Manifest 时只给一条警告很容易被忽略。解决./gradlew :app:processDebugMainManifest之后去看app/build/intermediates/merged_manifest/下的合并结果搜exported逐个组件补齐。用apkanalyzer manifest print app-debug.apk也能直接看最终产物里的属性。5.3 通知一条都不发不崩不报错现象代码里NotificationManagerCompat.notify()正常调用日志没有任何异常但通知栏里什么都没有。在 Android 12 以下的机器上同一份代码完全正常。原因POST_NOTIFICATIONS在 33 上是运行时权限没申请就是没授权notify()会被静默丢弃。NotificationManagerCompat在某些版本上会把未授权的通知当成普通失败吞掉。解决在发通知前统一做一次areNotificationsEnabled()检查未授权就先走 4.3 的申请流程。排查阶段可以在notify()前后各打一行日志确认是真的执行到了而不是被前置条件提前 return 掉。5.4 编译过了但 lint 报一堆“API 不存在”现象compileSdk已经改成 33构建成功但./gradlew lint报出一批莫名其妙的错误指向的都是早就存在的 API。原因AGP 版本太低它内置的 API 数据库不认识 33匹配不上就按“方法不存在”处理。也可能是 Gradle 缓存里还留着旧的 platform 索引。解决先./gradlew --version确认 AGP 和 Gradle 版本落在 2.2 那张表的区间里再./gradlew clean加删掉~/.gradle/caches/下的 transform 缓存重跑。两条都做完还报就是 AGP 真的需要升级了。5.5 相册读取返回空游标现象权限弹窗点了允许ContentResolver.query()不抛异常但cursor.count是 0。老设备上同样的代码能读到图。原因33 上READ_EXTERNAL_STORAGE对媒体集合已经失效必须用READ_MEDIA_IMAGES或者 Manifest 里声明了新权限但运行时代码还在申请旧的那个。解决按 4.3 里mediaPermission()的分支写法统一权限来源同时检查 Manifest 是否两套权限都在。定位时可以直接adb shell dumpsys package your.package | grep READ_MEDIA看系统实际记录到的授权状态。这五条覆盖了我遇到过的绝大多数升级失败场景。共同点是报错信息和真实原因往往隔着一层只看最后一行日志基本查不出来得知道该往哪个中间产物目录看。6. 把 android-33 固定住产物验证与 CI 版本锁定改完之后有个收尾动作很容易被跳过验证最终产物里的版本号到底是多少。构建脚本里写的是 33不代表打进 APK 的就是 33——尤其是多模块项目某个 library 模块的targetSdk没跟着改最后合并出来的清单可能还是旧值。# 直接读 APK 里的 manifest确认落地的版本号 aapt2 dump badging app/build/outputs/apk/debug/app-debug.apk \ | grep -E sdkVersion|targetSdkVersion # 期望输出形如 # sdkVersion:21 # targetSdkVersion:33aapt2在build-tools/33.x.x/目录下如果命令找不到就补全 PATH。另一种等价做法是apkanalyzer manifest print app-debug.apk输出更易读适合贴进构建日志里做归档。我一般在 CI 的构建后步骤加一条断言targetSdkVersion不等于 33 就让流水线失败比事后人工核对靠谱。CI 环境的固定有两层。第一层是 SDK 包版本固定别用“装最新”的写法# CI 镜像构建阶段版本号写死避免上游发新版导致产物漂移 sdkmanager --install \ platforms;android-33 \ build-tools;33.0.2 \ platform-tools # 一次性接受授权并把 licenses 目录纳入镜像层缓存 yes | sdkmanager --licenses第二层是工具链版本固定。Gradle 用 wrapper 锁住gradle/wrapper/gradle-wrapper.properties里的distributionUrlJDK 用镜像里预装的那个并显式设JAVA_HOMEAGP 版本写在根build.gradle的plugins块里而不是用动态版本。这三处任何一处用了动态版本你都会在某天早上收到一条“昨天还好好的今天构建挂了”的消息。还有一个我踩过的细节本地和 CI 的compileSdk一致不代表build-tools一致。AGP 在没显式指定buildToolsVersion时会自己挑一个它认为合适的本地 SDK 装了好几个版本CI 只装了一个挑出来的结果就可能不同。解决办法是在android {}块里显式写buildToolsVersion 33.0.2让两边用同一把工具。最后说个习惯。我现在升任何compileSdk都会先在一个只依赖最少库的空白模块上把链路跑通确认aapt2 dump badging输出对得上再往主工程搬。因为主工程里第三方库的minSdk冲突、namespace缺失、BuildConfig开关这些干扰项太多混在一起排查等于同时解五道方程。这套流程不新鲜但每次跳过它都会付出代价。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑