☰
android-33 升级实战:SDK、Gradle 与权限适配
2026/10/27 13:06:12 网站建设 项目流程

简介: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=33AGP 读不到 API level,回退到旧平台
platforms/android-33/data/res系统资源符号表aapt2 链接阶段报资源找不到
platforms/android-33/templates新建工程的模板不影响编译,只影响 IDE 向导
sources/android-33平台源码,供 IDE 跳转只能看到反编译的 stub 或空实现
build-tools/33.0.xaapt2、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.x7.0.211可用,但会给出建议升级 AGP 的警告
7.2.x7.3.311可用,个别 lint 规则对 33 支持不全
7.3.x7.411基本无警告,是我用得最多的一档
7.4.x7.511完整支持,配合 JDK 11 即可
8.0.x 及以上8.017完整支持,但必须换 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 的 targetSdkVersion33模块级 build.gradle
设备dumpsys的 targetSdk33是否装了旧包
通知权限弹窗首次触发时出现请求时机与拒绝分支

我踩过最亏的一次,是把targetSdk改在了根工程的allprojects里,结果子模块的defaultConfig把它覆盖回去了,构建日志一切正常,APK 里的值却还是 32,测试同学反馈“通知还是收不到”,排查了大半天。从那以后我养成一个习惯:任何 SDK 版本相关的改动,改完立刻用aapt2 dump badging打一次,几十秒的事,比事后翻构建日志划算得多。

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询