☰
android-33 平台包:安装、编译配置与运行期适配
2026/10/27 21:02:32 网站建设 项目流程

简介:这份 android-33 平台组件包面向 Android 应用开发者,对应 Android 13(API 33),用于补齐 Android Studio 等 IDE 中缺失的 platforms 平台目录,解决编译时找不到目标 SDK、无法按 API 33 特性构建与调试的问题,适合已有一定基础、需要适配新版本系统的开发者。压缩包共 2000 个文件,以 1974 个 XML 资源与配置描述文件为主体,辅以 22 个 HTML 说明页面和 4 个 TXT 文本,整体约 60.84MB;解压后放入 SDK 的 platforms 目录即可被识别。其中 XML 承担平台元数据、权限与控件属性定义,HTML 提供接口与组件说明,TXT 多为版本与校验信息。已有 855 人学习下载,可作为离线部署、内网环境或 SDK Manager 下载受限时的备选方案,帮助读者省去逐个组件拉取的时间,直接获得与 API 33 接口对齐的编译支持,降低版本不匹配引发的构建报错与兼容性排查成本。

1. android-33 不是装个包:它决定你能编译什么、跑不到什么

克隆一个停了很久的仓库,界面还没打开,同步阶段第一行就红了:Failed to find target with hash string 'android-33'。很多人的第一反应是把compileSdk降回 32 敷衍过去,结果第二天又遇到POST_NOTIFICATIONS不认、READ_MEDIA_IMAGES未定义。android sdk android-33 说穿了就是 Android SDK Platform 33 这个包,API Level 33、对应 Android 13,解压后固定落在 SDK 的platforms/android-33目录里,核心是一个只有方法签名、没有方法体的android.jar。它决定你编译期能调用哪些 API、aapt2 按哪套资源规则链接、lint 按哪个版本报NewApi。做老项目升级、验证新特性、给 CI 钉死编译环境的的人,都绕不开这份平台包,而它恰恰是最容易被“装了一半”的东西。

2. 拆开 android-33 目录:四个关键文件和三套版本号的关系

拿到这个包的人常有两种误判:一种是以为它是个安装程序,双击就完事;另一种是以为只要android.jar就够了,其余文件随手删。这个目录更像一份“编译期字典 + 资源底稿”的组合,少一个子目录,报错信息会指向完全不相干的地方。

2.1 android.jar 里全是桩:编译能过、运行才炸的原因

打开android-33/android.jar,随便挑个方法反编译,看到的方法体基本只有一句throw new RuntimeException("Stub!")。这不是包损坏,而是平台包的设计:它只负责让编译器知道“这个方法存在、参数是这些、返回是这个类型”,真正的实现躺在设备或模拟器的system/framework/framework.jar里。所以任何“我把 android.jar 塞进依赖里一起打包”的做法都会翻车,Duplicate class android.os.Bundle这类报错就是这么来的。

正确的用法只有一条:交给构建工具,让它挂在编译期 classpath 上,绝不参与打包。用 Gradle 时这件事是自动的,compileSdk 33就是在说“去platforms/android-33/android.jar找符号”;手写 javac 命令时才需要显式指定,见第 3 章的第四种装法。

还有一个常被忽略的细节:这份 jar 不含隐藏 API。想调@hide的方法,要么用反射,要么换用系统模块的core-for-system-modules.jar,别指望改个compileSdk就能解锁。

2.2 data/ 与 optional/ 各管什么

把目录摊平看,真正会被工具读到的就是下面这几个位置,其余多是模板和元数据:

路径内容什么时候会碰到
android.jarAPI 33 的桩实现编译期 classpath,几乎每次构建
data/res、data/fonts系统级资源与字体映射aapt2 链接资源、系统主题继承
optional/org.apache.http.legacy.jar已废弃的 HTTP 客户端老项目写useLibrary 'org.apache.http.legacy'
templates/新建工程的模板文件在 IDE 里创建 Activity 时
source.properties版本元数据工具判断“这是不是 API 33”的唯一依据
core-for-system-modules.jar系统模块编译用做 ROM 或系统应用才看

data/是最容易被误删的。有人为了省空间只留android.jar,结果资源编译阶段报一堆找不到@android:style/...的错,排查半天才发现是平台包的资源表被删了。整个platforms/android-33解压后一两百兆,省这点空间不值得。

