简介:Android SDK 平台包 android-33 对应 Android 13(即 API 33),面向正在做应用开发、需要为工程补齐指定编译平台的环境搭建者与进阶学习者,旨在解决本地 SDK 缺少该版本、Android Studio 中无法选择目标 API 的问题。压缩包共约 2000 个文件,以 1974 个 xml 资源与配置描述文件为主体,辅以 22 个 html 说明页和 4 个 txt 文本,整体约 60.84MB,解压后放入 SDK 的 platforms 目录即可参与编译。包内保留了该版本的平台描述、系统库与 API 定义等分层结构,可对照理解 Android 33 的接口变化与平台组成,验证工程在不同 API 级别下的兼容表现,也便于离线部署与多版本 SDK 并存维护。目前已有 855 人浏览学习,适合作为版本管理中的一份平台备份。
1. android-33 并不只是一个版本号:compileSdk、targetSdk、minSdk 的分工
把一个能正常出包的工程从 compileSdk 32 提升到 33,多数人以为改个数字就能完事,结果第一次在 CI 上构建就卡在Failed to find target with hash string 'android-33',而本地 Android Studio 里明明跑得通。android-33是 Android SDK 里平台包的目录名,也是 Gradle 认的 target hash;它同时牵扯三件事:SDK 目录里有没有真正装上这个 platform、build.gradle 里 compileSdk 和 targetSdk 分别写多少、以及 AGP、Gradle、JDK 这条工具链认不认这个数字。
这篇按我改一个中型 App 的真实顺序写:先把 android-33 装对并用命令行验证,再动 Gradle,再处理 targetSdk 33 带来的运行时权限变更,最后给出我踩过的坑和一套自检命令。适合正准备升 compileSdk 33、被 SDK 路径和 AGP 版本绕晕的 Android 开发者。
2. 把 android-33 装进本地 SDK:sdkmanager 命令行安装与目录核对
在本地 IDE 里,升级往往点几下弹窗就完成了,所以很多人从来没有看过platforms/android-33目录长什么样。一旦到了 CI 容器、内网构建机或者同事的新电脑上,问题就集中爆发。我一般不在 IDE 里装,而是直接用sdkmanager,因为它在任何一台机器上都能复现,出问题时也容易排查。
2.1 platforms/android-33 目录里到底该有什么
先明确“装好了”的标准。一个完整的 API 33 平台包,目录结构大致固定,缺哪一个都会在编译期以不同形式报错。
| 路径 | 作用 | 缺失时的典型表现 |
|---|---|---|
platforms/android-33/android.jar | 编译期 API 33 的类与资源符号 | 用到 33 新增类时报cannot find symbol |
platforms/android-33/package.xml | 包元数据,含版本、许可、依赖 | sdkmanager 认为未安装,反复重下 |
platforms/android-33/build.prop | 平台属性,ro.build.version.sdk=33 | AGP 读不到 API level,回退到旧平台 |
platforms/android-33/data/res | 系统资源符号表 | aapt2 链接阶段报资源找不到 |
platforms/android-33/templates | 新建工程的模板 | 不影响编译,只影响 IDE 向导 |
sources/android-33 | 平台源码,供 IDE 跳转 | 只能看到反编译的 stub 或空实现 |
build-tools/33.0.x | aapt2、d8、zipalign 等构建工具 | 报找不到 aapt2 / d8 可执行文件 |
注意,sources和platforms是两个独立的包,装完 platform 并不代表源码也到位。很多人抱怨“点进Activity只能看到一行方法签名”,原因就是sources;android-33没装,跟编译本身无关,但会让排查 API 33 新行为时很痛苦。
2.2 用 sdkmanager 装 platform 33、build-tools 与源码包
假设 SDK 根目录是/opt/android-sdk,cmdline-tools 已经解压到<sdk>/cmdline-tools/latest。下面这一组命令是我在每台新构建机上都会跑的。
# 1. 固定 SDK 根目录,后续所有命令都以它为准 export ANDROID_HOME=/opt/android-sdk export ANDROID_SDK_ROOT=$ANDROID_HOME export PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$PATH # 2. 先看仓库里到底有没有这个包,避免拼错包名 sdkmanager --list | grep -E "platforms;android-33|build-tools;33" # 3. 装平台包、构建工具、平台工具和源码(--sdk_root 显式指定,防止装到别处) sdkmanager --sdk_root=$ANDROID_HOME \ "platforms;android-33" \ "build-tools;33.0.2" \ "platform-tools" \ "sources;android-33" # 4. 首次装完必须接受许可,否则 AGP 构建会直接失败 yes | sdkmanager --sdk_root=$ANDROID_HOME --licenses命令本身不难,难在几个参数的含义。--sdk_root一定要显式带上,尤其机器上装过多个 SDK(IDE 自带一份、命令行一份、Docker 镜像里又一份)时,不带这个参数,sdkmanager会按它自己推断的根目录安装,结果就是“装了但 Gradle 找不到”。包名必须是platforms;android-33这种带分号的完整形式,直接写android-33会提示找不到包。build-tools 我一般选33.0.2而不是33.0.0,原因第 5 章会讲。
--list的输出里,platforms;android-33后面会标Installed或空。装完后重新执行一次,确认状态变化,再去看目录。
2.3 装完先核对:三条命令确认 android-33 真的可用
装完不算完,我习惯立刻验证三件事:目录存在、android.jar可读、Gradle 能识别。
# 1. 平台目录与关键文件是否齐全 ls -l $ANDROID_HOME/platforms/android-33/android.jar \ $ANDROID_HOME/platforms/android-33/build.prop \ $ANDROID_HOME/build-tools/33.0.2/aapt2 # 2. 从 build.prop 里读 API level,确认真的是 33 而不是残留的旧包 grep -i "ro.build.version.sdk" $ANDROID_HOME/platforms/android-33/build.prop # 3. 让 Gradle 自己说话:能打印出 33 就说明定位没问题 ./gradlew -q :app:properties | grep -i "compileSdk"第二、三步的意义在于排除一种很隐蔽的情况:目录名叫android-33,但里面的build.prop是从旧包拷过来的,或者package.xml版本字段不对。这种包在某些机器上能编译通过,用到 33 新 API 时才报错,排查成本极高。
如果工程里有local.properties,还要确认sdk.dir和ANDROID_HOME指向同一个 SDK。local.properties的优先级高于环境变量,这是“命令行装好了、IDE 还是报错”最常见的原因。local.properties属于本地文件,不要提交到版本库;CI 上应该由脚本在构建前生成,而不是靠人工放置。
3. Gradle 侧的三行改动:compileSdk 33 与 AGP / Gradle / JDK 的版本矩阵
SDK 装对只解决了“有没有”,接下来要解决“认不认”。android-33 对应的 API level 是 33,而 AGP 对 compileSdk 的支持是有下限的,版本太低的 AGP 会给出警告甚至直接失败。这一章把三处改动按依赖顺序排好,避免改了 build.gradle 才发现 Gradle 版本不够。
3.1 compileSdk 33 对 AGP、Gradle、JDK 的版本要求
下表是我实际用过的组合,也是目前比较省心的搭配。AGP 版本越高对 JDK 要求越严,这是升级时最容易忽略的一环。
| AGP 版本 | 最低 Gradle 版本 | 需要的 JDK | 对 compileSdk 33 的表现 |
|---|---|---|---|
| 7.0.x | 7.0.2 | 11 | 可用,但会给出建议升级 AGP 的警告 |
| 7.2.x | 7.3.3 | 11 | 可用,个别 lint 规则对 33 支持不全 |
| 7.3.x | 7.4 | 11 | 基本无警告,是我用得最多的一档 |
| 7.4.x | 7.5 | 11 | 完整支持,配合 JDK 11 即可 |
| 8.0.x 及以上 | 8.0 | 17 | 完整支持,但必须换 JDK 17 |
判断自己的工程在哪个档位,跑一条命令就够:
./gradlew -version输出里Gradle、JVM两行是关键。Gradle 版本低于表里的下限,就先改gradle/wrapper/gradle-wrapper.properties;JVM 版本不对,就先处理 JDK,再去动 AGP。顺序反了会连续遇到两轮互不相关的报错,很容易误判。
3.2 build.gradle 里真正要改的几行
Groovy DSL 和 Kotlin DSL 都有人用,下面给 Kotlin DSL 的写法,Groovy 只是把=去掉。
android { // 编译期看到的 API,决定能用哪些新类与新方法 compileSdk = 33 defaultConfig { applicationId = "com.example.demo" minSdk = 23 // 最低运行版本,升 compileSdk 不需要动它 targetSdk = 33 // 决定系统的兼容行为开关,详见第 4 章 versionCode = 1 versionName = "1.0" } compileOptions { // AGP 7.x 起建议显式声明,避免默认值随版本漂移 sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } }四个参数要分清楚。compileSdk只影响编译期,可以比targetSdk高,也可以在设备不支持的情况下照常编译;targetSdk决定系统是否对你开启新的兼容行为,是真正的行为开关;minSdk决定装机范围,升 compileSdk 完全不需要动它,很多人顺手把它一起抬上去,白白丢掉一批用户。buildToolsVersion一般不写,AGP 会自动选一个匹配版本,显式写死反而在换机器时容易冲突。
如果工程用的是compileSdkVersion 32这种旧写法,改成compileSdk = 33即可,两者等价,但新写法在 AGP 8 上更保险。
3.3 依赖库跟着抬:androidx 版本与 compileSdk 的联动
改完 compileSdk,下一个报错往往不是来自代码,而是来自依赖。高版本 androidx 库会在元数据里声明“使用我的工程必须 compileSdk ≥ 33”,于是./gradlew assembleDebug直接失败,报出类似Dependency 'androidx.core:core-ktx:x.y.z' requires libraries and applications that depend on it to compile against version 33 or later的错误。
处理思路有两种,按项目情况选。新项目直接抬依赖版本,配合 compileSdk 33;老项目如果依赖树很复杂,就先只改 compileSdk 让编译通过,再逐个升依赖,避免一次引入太多变量。
dependencies { // 这几个是升到 compileSdk 33 后最常一起动的,版本按自己工程的实际情况取 implementation("androidx.core:core-ktx:1.9.0") implementation("androidx.appcompat:appcompat:1.6.1") implementation("com.google.android.material:material:1.8.0") }依赖版本不要凭感觉写。我更推荐的做法是先跑一次依赖树,看清楚到底是哪个库把 compileSdk 顶到了 33,再决定是升级还是降级:
# 找出要求 compileSdk 33 的元凶 ./gradlew :app:dependencies --configuration debugRuntimeClasspath > deps.txt拿到deps.txt后按库名逐个比对,通常只有一两个传递依赖在“提要求”。把它们单独用resolutionStrategy固定住,比全量升级稳妥得多。
4. targetSdk 33 的行为变更清单:权限、通知与前台服务要改哪些代码
compileSdk 只影响编译,targetSdk = 33才是真正会让线上行为变化的那一行。API 33 的变更里,对普通 App 影响最大的是通知权限和细分媒体权限,其次是邻近设备权限。这一章按“改配置、改代码、改降级逻辑”的顺序给可抄的片段。
4.1 POST_NOTIFICATIONS:targetSdk 33 后通知不再自动弹
targetSdk 低于 33 时,App 第一次创建通知渠道,系统会代为弹一次授权框;把 targetSdk 提到 33 之后,这个“系统代劳”没有了,必须自己发运行时权限请求,否则通知静默失败,用户什么都看不到。
先声明权限:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <!-- targetSdk 33 起,发通知必须持有该运行时权限 --> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> </manifest>再在合适的时机请求。我一般在“用户第一次进入需要推送的页面”或者“首次创建通知渠道”时请求,而不是一启动就弹。
// 用 ActivityResultContracts 注册,避免手写 onRequestPermissionsResult private val notifyPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted -> if (granted) { // 授权成功,可以正常发通知 NotificationHelper.ensureChannel(this) } else { // 被拒:不要反复弹,改为应用内提示 + 引导去设置页 showInAppNotificationHint() } } private fun requestNotifyPermissionIfNeeded() { // 只有 Android 13(API 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) { notifyPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) } }这段代码有两个容易忽略的点。第一,Build.VERSION_CODES.TIRAMISU就是 33,用常量比写死数字更可读;第二,被拒之后不要每次进页面都弹,系统在用户选择“拒绝两次”后不会再显示弹窗,你的launch调用会静默返回,表现为“代码执行了但没反应”,很容易被误判成框架问题。
4.2 READ_MEDIA_IMAGES / VIDEO / AUDIO:按类型申请,别再要 READ_EXTERNAL_STORAGE
API 33 把“读外部存储”拆成了三个细分权限。targetSdk 33 之后,继续申请READ_EXTERNAL_STORAGE在读媒体场景下不会生效,必须按类型申请。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <!-- Android 13 起按媒体类型授权,用户可只给图片不给视频 --> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> <!-- 兼容 32 及以下,加 maxSdkVersion 避免新系统上申请无效权限 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> </manifest>代码里按版本分叉,别把两套权限名字混在一个数组里:
private fun mediaPermissions(): Array<String> { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // 只选当前业务真正需要的类型,选图就只要 IMAGES arrayOf(Manifest.permission.READ_MEDIA_IMAGES) } else { arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE) } }只选需要的类型很关键。同一个页面既要选图又要选视频,就申请两个;只选图却把 VIDEO 也申请上,用户会看到一个多余的系统弹窗,拒绝率明显上升。另外,如果你只是让用户选一张图回传,根本不需要这些权限,用系统相册选择器(Photo Picker 那类方案)就能拿到结果,权限一个都不用申请,这是我最推荐的路径。
4.3 NEARBY_WIFI_DEVICES 与前台服务通知的可见性边界
如果 App 里有连接设备、扫描周边网络这类功能,targetSdk 33 之后要改用NEARBY_WIFI_DEVICES。如果你的业务不依赖 Wi-Fi 信息推断地理位置,可以加neverForLocation标志,避免被要求同时申请定位权限。
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" android:usesPermissionFlags="neverForLocation" tools:targetApi="33" />另一个容易误判的点是前台服务。把 targetSdk 提到 33 后,如果用户拒绝了通知权限,前台服务的常驻通知不会显示,但服务本身仍在运行。所以千万别用“通知栏有没有那条通知”来判断服务是否还活着,这类玄学判断在测试机上一切正常,在用户机上就会导致重复启动服务。正确做法是查ActivityManager里的服务状态,或者干脆在代码里维护自己的状态机。
5. 升 android-33 的排查清单:五类报错的现象、原因与解法
下面这五条是我在不同工程里真实遇到过的,顺序按出现频率排。每条都按“看到什么、为什么、怎么改”写,方便直接对号入座。
第一类:Failed to find target with hash string 'android-33'。现象是本地能编译、CI 直接失败,或者反过来。原因通常是 SDK 定位不一致:IDE 用的是自带的 SDK,命令行用的是ANDROID_HOME,两份里只有一份装了 platform 33。解决方式是先跑./gradlew -q :app:properties | grep -i sdk看 Gradle 实际用的是哪个目录,再确认该目录下platforms/android-33/android.jar存在,缺了就用第 2 章的命令补装。
第二类:Installed Build Tools revision 33.0.0 is corrupted. Remove and install again。现象是清理后重装依然报同样的错。原因是 33.0.0 这个版本的包在部分分发渠道里内容不完整,aapt2或d8缺失。解决方式是删掉build-tools/33.0.0整个目录,改装33.0.2,或者在模块里显式写buildToolsVersion = "33.0.2"。这个坑很典型,报错文本让你“重装”,但重装同一个版本号并不会好。
第三类:requires libraries and applications that depend on it to compile against version 33 or later。现象是改完 compileSdk 后依赖解析失败。原因是某个 androidx 库的元数据把下限抬到了 33,而你还在用 32。解决方式是二选一:把 compileSdk 提到 33,或者把那个库降回兼容版本。判断哪个库在提要求,用第 3 章的依赖树命令,不要凭猜测一个个试。
第四类:通知完全没反应,日志里也没有异常。现象是NotificationManagerCompat.notify调用返回正常,用户却收不到。原因有两个,一是 targetSdk 33 后没有请求POST_NOTIFICATIONS,二是请求了但用户拒过一次,代码里没有处理拒绝分支。解决方式是按 4.1 的写法补上请求,并且在PermissionChecker里区分“未请求”“已拒绝”“永久拒绝”三种状态,分别给不同引导。
第五类:Unsupported class file major version或Unsupported Java。现象是升级 AGP 后编译直接崩在插件加载阶段。原因是 AGP 8.x 要求 JDK 17,而机器上JAVA_HOME还指向 JDK 11,或者 IDE 的 Gradle JDK 设置与命令行不一致。解决方式是在gradle.properties里固定:
# 显式指定构建用的 JDK,避免 IDE 与命令行各用一套 org.gradle.java.home=/usr/lib/jvm/jdk-17改完务必在命令行和 IDE 各跑一次./gradlew -version,两边输出一致才算真的对齐。
6. 验证 android-33 是否真的生效:三条命令加一张自检表
改完不算完,得证明改对了。我习惯用三条命令覆盖“构建产物、清单文件、运行时”三个层面,任何一层对不上,说明改动没有真正落地。
# 1. 看 APK 里最终写入的 sdk 版本,这是最权威的一层 $ANDROID_HOME/build-tools/33.0.2/aapt2 dump badging \ app/build/outputs/apk/debug/app-debug.apk | grep -E "sdkVersion|targetSdkVersion" # 2. 看合并后的清单,排查依赖库把 targetSdk 又改回去的情况 ./gradlew :app:processDebugMainManifest grep -E "targetSdkVersion|compileSdkVersion" \ app/build/intermediates/merged_manifests/debug/AndroidManifest.xml # 3. 装到 API 33 的设备上,看系统实际认到的值 adb shell dumpsys package com.example.demo | grep -i "targetSdk"第一条输出里应该能看到sdkVersion:'23'和targetSdkVersion:'33',如果targetSdkVersion还是 32,说明改的不是真正生效的那个 module,多模块工程里这种情况很常见。第三条最容易被忽略但价值最高,它验证的是设备端真实行为,能排除“构建产物对了但装的是旧包”这种低级问题。
| 自检项 | 期望值 | 不达标时的排查方向 |
|---|---|---|
android.jar存在 | 文件可读 | SDK 安装路径 |
Gradle版本 | 不低于 AGP 要求 | wrapper.properties |
JVM版本 | 与 AGP 档位匹配 | org.gradle.java.home |
| APK 的 targetSdkVersion | 33 | 模块级 build.gradle |
设备dumpsys的 targetSdk | 33 | 是否装了旧包 |
| 通知权限弹窗 | 首次触发时出现 | 请求时机与拒绝分支 |
我踩过最亏的一次,是把targetSdk改在了根工程的allprojects里,结果子模块的defaultConfig把它覆盖回去了,构建日志一切正常,APK 里的值却还是 32,测试同学反馈“通知还是收不到”,排查了大半天。从那以后我养成一个习惯:任何 SDK 版本相关的改动,改完立刻用aapt2 dump badging打一次,几十秒的事,比事后翻构建日志划算得多。
希望帮到你。
本文还有配套的精品资源,点击获取