Android 13适配实战:targetSdk 33权限变更与SDK配置全解析
简介:面向 Android 应用开发者的 SDK Platform 33 离线资源包,对应 Android 13 与 API 级别 33,主要服务于需要在集成开发环境或命令行中指定平台版本进行构建的开发者,可有效解决 SDK 平台缺失、联网下载缓慢或版本不匹配导致的构建失败问题。压缩包共 2000 个文件,其中 1974 个 xml 为系统框架资源、属性定义与设备配置,22 个 html 为 API 参考页面,4 个 txt 为版本说明,整体约 60.84MB,便于离线归档、快速部署与多机器迁移。已有 855 人学习下载。作为 SDK 的离线平台包,放入 platforms 对应目录后即可被 Android Studio 识别,供开发者编译、调试与模拟运行,确保应用适配 Android 13 的权限模型、界面特性和行为变更。包内框架资源文件还可作为查阅系统配置的离线素材,配合文档能更深入理解平台结构、资源类型与开发要求,对需要精确控制 targetSdkVersion 或维护旧工程兼容性的团队尤其实用。
1. 把 targetSdkVersion 提到 33 那天,Android 13 会先给你点颜色看看
当你的应用第一次在 Android 13 设备上弹出那个细化后的位置权限弹窗时,你会发现旧代码里所有围绕 READ_EXTERNAL_STORAGE 的兼容逻辑几乎全部作废。这不是某个厂商的魔改,而是 android-33 这套 SDK 把运行时权限和前台服务类型的规则彻底重写了。这份笔记是我把模拟项目X从 API 31 迁到 android-33 时整理的完整记录,覆盖 SDK 目录拆解、命令行安装、Gradle 参数配合、模拟器调试和五个高频翻车现场。适合正在适配 Android 13 的开发者,也适合刚拿到 android-33 离线包不知道往哪放的新人。你能按步骤走完,也能少踩几个我已经踩过的坑。
2. Android SDK 33 到底装了什么:目录结构与三条落地路径
2.1 platforms/android-33 目录里每个文件都不是摆设
先从最常见的离线包说起。一个完整的 android-33 SDK 组件,最先打开的就是platforms/android-33目录。这里最关键的文件是android.jar,它不是你应用运行时的依赖,而是编译期使用的桩文件。我在某公司接手过一个老项目,同事直接把android.jar塞进 libs 当普通 jar 用,结果编译能过、一运行就NoSuchMethodError,这就是没搞懂它本质。
android-33 的android.jar里包含的是对外公开的 API 类和方法签名。真正的实现逻辑都在设备系统的 framework 里,不会打进你的 APK。所以你在compileSdk 33下调用新 API,编译期没问题,运行期要看设备系统版本。
目录下还有data/res和data/scripts等辅助资源,这些是模拟器或系统镜像用的,不需要直接动。关键要注意platform目录的版本号必须精确到android-33,而不是android-31或android-34。Gradle 依赖compileSdk时会去查找platforms/android-<版本号>这个确切目录,多一个字符、少一个字符都会报错。
2.2 三种把 android-33 放进 SDK 目录的路径
路径一:官方 sdkmanager 命令行安装。我一般是这么操作:
# 先安装 platform-tools,保证 adb 存在 sdkmanager --install platform-tools # 再装 android-33 platform 和匹配的 build-tools sdkmanager --install "platforms;android-33" "build-tools;33.0.2" # 查看是否装好 sdkmanager --list_installed注意分号是必须的,SDK 内部目录名就是用分号分隔的。build-tools版本我建议选33.0.2,它修复了 33.0.0 在 Windows 上偶发的路径过长问题。如果你用 Android Studio 自带的 SDK Manager,图形界面勾选同一项效果一样,但命令行的好处是能直接看到下载目录是否完整。
路径二:离线包手动放置。很多下载资源是打包好的platforms/android-33目录。拿到以后,别解压到桌面,也别改文件名。我一般会直接把它放到ANDROID_HOME指向的 SDK 目录下,比如~/Library/Android/sdk/platforms/(Windows 上默认是%LOCALAPPDATA%\Android\Sdk)。放完后必须检查一点:目录下要有个android-33文件夹,里面必须包含android.jar。如果发给你的人把版本号写成 32,但内容又是 33 的,那基本是这个包被改过名,风险很大。
路径三:通过 Gradle 自动补齐。如果你在build.gradle里写compileSdk 33而本机没有对应 platform,新版 Gradle 会自动下载。但前提是build-tools版本也要匹配,否则会卡在 “SDK Build Tools 33.0.0” 缺失这一步。
这三条路径的核心共同点:Android SDK 是“按目录识别”的,不是按注册表识别。目录里的文件结构、版本号都严格依赖官方结构。我见过有人把android.jar单独拿出来改名为android33.jar想塞给 Gradle,最后只能得到一个诡异的aapt2 compile failed。
2.3 build-tools、platform-tools 和 emulator 各自负责什么
为什么安装 android-33 时要求同时安装 build-tools?因为build-tools里包含aapt2、zipalign、d8等工具,它们负责把资源编译、DEX 打包和 APK 对齐。platform-tools里是adb、fastboot,负责设备和模拟器交互。emulator目录则是模拟器本体。
| 组件目录 | 关键文件 | 职责 | 和 android-33 的关系 |
|---|---|---|---|
| platforms/android-33 | android.jar、data/res | 编译期 API 桩与资源 | compileSdk 指向它 |
| build-tools/33.0.2 | aapt2、d8、zipalign | 资源打包与 DEX 化 | 版本必须 >= 编译要求 |
| platform-tools | adb、fastboot | 设备通讯 | 版本太老会导致 adb 异常 |
| emulator | emulator 可执行文件 | 运行 AVD | 系统镜像需匹配 API 33 |
很多时候你下载的“android-33 离线包”只包含platform,build-tools是单独的压缩包,别漏掉。没有build-tools,你连 APK 都打不出来,因为单靠 Gradle 内部默认工具链并不能自动补齐所有版本。
2.4 下载中断后怎么判断包是坏的
这是血泪经验。我有一次用sdkmanager时网络波动中断,再次安装时它直接跳过解压,导致platforms/android-33目录里没有android.jar。运行 Gradle 时报Expected file android.jar at ... doesn't exist。解决方法是进入 SDK 目录,把platforms/android-33整个文件夹删除,然后重新执行sdkmanager --install。半成品别指望它能自愈,删干净重来反而是最快的。
如果你是从网盘下载的离线包,最好检查一层:platforms/android-33下应该有android.jar、package.xml、data目录和source.properties。打开source.properties,确认Pkg.Revision=33。如果版本号符合但目录名是 32,那大概率是发包人改错了目录名。这种我遇到过,直接把目录名改回android-33即可,但前提是android.jar内容没有被替换过。目录名和内部信息不一致时,宁可不装。
3. 把 android-33 真正用进工程:Gradle 参数与命令行实测
3.1 compileSdk、targetSdk、buildToolsVersion 的三角关系
先解释三个参数的区别。compileSdk只是编译期平台,targetSdk决定系统兼容模式,buildToolsVersion决定打包工具版本。很多人把compileSdk和targetSdk理解成同一个东西,实际差别很大。
| 参数 | 推荐值 | 作用 | 常见误区 |
|---|---|---|---|
| compileSdk | 33 | 定义可用的 API 级别 | 写成 targetSdkVersion 33 不够 |
| targetSdk | 33 | 运行时权限与行为变更 | 保持 33 才能触发 Android 13 新特性 |
| buildToolsVersion | 33.0.2 | 打包工具版本 | 过高/过低都会让 Gradle 重新下载 |
Android 13 的行为变更,比如通知要运行时权限、媒体权限拆分,都取决于targetSdkVersion是否大于等于 33。如果你compileSdk是 33 而targetSdk是 31,代码里可以调用 SDK 33 的新 API,但系统不会触发新的权限模型。这很容易造成“我用了新 API 但表现还是老的”的错觉。我见过不少项目,升级完compileSdk却忘了改targetSdk,结果用户反馈没有适配效果,最终排查半天才找到根因。
另外,buildToolsVersion在 Android Studio 新版本里可以省略,Gradle 会使用默认版本。但如果你项目里显式指定了,就以你指定的为准。显式指定的好处是可复现,坏处是版本不对就报错。我一般会保持显式指定,方便多个终端统一。
3.2 一个最小可用工程配置样例
下面是我在模拟项目 X 里用过的精简配置:
android { compileSdk 33 buildToolsVersion "33.0.2" defaultConfig { applicationId "com.example.demo" minSdk 24 targetSdk 33 versionCode 1 versionName "1.0" } }这里compileSdk 33是关键,Gradle 会去 SDK 目录查找platforms/android-33。注意我写的是33而不是"33",新版 AGP 允许整数写法。然后targetSdk 33必须写在defaultConfig里。如果你用了某些第三方依赖,它们可能自带targetSdk覆盖,那就需要靠 Gradle 的冲突策略强制统一。
补充一点:如果你在settings.gradle里配置了pluginManagement的repositories,确保google()在里面,否则自动下载 SDK 和 build-tools 会失败。这个看似废话,但很多人复制老项目模板时忘了加,导致每次构建都提示“无法解析资源”。
3.3 命令行编译流程
我不一直开 Android Studio,很多适配验证直接在终端跑:
# 先确认本机 ANDROID_HOME 指向 sdk echo $ANDROID_HOME # 清理旧构建 ./gradlew clean # 编译 debug apk ./gradlew assembleDebug # 如果只需要检查编译期错误 ./gradlew compileDebugJavaWithJavac完成后 APK 在app/build/outputs/apk/debug/app-debug.apk。接着用 adb 装到 Android 13 手机上测试:
adb install -r app/build/outputs/apk/debug/app-debug.apk注意第一次跑的时候,Gradle 会检查compileSdk对应 platform 是否齐全。如果之前没装过,它会提示Failed to find target with hash string 'android-33'。这时候执行sdkmanager安装,或者确认离线包的目录名。这一步最容易翻车,因为 Gradle 不会自动定位到你自定义目录以外的路径。
如果你把 SDK 放在非默认位置,可以在项目根目录的local.properties里指定:
echo "sdk.dir=/path/to/your/sdk" >> local.properties注意local.properties是本地文件,不应该提交到版本控制。另一个方法是设置ANDROID_HOME环境变量,Gradle 优先读这个。我本人习惯用ANDROID_HOME,因为命令行脚本可以到处直接用。
3.4 AGP 版本与 compileSdk 的匹配
AGP 7.4 可以支持compileSdk 33,AGP 8.0 以后对 SDK 版本和 JDK 版本都有对应要求。如果你在升级 compileSdk 时遇到compileSdkVersion is not supported,大概率是 AGP 版本太低。这时候去build.gradle里升级 AGP 插件版本,别只改 compileSdk。我见过有人把 AGP 从 4.2 直接改到 8.1,结果 Gradle wrapper 版本跟着崩了,一下从适配问题变成构建系统问题。所以升 compileSdk 前,先看 AGP 对应的兼容矩阵。
4. android-33 适配踩坑记录:现象、原因、解决办法
4.1 权限相关的坑:通知、媒体文件、前台服务
坑 1:通知完全收不到,或通知栏不显示。
现象:targetSdk升到 33 后,应用自己的自定义通知在 Android 13 上不弹,但日志里没有错误。
原因:Android 13 新增POST_NOTIFICATIONS运行时权限,targetSdk 33下若未申请,系统直接拦截所有通知显示。
解决:在AndroidManifest.xml中声明权限,然后在代码里像申请位置权限那样请求:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />val notificationPermission = if (Build.VERSION.SDK_INT >= 33) { Manifest.permission.POST_NOTIFICATIONS } else return requestPermissionLauncher.launch(notificationPermission)注意requestPermissionLauncher是 Activity 里注册的ActivityResultLauncher。还有一个细节:如果你在 targetSdk 32 时用NotificationChannel还能显示,升到 33 后 channel 是否生效取决于权限申请的结果。所以要先申请权限,再发通知,顺序别反。
坑 2:读取媒体文件的旧权限失效。
现象:targetSdk 33继续用READ_EXTERNAL_STORAGE读图片库,结果返回空列表。
原因:Android 13 把存储权限拆成了READ_MEDIA_IMAGES、READ_MEDIA_VIDEO、READ_MEDIA_AUDIO,旧权限在 targetSdk 33 下不再授权媒体文件。
解决:根据读取内容分别声明新权限。如果只读图片:
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />代码里按版本判断:
val permission = if (Build.VERSION.SDK_INT >= 33) { Manifest.permission.READ_MEDIA_IMAGES } else { Manifest.permission.READ_EXTERNAL_STORAGE }这两个权限不能同时申请,否则系统弹窗会出现奇怪的重复授权提示。我是在某图像处理 Demo 上踩到的,当时把新旧权限都写了,授权界面直接卡死。
坑 3:前台服务启动崩溃,报 MissingForegroundServiceTypeException。
现象:targetSdk 33下启动前台服务时,突然抛MissingForegroundServiceTypeException。
原因:Android 13 要求 targetSdk 33 下必须给 service 指定foregroundServiceType,否则系统不认为这是一个合法的前台服务。
解决:在 Manifest 里给 service 加类型,比如定位服务:
<service android:name=".LocationService" android:foregroundServiceType="location" />同时启动代码里调startForeground时也要传入一样的类型。注意如果你的服务同时需要相机和麦克风,类型可以写成camera|microphone,但必须在启动时声明清楚。这类崩溃只在 Android 13 上出现,所以很容易在真机适配时被忽略。
4.2 构建与工具链的坑:build-tools、NDK、模拟器
坑 4:编译时报Failed to find Build Tools revision 33.0.0。
现象:Gradle 提示找不到build-tools 33.0.0,但你已经装了 33.0.2。
原因:项目里buildToolsVersion写死了"33.0.0",而本机只装了 33.0.2,版本号不匹配,Gradle 不会自动降级。
解决:要么把buildToolsVersion改成"33.0.2",要么额外安装 33.0.0。我一般直接改配置,少装一个版本省空间。但如果有 native 代码,还要检查 NDK 版本,因为 NDK 的编译工具链也可能依赖特定 API。当时我把 NDK 从 25 升到 26,才解决了undefined reference的问题。
坑 5:模拟器启动后 adb offline,或者直接黑屏。
现象:AVD 使用 android-33 的系统镜像,启动后adb devices显示 offline,模拟器界面一直黑屏。
原因:platform-tools版本太旧,和新的 emulator 镜像存在兼容问题;或者模拟器里的 GPU 渲染缓存崩溃。
解决:先更新 platform-tools 到最新,然后冷启动模拟器,禁用快照:
emulator -avd your_avd_name -no-snapshot如果还不行,删除~/.android/avd/your_avd_name.avd下的快照缓存文件,再重新启动。这个坑很费时间,因为黑屏不会报错,只能抓 logcat 慢慢看。
坑 6:APK 运行时ClassNotFoundException,但编译期一切正常。
现象:用targetSdk 33打包后,在 Android 13 设备上运行,某个类找不到,但编译和安装都没问题。
原因:有可能是你引用的类只存在于android.jar的 stub 中,运行时设备系统没有对应实现,也就是用了隐藏 API;也可能是 R8 混淆把本来需要的类剥离了。
解决:先用adb shell dumpsys package确认设备系统 SDK 版本,再检查代码里是否使用了非公开 API。如果是隐藏 API,改成反射或替换成官方支持的方法。R8 导致的,就在proguard-rules.pro里加 keep 规则。这个问题和 android-33 本身关系不大,但升级 targetSdk 后很多人顺手开了全量混淆,结果把问题暴露出来了。
5. 不用玄学,用三条验证路径确认 android-33 已生效
第一,看 APK 解包后的 targetSdkVersion。用aapt2从 build-tools 里直接解析:
~/Android/sdk/build-tools/33.0.2/aapt2 dump badging app-debug.apk | grep -E "sdkVersion|targetSdkVersion"输出里能看到sdkVersion和targetSdkVersion。注意sdkVersion实际是最低版本,targetSdkVersion才是我们要确认的 33。如果这个值还是 31,说明 Gradle 配置没改对或者有依赖强行覆盖了 targetSdk。
第二,确认运行设备的系统版本,避免在错误的设备上测试:
adb shell getprop ro.build.version.sdk adb shell getprop ro.build.version.release如果release不是 13 或更高,那 Android 13 的权限新特性就不会表现。我见过有人拿一台 API 30 的设备测试 targetSdk 33 的通知权限,结果正常显示,转头换个 Android 13 手机就翻车。
第三,检查最终合并后的 Manifest,确认uses-sdk标签里的实际值。Gradle 编译过程中会做多次 AndroidManifest 合并,直接看中间产物最可靠:
cat app/build/intermediates/merged_manifest/debug/processDebugMainManifest/AndroidManifest.xml | grep uses-sdk这里显示的targetSdkVersion是合并完、各个依赖库互相覆盖后的最终值。从那以后,我每次拿到新 SDK 或离线包,都会先跑一遍aapt2 dump badging,确认真正打进 APK 的版本和 Manifest 里的配置一致,再上设备做权限冒烟。这个习惯帮我躲过不少“以为改了配置其实缓存没清”的坑,希望你也能用上。
本文还有配套的精品资源,点击获取