2.3 compileSdk、targetSdk、minSdk:三套版本号互相导致问题的情况

新手最容易把这三个当成一回事。它们各管一段,改错任何一个,症状完全不同:

字段写在哪作用典型值
compileSdk模块级构建脚本编译期用哪份 android.jar33
targetSdkdefaultConfig运行时按哪个版本的行为规则跑33
minSdkdefaultConfig最低能装在什么版本的设备上21 / 24
buildToolsVersion可省略aapt2、d8 用哪个版本33.0.2

compileSdk 33只影响“你能不能写这行代码”,targetSdk 33才影响“设备会不会用新规则约束你”。所以会出现一种经典错位:编译全绿、安装正常、跑起来通知不弹——因为targetSdk升到 33 后,通知必须先申请运行时权限,而代码里只写了清单声明。

另一头,minSdk决定 lint 会不会拦你。minSdk 24却直接调 API 33 的方法,NewApi一定报,这时候该加的是版本判断或@RequiresApi,不是把minSdk一股脑提到 33——那等于把大半存量设备直接排除,属于用改配置掩盖设计问题。

3. 三条安装路径:sdkmanager 在线、离线解压、CI 无人值守

平台包的安装方式不止一种,选哪种取决于你的网络状况和是否需要“一台机器装完、十台机器复用”。三条路径装出来的目录结构必须完全一致,否则后面 Gradle 找不着,症状都一样但原因各不相同。

3.1 用 sdkmanager 在线安装并校验

这是最省事也最不容易出错的方式,前提是cmdline-tools是新版。

# 1) 先确认 sdkmanager 在位且版本够新,老版本不认识 platforms;android-33 这种写法 export ANDROID_HOME="$HOME/Android/Sdk" export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$PATH" sdkmanager --version # 2) 只装编译必需的三件套,别顺手把整个 SDK 全量拉下来 sdkmanager --sdk_root="$ANDROID_HOME" \ "platform-tools" \ "platforms;android-33" \ "build-tools;33.0.2" # 3) 想看源码跳转再加这个,可选,几十兆 sdkmanager --sdk_root="$ANDROID_HOME" "sources;android-33" # 4) 校验:列表里要能看到 android-33,并且带了 revision 号 sdkmanager --sdk_root="$ANDROID_HOME" --list_installed | grep -E "android-33|33.0.2"

参数上需要留意三处。--sdk_root必须显式给,尤其是机器上装过两个 SDK 的情况,不给就会装到工具自己推断的位置,然后项目读的是另一个位置。包名必须带分号,platforms;android-33是固定写法,写成android-33会被当成未知包。build-tools;33.0.2建议和平台包一起装,因为 aapt2 的版本跟平台包不匹配时,资源链接阶段的报错会非常难读,常见的是Android resource linking failed后面跟一串莫名奇妙的属性名。

装完别急着打开项目,先去platforms/android-33里看一眼source.properties有没有、android.jar是不是几十兆的正常体积。零字节的 jar 多见于下载被中断,sdkmanager有时不会主动报错。

3.2 离线包手工解压:目录名和元数据一个都不能错

内网机器、构建机没外网、或者要把 SDK 打进基础镜像时,只能走离线这条路。它本身不难,难在两个地方:目录层级和元数据。

SDK="$HOME/Android/Sdk" # 目标目录必须严格是 platforms/android-33,多一层或少一层都识别不到 mkdir -p "$SDK/platforms/android-33" unzip -q platform-33_r03.zip -d "$SDK/platforms/android-33" # 解压后先看根上有没有源文件,压平错了这里会是空的 cat "$SDK/platforms/android-33/source.properties" # 至少要能读到这两行,缺了工具就当这个目录不存在 # AndroidVersion.ApiLevel=33 # Platform.Version=13 # 确认 android.jar 不是空壳 ls -lh "$SDK/platforms/android-33/android.jar"

第一个坑是压缩包内层还套了一级目录。不少离线包的根目录叫android-33或者android-13,解压到platforms/android-33之后变成platforms/android-33/android-33/android.jar,工具自然找不到。判断方法很简单:ls platforms/android-33必须直接看到android.jar和source.properties,看不到就是层级错了。

第二个坑是package.xml。用sdkmanager装出来的包会带一份清单文件,sdkmanager --list_installed靠它列出来;手工解压的包没有这个文件,所以命令行列表里看不到,但 IDE 和 Gradle 仍能靠source.properties认出来。这不是故障,不用为了“让它显示出来”去伪造清单。真正致命的是source.properties也缺失,那种目录结构再对也白搭,工具只会报找不到平台。

3.3 CI 里无人值守安装:许可、路径、缓存三件事

构建机上没人点同意按钮,--licenses必须自动过;同时 SDK 目录最好落在工作区的相对路径里,方便整目录缓存。

set -euo pipefail SDK_ROOT="$PWD/.android-sdk" mkdir -p "$SDK_ROOT/cmdline-tools" # 假定 commandlinetools 压缩包已由缓存或制品仓库准备好,避免每次联网 unzip -q commandlinetools.zip -d "$SDK_ROOT/cmdline-tools" mv "$SDK_ROOT/cmdline-tools/cmdline-tools" "$SDK_ROOT/cmdline-tools/latest" export ANDROID_HOME="$SDK_ROOT" export PATH="$SDK_ROOT/cmdline-tools/latest/bin:$SDK_ROOT/platform-tools:$PATH" # 许可证必须接受,否则后面所有 install 都会静默失败或直接退出 yes | sdkmanager --sdk_root="$SDK_ROOT" --licenses > /dev/null sdkmanager --sdk_root="$SDK_ROOT" --install \ "platforms;android-33" "build-tools;33.0.2" "platform-tools"

set -euo pipefail不是摆设。没有它,yes | sdkmanager --licenses即便失败,脚本也会带着一脸无辜继续往下跑,最后挂在 Gradle 阶段的报错信息跟许可毫无关系,排查方向直接跑偏。缓存策略上,.android-sdk和 Gradle 的用户缓存目录一起缓存最省时间;只缓存前者的话,每次构建仍要重新解析依赖。

第三个容易漏的点是local.properties。构建机上通常不该有这个文件,路径由环境变量ANDROID_HOME或sdk.dir决定;如果有人把本地开发机的绝对路径提交进了仓库,CI 上一定报“SDK 位置无效”,而错误信息里那串路径看着特别眼熟——就是某个人的家目录。

3.4 不用 IDE:javac 加 d8 直接出 dex

只想验证一段代码在 API 33 下能不能编过,或者做个小工具类,不必拉起整个 Gradle 项目。

API="$ANDROID_HOME/platforms/android-33/android.jar" mkdir -p out/classes out/dex # javac 9 之后不再接受把 8 以上的版本塞进 -bootclasspath,直接给 -classpath 更省事 javac -source 11 -target 11 -classpath "$API" -d out/classes $(find src -name '*.java') # --lib 告诉 d8 哪些类由系统提供,不要打进 dex;--min-api 决定脱糖目标 "$ANDROID_HOME/build-tools/33.0.2/d8" \ --lib "$API" --min-api 24 --output out/dex $(find out/classes -name '*.class')

-source/-target 11要和项目实际用的语言级别对齐,写高了 javac 直接拒绝,写低了遇到var之类的语法会报错。--min-api 24的作用是让 d8 知道可以假定设备至少有 24,从而选择不同的脱糖策略;这个值必须和项目里的minSdk一致,写成 21 而项目是 24,多出来的兼容层不会报错但会白白增大体积。输出的classes.dex不能直接装,只是用来确认“这套符号在 33 下能解析”,真要出包还得回到构建工具。

4. 用 API 33 编出第一个包:Gradle 配置与四个行为变更的开关

平台包装好了,下一步是让它真正参与构建。这一段的关键不在“配置怎么写”,而在“配置对了但行为不对”的那部分——API 33 引入的几处变更,全都是编译期无感、运行期才现形的类型。

4.1 最小可用的构建配置与版本约束

// 模块级构建脚本 android { namespace 'com.example.demo33' // 8.x 起必填,别再写进清单文件 compileSdk 33 defaultConfig { applicationId "com.example.demo33" minSdk 24 targetSdk 33 versionCode 1 versionName "1.0" } compileOptions { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } buildFeatures { buildConfig true } }

namespace与applicationId是两个东西:前者决定生成的R类在哪个包下,后者是装机后的唯一标识。升级构建插件版本时最常见的报错就是清单里还有package="...",两边冲突。compileSdk 33写在模块级而不是defaultConfig里,写错位置不会立刻报错,但compileSdk不生效,症状是明明装了平台包却依然提示找不到 API。

构建工具链自身的版本约束必须一起看,否则会陷入“包没错、代码没错、就是编不过”的僵局:

构建插件版本需要的 JDK对 API 33 的支持
7.0 – 7.211能编但会告警,不建议
7.3 – 7.411可用,7.4 起算正式支持
8.0 及以上17推荐,namespace必须写在脚本里

JDK 版本不对时的报错很有迷惑性,往往是一句Unsupported class file major version,看着像依赖冲突,实际是构建插件跑在低于它要求的 JDK 上。改JAVA_HOME比改依赖快得多。

4.2 四个运行期变更:清单声明只是第一步

API 33 的行为变更里,下面四条最容易在升级后第一个版本就暴露出来。

通知权限。targetSdk到 33 后,发通知必须先拿运行时权限,只写清单不申请,通知静默丢失,日志里也没有明显报错:

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
// 只在 33 及以上申请,低版本没有这个权限,申请了也拿不到回调 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 1001); }

细化媒体权限。读图片从READ_EXTERNAL_STORAGE换成READ_MEDIA_IMAGES,视频、音频各一个。老代码继续申请旧权限,在 33 的设备上会直接返回拒绝,且不会弹框。照片选择器是更省事的替代路径,不涉及权限申请。

动态注册广播的导出标志。33 起注册接收器必须明确说清是否对外导出,不写就抛异常:

// RECEIVER_NOT_EXPORTED 表示只接收本应用内广播,跨进程的才用 EXPORTED ContextCompat.registerReceiver(context, receiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED);

剪贴板预览。系统会在复制后弹一个小预览,密码、验证码这类内容必须打上敏感标记,否则明文显示在屏幕上:

if (Build.VERSION.SDK_INT >= 33) { PersistableBundle extras = new PersistableBundle(); extras.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true); clip.getDescription().setExtras(extras); }

另外还有一条值得单独提:预测性返回手势。清单里application节点加上android:enableOnBackInvokedCallback="true"才会走新回调;开了之后旧的回退拦截逻辑可能收不到事件,这是升级时最容易被漏掉的一处。

4.3 验证产物真的挂在 33 上

编译通过不等于配置生效,出包后花十秒核对一下元数据,能省掉后面半小时的自我怀疑。

./gradlew :app:assembleDebug # 看产物里的 sdkVersion 与 targetSdkVersion 是否与预期一致 "$ANDROID_HOME/build-tools/33.0.2/aapt2" dump badging \ app/build/outputs/apk/debug/app-debug.apk | grep -E "sdkVersion|targetSdkVersion" # 期望看到 sdkVersion:'24' 与 targetSdkVersion:'33' # lint 会把“编译期能过、低版本设备会崩”的调用挑出来 ./gradlew :app:lintDebug

aapt2 dump badging读的是产物里编译后的清单,比看源文件可靠。lint 报告落在app/build/reports/下,重点看NewApi与UnusedAttribute两类:前者是低版本会崩的调用,后者常出现在清单属性写错位置的情况,比如把只在 33 生效的属性写到了低版本不认的节点上。

5. 排查清单:五个把 compileSdk 33 卡住的真实坑

下面五条基本覆盖了从装包到运行的主要失败面,按“现象 → 原因 → 解决”记,遇到时可以直接对号入座。

5.1 装包阶段:找不到平台、包是坏的

现象:同步时报Failed to find target with hash string 'android-33',但目录明明存在。原因通常是三处之一:ANDROID_HOME指向了另一个 SDK、项目里的local.properties写死了旧路径、或者目录层级多套了一层。解决是先打印实际读取的路径,再对照platforms/android-33/android.jar是否存在;层级错了就往上挪一层,路径写死了就删掉那行改用环境变量。

现象:platforms/android-33存在且结构正确,命令行列表里却看不到它。原因是手工解压的包没有package.xml清单,sdkmanager只认清单。解决是不用管它,IDE 与构建工具靠source.properties识别,只要能编过就别为了好看去伪造清单文件。

现象:报Installed Build Tools revision 33.0.0 is corrupted。原因是下载中断导致 jar 或可执行文件残缺,常见于网络不稳时装的包。解决是删掉build-tools/33.0.0整个目录重装,顺手换到 33.0.2,同时清一下构建守护进程,否则它会拿着旧的文件句柄继续用。

5.2 构建阶段:版本与工具链对不上

现象:compileSdkVersion 33 requires JDK 11 or above,或者反过来报Unsupported class file major version 61。原因是构建插件要求的 JDK 与当前JAVA_HOME不匹配,装了 33 的平台包但这台机器跑的是 8。解决是把JAVA_HOME切到插件要求的版本,注意只改项目设置里的 JDK 不够,命令行构建走的是环境变量。

现象:资源链接阶段报错,提示一堆@android:style找不到。原因是platforms/android-33被裁剪过,data/目录被删了,只剩一个 jar。解决是重新装一遍平台包,别为了省空间手动删子目录,这个目录不是能随便精简的。

5.3 运行阶段:编得过但行为不对

现象:编译全绿,装机后通知不弹,日志里没有任何异常。原因是targetSdk已是 33,但代码里只有清单声明,没做运行时申请。解决是补上 33 及以上的权限请求,并处理用户拒绝后的降级路径——第一次拒绝之后就不会再弹系统框了,得自己引导去设置页。

现象:在 33 的设备上闪退,堆栈指向某个 33 才有的方法。原因往往是用了高版本 API 却没做版本判断,编译期因为compileSdk 33放行了,低版本设备上找不到符号。解决是加Build.VERSION.SDK_INT判断或@RequiresApi注解,并且用 lint 的NewApi规则把这类问题在提交前拦下来,别等用户反馈。

6. 把 android-33 用出复利:API 差异比对与接手项目的自检套路

平台包装完只是起点,真正省时间的是把它当成一份可以查询的数据源。升级到 33 时最常被问的问题是“这个类/方法到底是 33 才有的,还是我记错了”,靠翻文档效率很低,直接比对两份 jar 更快。

#!/usr/bin/env bash # 比对两个 API 级别的类清单,输出 33 相对 32 新增的公开类 for api in 32 33; do unzip -Z1 "$ANDROID_HOME/platforms/android-$api/android.jar" \ | grep '\.class$' \ | sed 's#/#.#g; s#\.class$##' \ | sort > "/tmp/api$api.txt" done # comm -13 取“只在第二个文件里出现”的行;过滤掉内部类,噪音太多 comm -13 /tmp/api32.txt /tmp/api33.txt | grep -v '\$' | head -40

unzip -Z1只列文件名不解压,几十兆的 jar 也能秒出结果;sed把路径式的类名转成点号形式,方便直接贴进代码里搜索;comm要求两个输入都已排序,所以前面必须带sort。滤掉$是因为内部类数量庞大,真正要看的公开类会被淹没。

方法级别的差异用javap补,比看源码快:

# 看某个类在 33 里多了哪些方法,配合 diff 用效果更好 javap -classpath "$ANDROID_HOME/platforms/android-33/android.jar" \ android.app.NotificationManager | grep -i "channel\|permission"

这套比对方法我一般会顺手存成一个脚本放在项目根目录,每次升级compileSdk前跑一遍,把新增类列出来,对照自己的调用点逐个确认。比起事后在崩溃平台上抓NoSuchMethodError,这个动作的成本几乎为零。

接手一个已经写着compileSdk 33的项目时,我会按固定顺序过一遍,顺序错了就会在无关的地方浪费时间:

检查项看什么判定标准
平台包完整性platforms/android-33下四个关键文件jar 非空、source.properties有 ApiLevel
版本三元组compileSdk/targetSdk/minSdk三者关系合理,不是靠提 minSdk 掩盖问题
工具链构建插件版本与 JDK插件 8.x 必须配 JDK 17
行为变更通知、媒体权限、广播注册、剪贴板每项都有对应的运行期处理,不只是清单声明
产物元数据aapt2 dump badging输出targetSdkVersion与脚本里写的一致

有一回我图省事,跳过最后一行直接发了包,结果构建脚本里targetSdk被一个产品维度的配置覆盖成了 30,功能全对、行为规则全按老版本走,测试环境的 33 设备上什么事都没有,线上却陆续出现通知收不到。从那以后我每次出包前都会把aapt2 dump badging的输出贴进发布记录,谁改的、改成什么,一眼能看到。这个习惯不花时间,但省下的都是最贵的那部分排查成本。希望帮到你。

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

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

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

立即咨询