- 1:Android 12 (API Level 31-32)
- 1.1:设计系统
- 1.2:隐私与安全
- 1.3:用户界面
- 1.4:媒体与音频
- 1.5:性能与连接
- 1.6:开发者功能
- 2:Android 13 (API Level 33)
- 2.1:个性化与用户界面
- 2.2:隐私与安全
- 2.3:连接性与音频
- 2.4:用户体验
- 2.5:平板与大屏幕
- 2.6:性能优化
- 3:Android 14 (API Level 34)
- 3.1:个性化定制
- 3.2:相机与媒体
- 3.3:隐私与安全
- 3.4:电池与性能
- 3.5:无障碍功能
- 3.6:健康与安全
- 3.7:大屏幕功能
- 3.8:开发者功能
- 4:Android 15 (API Level 35)
- 4.1:安全与防盗
- 4.2:Private Space(私密空间)
- 4.3:相机增强
- 4.4:多任务与大屏幕
- 4.5:连接性
- 4.6:用户界面
- 4.7:其他功能
- 4.8:技术改进
- 5:Android 16 (API Level 36)
- 5.1:设计系统
- 5.2:通知与实时更新
- 5.3:桌面模式
- 5.4:Linux 终端
- 5.5:大屏幕改进
- 5.6:相机增强
- 5.7:隐私与安全
- 5.8:性能增强
- 5.9:媒体功能
- 5.10:连接性
- 5.11:无障碍功能
- 5.12:用户体验
- 5.13:开发者功能
- 5.14:2026 年规划
- 6:版本对比总结
- 6.1:设计演进
- 6.2:隐私安全演进
- 6.3:相机功能演进
- 6.4:连接性演进
- 6.5:大屏幕功能演进
- 6.6:性能优化演进
- 7:开发者 API Level 参考
- 8:目录
- 9:Android 12 (API 31)
- 9.1:所有应用的行为变更
- 9.2:针对 API 31+ 的行为变更
- 10:Android 13 (API 33)
- 10.1:所有应用的行为变更
- 10.2:针对 API 33+ 的行为变更
- 11:Android 14 (API 34)
- 11.1:所有应用的行为变更
- 11.2:针对 API 34+ 的行为变更
- 12:Android 15 (API 35)
- 12.1:所有应用的行为变更
- 12.2:针对 API 35+ 的行为变更
- 13:Android 16 (API 36)
- 13.1:所有应用的行为变更
- 13.2:针对 API 36+ 的行为变更
- 14:迁移检查清单
- 14.1:Android 12 迁移
- 14.2:Android 13 迁移
- 14.3:Android 14 迁移
- 14.4:Android 15 迁移
- 14.5:Android 16 迁移
- 15:参考资源
Android 12 - 16 主要新特性和应用行为变更详解
下文将列出 Android 12 到 Android 16 的的所有主要新特性,并保含应用行为变更,包括代码示例和迁移指南。
Android 12 (API Level 31-32)
发布日期: 2021年10月4日
代号: Snow Cone
设计系统
Material You
- 全新设计语言:引入 Material You 设计系统,带来更大的按钮、更多动画效果
- 动态配色(Monet):系统自动根据壁纸提取颜色,生成个性化主题
- 自定义主题:支持应用程序和系统菜单使用壁纸配色方案
隐私与安全
隐私仪表盘(Privacy Dashboard)
- 统一查看应用权限使用情况
- 显示位置、相机、麦克风访问历史
- 支持直接撤销应用权限
权限控制增强
- 模糊位置:应用请求位置时可选择提供模糊位置而非精确位置
- 相机和麦克风开关:快速设置中新增全局禁用相机/麦克风的开关
- 剪贴板访问通知:应用访问剪贴板时显示通知
- 蓝牙权限独立:使用蓝牙不再需要位置权限
Android Private Compute Core
- 私密计算核心,保证个人信息安全、私密且本地存储
应用休眠(App Hibernation)
- 长期未使用的应用自动休眠,优化设备存储、性能和安全性
用户界面
启动画面(Splash Screen)
- 为每个已安装应用自动生成启动画面
- 开发者可自定义启动画面样式
截屏功能增强
- 滚动截屏:原生支持长截图功能
单手模式
- 内置单手模式,屏幕内容缩小至一半,方便单手操作
小部件(Widgets)
- 视觉设计全面改版
- 支持圆角 API
媒体与音频
- 空间音频:支持 MPEG-H 3D Audio 和空间音频
- HEVC 视频转码:自动转码 HEVC 视频以兼容不支持的应用
- 触觉反馈生成器(HapticGenerator):从音频生成触觉反馈
性能与连接
- 带宽估算改进
- Wi-Fi P2P 并发:同时支持 Wi-Fi 点对点连接和互联网连接
- 配套设备管理增强
开发者功能
- 富内容插入 API:简化应用间传输格式化文本和媒体(如剪贴板)
- Camera2 供应商扩展
- 原生动画图像解码
- 兼容媒体转码
- AppSearch 设备内搜索引擎
- 性能等级 API:提供设备能力信息
Android 13 (API Level 33)
发布日期: 2022年8月15日
代号: Tiramisu
个性化与用户界面
Material You 扩展
- 第三方应用图标主题化:第三方应用支持使用 Material You 主题图标
- 非 Google 应用配色:自定义应用以匹配壁纸主题和颜色
锁屏与时钟
- 双时钟样式:在单行和双行时钟布局之间切换
媒体播放器
- 更新的媒体播放器设计,根据专辑封面调整外观
- 动态播放进度条
隐私与安全
照片选择器(Photo Picker)
- 精细化媒体权限:仅选择特定照片和视频供应用访问,无需共享整个媒体库
- 更安全的媒体选择方式
通知权限
- 运行时通知权限:从 Play 商店下载的新应用需要权限才能发送通知
隐私仪表盘增强
- 查看过去 7 天的数据访问历史
- 了解应用如何使用个人信息
剪贴板改进
- 复制内容到剪贴板时显示标准视觉确认
- 提供复制内容的预览
附近 Wi-Fi 设备权限
- 新增用于访问附近 Wi-Fi 设备的权限
连接性与音频
蓝牙 LE Audio
- 支持蓝牙 LE Audio 和 LC3 音频编解码器
- 在多个蓝牙设备之间接收和共享音频
Wi-Fi 7
- 支持 Wi-Fi 7,降低延迟、缓冲、卡顿和拥塞
MIDI 2.0
- 支持 MIDI 2.0 标准
- 通过 USB 连接 MIDI 2.0 硬件
用户体验
多语言支持
- 应用级语言设置:为单个应用分配特定语言
- 系统语言和应用语言可独立设置
快捷功能
- QR 码扫描器:通知托盘中快速启动 QR 码扫描器
- 分屏通知:长按并拖动通知可在手机和平板上以分屏模式打开
预测性返回手势
- 适用于手机、大屏幕和折叠设备的预测性返回手势
平板与大屏幕
- 更新的任务栏:平板电脑上一览所有应用,轻松拖放进入分屏模式
- 手掌识别:平板电脑识别手掌和触控笔为独立触摸,减少误触
性能优化
- JNI 调用性能:JNI 调用速度提升最高 2.5 倍
- OpenJDK 11 更新
- ART 运行时优化
- 更快的连字符处理
Android 14 (API Level 34)
发布日期: 2023年10月4日
代号: Upside Down Cake
个性化定制
AI 壁纸生成
- 生成式 AI 壁纸:使用 AI 文本到图像扩散模型创建自定义壁纸(首次在 Pixel 8/8 Pro 上提供)
锁屏定制
- 自定义选择器更新:更轻松地切换壁纸和自定义锁屏快捷方式
- 多种时钟样式:新的时钟样式和流畅动画
相机与媒体
Ultra HDR
- Ultra HDR 图像格式:拍摄和显示高动态范围照片
- HDR 视频捕获:Camera2 API 支持高动态范围视频捕获
隐私与安全
数据共享通知
- 应用与第三方共享位置数据时通知用户
Photo Picker 功能
- 选择特定照片而非授予完整媒体库访问权限
PIN 安全增强
- 六位 PIN 码:自动解锁无需按回车
应用安装限制
- 阻止安装基于 Android 5.1 Lollipip API 及更早版本的应用
电池与性能
电池信息增强
- "自上次充满电以来的屏幕使用时间"功能回归
- 显示电池制造日期和循环计数
- 更高效的 Android 系统进程,改善电池续航
无障碍功能
- 改进的放大器:支持从 100% 缩放开始的捏合缩放
- 通知闪光灯:无障碍通知闪光功能
健康与安全
Health Connect
- 集成到设置中作为健康和健身数据的中心枢纽
未知追踪器警报
- 检测陌生的蓝牙追踪设备
地震预警系统
- Android 地震预警系统在 95 个以上国家/地区提供早期预警
大屏幕功能
- 应用配对(App Pair):保存并启动特定应用组合
- 拖放功能:在全尺寸应用之间拖放内容
- 视频通话自动取景
开发者功能
- Jetpack Compose:部分设置应用使用 Jetpack Compose 框架重写
- Credential Manager:统一登录方法
- 截屏检测 API
- OpenJDK 17 更新
Android 15 (API Level 35)
发布日期: 2024年10月15日
代号: Vanilla Ice Cream
安全与防盗
盗窃检测锁(Theft Detection Lock)
- AI 盗窃检测:手机感知被抢夺并有人试图跑步、骑车或开车逃离时自动锁定设备
- 适用于大多数 Android 10+ 设备
远程锁定(Remote Lock)
- 使用手机号码和简单安全检查从任何设备快速锁定手机
防盗设置保护
- 针对小偷目标设置(如移除 SIM 卡或关闭"查找我的设备")添加认证要求
- 检测到应用和设置多次失败尝试时锁定设备
Private Space(私密空间)
- 数字保险箱:在手机上创建独立的私密空间来组织敏感应用(社交、约会或银行应用)
- 锁定时,应用对他人几乎不可见,隐藏于应用列表、最近应用、通知和设置中
相机增强
专业相机功能
- 夜间模式场景检测
- 混合自动曝光
- 精确色温调整
- 弱光增强:开发者可控制相机预览的亮度提升
- 高级闪光灯强度调整:拍摄照片时精确控制闪光灯强度
多任务与大屏幕
分屏与任务栏
- 保存最喜欢的分屏应用组合以快速访问
- 在屏幕上固定任务栏以快速切换应用
部分屏幕共享
- 用户可以仅共享或录制应用窗口,而非整个设备屏幕
连接性
卫星消息
- 运营商消息应用可在没有移动或 Wi-Fi 连接的情况下使用卫星连接收发消息
蓝牙快速设置
- 点击蓝牙快速设置磁贴打开弹出对话框
- 执行更多功能:切换蓝牙、连接/断开单个设备、进入设置页面、配对新设备
用户界面
预测性返回手势
- 正式从开发者选项毕业
- 系统动画(返回主屏幕、跨任务、跨活动)提供更流畅直观的导航体验
应用归档
- 直接从系统设置归档应用,释放存储空间
- 归档应用可轻松恢复,权限被记住
- 独立于 Google Play 的应用归档选项
其他功能
通行密钥(Passkeys)
- 使用 Passkeys 进行身份验证的应用支持一键登录
键盘振动
- 新增"键盘振动"开关,全局禁用键盘振动
- 关闭时系统设置将覆盖各个键盘应用内的设置
敏感通知
- 防止恶意 Android 应用读取一次性密码(OTP)
技术改进
ANGLE
- 将 ANGLE 作为在 Vulkan 之上运行 OpenGL ES 的可选层
- 标准化 Android OpenGL 实现以提高兼容性,某些情况下提高性能
虚拟 A/B 更新
- 新版本的 Android 虚拟 A/B 更新机制
- 更快、更小、更高性能的 OTA 更新
无障碍功能
- 支持通过 USB 和蓝牙使用 HID 标准的盲文显示器
性能 API
- ApplicationStartInfo API:应用启动洞察
- 详细应用大小信息
- 改进的 SQLite 数据库 API
- Android 动态性能框架更新
Android 16 (API Level 36)
发布日期: 2025年6月10日
内核版本: Linux 6.12
设计系统
Material 3 Expressive
- 全新设计语言:Material 3 Expressive 设计语言全面改版
- 增加动画、颜色和模糊效果的使用
- 2025年9月开始部署到 Pixel 6 及更新设备和 Pixel Tablet
- 注:初始版本不包含,后续更新
通知与实时更新
Live Updates(实时更新)
- 新通知类别:帮助用户监控和快速访问重要的正在进行的活动
- 渐进式音量:来自同一应用的连续通知,音量会在一分钟内逐渐降低,而非反复全音量播放
Progress-Centric Notifications
- 以进度为中心的通知设计
桌面模式
Desktop Mode for Tablets
- 平板电脑桌面模式,类似 ChromeOS 和三星 DeX
- 注:初始版本不包含,计划 2025 年晚些时候发布
Linux 终端
Linux Terminal(扩展版)
- 扩展的"Linux 终端"功能(最初在 Android 15 QPR2 beta 引入)
- 允许用户在设备上的虚拟机中运行 Linux 应用程序
- 利用 Android 虚拟化框架(AVF)创建基于 Debian 的环境
大屏幕改进
屏幕方向和可调整大小
- 移除应用在大屏幕上限制屏幕方向和可调整大小的能力
- 鼓励开发者创建自适应应用,流畅调整不同显示尺寸和方向
- 2025年:影响大屏设备(屏幕宽度 > 600dp)上针对 API level 36 的应用,可选择退出
- 2026年:扩展到针对 API level 37 的应用,取消退出选项
相机增强
专业相机功能
- 夜间模式场景检测
- 混合自动曝光
- 精确色温调整
- 更好支持专业相机用户
隐私与安全
Privacy Sandbox 集成
- 隐私沙盒功能整合
密钥共享 API
- Key sharing API
Intent 安全改进
- 改进的 Intent 安全性
健康与健身权限
- 更细粒度的健康和健身权限
性能增强
系统性能工具
- 系统触发的性能分析
- 自适应刷新率 API
- 更好的作业调度内省
- CPU 和 GPU 裕量 API
媒体功能
Photo Picker 改进
- 照片选择器改进
专业视频编解码器
- 高级专业视频编解码器支持
色温与图像格式
- 精确色温调整
- UltraHDR 图像格式支持
连接性
测距 API
- 通用测距 API
- 安全 Wi-Fi 位置测距
- 配套设备存在检测
无障碍功能
- 改进的无障碍 API
- LE Audio 助听器功能:如环境音量控制
用户体验
电源按钮自定义
- 双击电源按钮可设置为打开 Google Wallet 而非相机
- 在设置 > 系统 > 手势中选择
HDR 截屏
- Android 16 Beta 2 支持真正的 HDR 截屏
竖排文本渲染
- 支持竖排文本渲染
自动主题应用图标
- 自动化的主题应用图标
开发者功能
更丰富的触觉反馈 API
- Richer haptics APIs
预测性返回导航
- Predictive back navigation support
双 API 发布计划
- 2025 年计划发布两个 Android API 版本
- Q4 2025:包含新开发者 API、功能更新、优化和错误修复的次要版本
2026 年规划
- 2026 年 2-3 月:Android 16 QPR3
- 2026 年 6 月:Android 17 稳定版预计发布
版本对比总结
设计演进
- Android 12: Material You(动态配色)
- Android 13: Material You 扩展(第三方图标主题化)
- Android 16: Material 3 Expressive(增强动画和模糊效果)
隐私安全演进
- Android 12: 隐私仪表盘、模糊位置、相机/麦克风开关
- Android 13: 通知权限、照片选择器、细粒度媒体权限
- Android 14: 数据共享通知、六位 PIN 增强
- Android 15: 盗窃检测锁、远程锁定、私密空间
- Android 16: Privacy Sandbox、密钥共享 API
相机功能演进
- Android 12: Camera2 扩展、HEVC 转码
- Android 13: HDR 视频捕获
- Android 14: Ultra HDR 图像格式
- Android 15: 夜间模式检测、混合曝光、弱光增强
- Android 16: 专业视频编解码器、精确色温
连接性演进
- Android 12: Wi-Fi P2P 并发
- Android 13: 蓝牙 LE Audio、Wi-Fi 7、MIDI 2.0
- Android 15: 卫星消息
- Android 16: 通用测距 API、安全 Wi-Fi 测距
大屏幕功能演进
- Android 13: 平板任务栏、手掌识别、分屏通知
- Android 14: 应用配对、拖放功能
- Android 15: 部分屏幕共享、固定任务栏
- Android 16: 桌面模式、强制应用自适应
性能优化演进
- Android 12: AppSearch、性能等级 API
- Android 13: JNI 2.5x 提升、OpenJDK 11
- Android 14: OpenJDK 17、Jetpack Compose
- Android 15: 虚拟 A/B 更新、ANGLE、ApplicationStartInfo
- Android 16: CPU/GPU 裕量 API、自适应刷新率
开发者 API Level 参考
| Android 版本 | API Level | 发布日期 | 代号 |
|---|---|---|---|
| Android 12 | 31 (12.0) / 32 (12L) | 2021-10-04 | Snow Cone |
| Android 13 | 33 | 2022-08-15 | Tiramisu |
| Android 14 | 34 | 2023-10-04 | Upside Down Cake |
| Android 15 | 35 | 2024-10-15 | Vanilla Ice Cream |
| Android 16 | 36 | 2025-06-10 | (未公布甜点代号) |
目录
Android 12 (API 31)
所有应用的行为变更
1. 启动画面(Splash Screen)
变更说明:系统为所有应用自动应用默认启动画面
迁移代码:
// 在 Activity 的 onCreate 之前安装 splash screen
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// 安装 splash screen
val splashScreen = installSplashScreen()
// 可选:自定义退出动画
splashScreen.setOnExitAnimationListener { splashScreenView ->
val slideUp = ObjectAnimator.ofFloat(
splashScreenView,
View.TRANSLATION_Y,
0f,
-splashScreenView.height.toFloat()
)
slideUp.interpolator = AnticipateInterpolator()
slideUp.duration = 200L
slideUp.doOnEnd { splashScreenView.remove() }
slideUp.start()
}
super.onCreate(savedInstanceState)
}
}
依赖:
dependencies {
implementation "androidx.core:core-splashscreen:1.0.0"
}
2. 过度滚动效果(Overscroll Effect)
变更说明:新的"拉伸和反弹"视觉效果取代之前的发光效果
保持旧效果:
<!-- 在 values-v31/themes.xml 中 -->
<style name="AppTheme" parent="Theme.MaterialComponents">
<item name="android:enableEdgeToEdge">false</item>
</style>
3. Web Intent 解析
变更说明:Web Intent 现在需要域名批准或用户设置
App Links 配置:
<!-- AndroidManifest.xml -->
<activity android:name=".MyActivity">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https"
android:host="www.example.com" />
</intent-filter>
</activity>
assetlinks.json(放在服务器 https://www.example.com/.well-known/assetlinks.json):
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.app",
"sha256_cert_fingerprints": [
"14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"
]
}
}]
4. 剪贴板访问通知
变更说明:应用访问剪贴板时显示 Toast 通知
无需代码变更,但可以通过以下方式隐藏敏感内容:
// Android 13+ 支持
val clip = ClipData.newPlainText("label", "Sensitive data")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
clip.description.extras = PersistableBundle().apply {
putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
}
}
clipboardManager.setPrimaryClip(clip)
5. 电池优化中的位置访问
变更说明:前台位置访问在省电模式下继续工作
检查代码:
val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
if (powerManager.isPowerSaveMode) {
// 前台服务仍可访问位置
// 但后台访问仍受限
}
针对 API 31+ 的行为变更
1. 组件导出声明(必须)
变更说明:带有 Intent Filter 的组件必须显式声明 android:exported
错误示例:
<!-- 会导致安装失败 -->
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
正确示例:
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name=".MyReceiver"
android:exported="false">
<intent-filter>
<action android:name="com.example.ACTION" />
</intent-filter>
</receiver>
2. 蓝牙权限细化
变更说明:新增细粒度蓝牙权限,不再需要位置权限
旧代码:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
新代码:
<!-- 扫描设备 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<!-- 广播 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<!-- 连接设备 -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- 如果支持旧版本 -->
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
运行时请求:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
requestPermissions(
arrayOf(
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT
),
REQUEST_CODE
)
} else {
requestPermissions(
arrayOf(
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN,
Manifest.permission.ACCESS_FINE_LOCATION
),
REQUEST_CODE
)
}
3. 精确位置与模糊位置
变更说明:用户可选择提供精确或模糊位置
清单配置:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
检查授权精度:
fun checkLocationAccuracy() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
when {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED -> {
// 精确位置权限
Log.d(TAG, "Precise location access granted")
}
ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED -> {
// 仅模糊位置权限
Log.d(TAG, "Approximate location access granted")
}
else -> {
// 无位置权限
requestLocationPermission()
}
}
}
}
4. 前台服务启动限制
变更说明:后台应用不能启动前台服务(少数例外情况除外)
异常情况:
fun startForegroundServiceIfAllowed() {
val intent = Intent(this, MyForegroundService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
try {
startForegroundService(intent)
} catch (e: ForegroundServiceStartNotAllowedException) {
// 应用不在允许启动前台服务的状态
// 使用替代方案,如 WorkManager
scheduleWorkWithWorkManager()
}
} else {
startForegroundService(intent)
}
}
允许的情况:
- 应用具有可见 Activity
- 应用在前台
- 从前台应用的 PendingIntent 启动
- 从系统广播接收器启动(如 BOOT_COMPLETED)
5. 精确闹钟权限
变更说明:设置精确闹钟需要 SCHEDULE_EXACT_ALARM 权限
清单配置:
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
使用代码:
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
when {
alarmManager.canScheduleExactAlarms() -> {
// 可以设置精确闹钟
alarmManager.setExact(
AlarmManager.RTC_WAKEUP,
triggerTime,
pendingIntent
)
}
else -> {
// 引导用户到设置页面
val intent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM)
startActivity(intent)
}
}
} else {
alarmManager.setExact(
AlarmManager.RTC_WAKEUP,
triggerTime,
pendingIntent
)
}
6. 通知蹦床限制
变更说明:禁止从服务/广播接收器通过通知启动 Activity
错误示例:
// 不再允许
class MyService : Service() {
fun showNotification() {
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this, 0, intent, PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentIntent(pendingIntent)
.build()
startForeground(NOTIFICATION_ID, notification)
}
}
正确示例:
// 直接启动 Activity
class MyService : Service() {
fun showNotification() {
val intent = Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
val pendingIntent = PendingIntent.getActivity(
this, 0, intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentIntent(pendingIntent)
.build()
startForeground(NOTIFICATION_ID, notification)
}
}
7. 自定义通知
变更说明:通知使用标准系统模板,自定义视图尺寸减小
适配代码:
val customView = RemoteViews(packageName, R.layout.notification_collapsed)
val customBigView = RemoteViews(packageName, R.layout.notification_expanded)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setCustomContentView(customView) // 折叠视图
.setCustomBigContentView(customBigView) // 展开视图
.setStyle(NotificationCompat.DecoratedCustomViewStyle())
.build()
布局尺寸限制(Android 12+):
- 折叠视图:48dp 高度
- 展开视图:最大 256dp 高度
Android 13 (API 33)
所有应用的行为变更
1. 任务管理器变更
变更说明:用户可在任务管理器中停止具有前台服务的应用
无需代码变更,但建议监听服务停止:
class MyForegroundService : Service() {
override fun onDestroy() {
super.onDestroy()
// 清理资源
Log.d(TAG, "Service stopped by user from Task Manager")
// 保存状态,以便用户重新打开应用时恢复
}
}
2. JobScheduler 改进
变更说明:改进预取作业处理
使用建议:
val jobScheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
val jobInfo = JobInfo.Builder(JOB_ID, componentName)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setPrefetch(true) // 标记为预取作业
.build()
jobScheduler.schedule(jobInfo)
3. 前台服务通知关闭
变更说明:用户可关闭与前台服务关联的通知
保持通知可见:
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("Foreground Service")
.setOngoing(true) // 标记为持续通知
.build()
startForeground(NOTIFICATION_ID, notification)
4. 限制应用 Standby Bucket
变更说明:受限存储桶的新限制
检查存储桶状态:
val usageStatsManager = getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
@RequiresApi(Build.VERSION_CODES.P)
fun checkStandbyBucket() {
val bucket = usageStatsManager.appStandbyBucket
when (bucket) {
UsageStatsManager.STANDBY_BUCKET_ACTIVE -> {
// 活跃:应用正在使用或最近使用过
}
UsageStatsManager.STANDBY_BUCKET_WORKING_SET -> {
// 工作集:应用经常使用
}
UsageStatsManager.STANDBY_BUCKET_FREQUENT -> {
// 频繁:应用经常使用但不是每天
}
UsageStatsManager.STANDBY_BUCKET_RARE -> {
// 罕见:不经常使用的应用
}
UsageStatsManager.STANDBY_BUCKET_RESTRICTED -> {
// 受限:最低优先级(Android 12+)
// 严重限制后台作业、闹钟和网络访问
}
}
}
针对 API 33+ 的行为变更
1. 通知运行时权限
变更说明:发送通知需要运行时权限
清单配置:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
请求权限:
class MainActivity : AppCompatActivity() {
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
// 权限已授予
sendNotification()
} else {
// 权限被拒绝
// 说明通知的重要性或提供替代方案
}
}
private fun checkNotificationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
when {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED -> {
sendNotification()
}
shouldShowRequestPermissionRationale(
Manifest.permission.POST_NOTIFICATIONS
) -> {
// 显示解释对话框
showPermissionRationale()
}
else -> {
requestPermissionLauncher.launch(
Manifest.permission.POST_NOTIFICATIONS
)
}
}
} else {
// Android 12 及以下无需权限
sendNotification()
}
}
}
前台服务通知特殊处理:
// 即使没有通知权限,前台服务也会在任务管理器中显示
class MyService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification = createNotification()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// Android 13+: 如果没有通知权限,通知不会在通知栏显示
// 但会在任务管理器中显示
startForeground(NOTIFICATION_ID, notification)
} else {
startForeground(NOTIFICATION_ID, notification)
}
return START_STICKY
}
}
2. 细粒度媒体权限
变更说明:替换 READ_EXTERNAL_STORAGE 为特定媒体权限
清单配置:
<!-- 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" />
<!-- Android 12 及以下 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
请求权限:
private val requestMediaPermissions = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
permissions.entries.forEach { entry ->
val permission = entry.key
val isGranted = entry.value
when (permission) {
Manifest.permission.READ_MEDIA_IMAGES -> {
if (isGranted) loadImages()
}
Manifest.permission.READ_MEDIA_VIDEO -> {
if (isGranted) loadVideos()
}
Manifest.permission.READ_MEDIA_AUDIO -> {
if (isGranted) loadAudio()
}
}
}
}
fun requestMediaAccess() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
requestMediaPermissions.launch(
arrayOf(
Manifest.permission.READ_MEDIA_IMAGES,
Manifest.permission.READ_MEDIA_VIDEO,
Manifest.permission.READ_MEDIA_AUDIO
)
)
} else {
requestMediaPermissions.launch(
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)
)
}
}
3. 附近 Wi-Fi 设备权限
变更说明:访问附近 Wi-Fi 设备需要新权限
清单配置:
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
android:usesPermissionFlags="neverForLocation" />
<!-- 如果确实需要位置派生 -->
<!-- <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" /> -->
请求权限:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
requestPermissions(
arrayOf(Manifest.permission.NEARBY_WIFI_DEVICES),
REQUEST_CODE
)
}
4. 后台身体传感器权限
变更说明:后台访问身体传感器需要单独权限
清单配置:
<uses-permission android:name="android.permission.BODY_SENSORS" />
<uses-permission android:name="android.permission.BODY_SENSORS_BACKGROUND" />
请求步骤:
// 步骤 1: 先请求前台权限
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.BODY_SENSORS
) != PackageManager.PERMISSION_GRANTED
) {
requestPermissions(
arrayOf(Manifest.permission.BODY_SENSORS),
REQUEST_BODY_SENSORS
)
}
// 步骤 2: 前台权限授予后,再请求后台权限
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (requestCode == REQUEST_BODY_SENSORS) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 前台权限已授予,请求后台权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
requestPermissions(
arrayOf(Manifest.permission.BODY_SENSORS_BACKGROUND),
REQUEST_BODY_SENSORS_BACKGROUND
)
}
}
}
}
5. 媒体控制变更
变更说明:媒体控制从 PlaybackState 派生,而非通知操作
旧代码(不推荐):
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.addAction(R.drawable.ic_previous, "Previous", previousPendingIntent)
.addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)
.addAction(R.drawable.ic_next, "Next", nextPendingIntent)
.build()
新代码(推荐):
// 使用 MediaSession
val mediaSession = MediaSessionCompat(this, TAG)
mediaSession.setCallback(object : MediaSessionCompat.Callback() {
override fun onPlay() {
// 处理播放
}
override fun onPause() {
// 处理暂停
}
override fun onSkipToNext() {
// 下一首
}
override fun onSkipToPrevious() {
// 上一首
}
})
// 设置 PlaybackState
val playbackState = PlaybackStateCompat.Builder()
.setActions(
PlaybackStateCompat.ACTION_PLAY or
PlaybackStateCompat.ACTION_PAUSE or
PlaybackStateCompat.ACTION_SKIP_TO_NEXT or
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or
PlaybackStateCompat.ACTION_STOP
)
.setState(PlaybackStateCompat.STATE_PLAYING, position, 1.0f)
.build()
mediaSession.setPlaybackState(playbackState)
// 通知会自动显示最多 5 个操作按钮
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setStyle(
androidx.media.app.NotificationCompat.MediaStyle()
.setMediaSession(mediaSession.sessionToken)
.setShowActionsInCompactView(0, 1, 2) // 紧凑视图显示的操作索引
)
.build()
6. 蓝牙 API 弃用
变更说明:BluetoothAdapter.enable() 和 disable() 已弃用
旧代码:
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
bluetoothAdapter.enable() //已弃用
新代码:
// 使用 Intent 请求用户启用蓝牙
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
} else {
bluetoothAdapter.enable()
}
Android 14 (API 34)
所有应用的行为变更
1. 精确闹钟权限变更
变更说明:新安装的应用不再预授予精确闹钟权限
检查和请求:
val alarmManager = getSystemService(AlarmManager::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (!alarmManager.canScheduleExactAlarms()) {
// 引导用户授权
Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM).also {
it.data = Uri.parse("package:$packageName")
startActivity(it)
}
} else {
// 可以设置精确闹钟
scheduleExactAlarm()
}
}
2. 后台进程限制
变更说明:应用只能终止自己的后台进程
错误代码:
val activityManager = getSystemService(ActivityManager::class.java)
// Android 14+ 无效(只能终止自己的进程)
activityManager.killBackgroundProcesses("com.other.app")
正确代码:
// 只终止自己的进程
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
// 建议让系统管理进程生命周期
finishAndRemoveTask()
} else {
val activityManager = getSystemService(ActivityManager::class.java)
activityManager.killBackgroundProcesses(packageName)
}
3. 通知可关闭性
变更说明:大多数通知现在可被用户关闭
例外情况(不可关闭):
// 电话通知
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_call)
.setCategory(NotificationCompat.CATEGORY_CALL)
.setOngoing(true)
.setFullScreenIntent(pendingIntent, true)
.build()
// 媒体通知
val mediaNotification = NotificationCompat.Builder(this, CHANNEL_ID)
.setStyle(MediaStyle().setMediaSession(mediaSession.sessionToken))
.build()
// 企业设备策略通知(需设备所有者)
4. 字体缩放到 200%
变更说明:系统支持字体放大到 200%
测试和适配:
// 在 Activity 中限制字体缩放(不推荐)
override fun attachBaseContext(newBase: Context) {
val config = Configuration(newBase.resources.configuration)
if (config.fontScale > 1.3f) {
config.fontScale = 1.3f
}
super.attachBaseContext(newBase.createConfigurationContext(config))
}
推荐做法:使用 sp 单位并测试 UI
<!-- 使用 sp 而非 dp -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:maxLines="2"
android:ellipsize="end" />
5. 蓝牙 MTU 协商
变更说明:首个 GATT 客户端请求 MTU 时设为 517 字节
代码示例:
bluetoothGatt.requestMtu(517)
// 监听 MTU 变更
override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, "MTU changed to: $mtu")
// 使用新的 MTU 大小
}
}
针对 API 34+ 的行为变更
1. 前台服务类型必需
变更说明:每个前台服务必须至少指定一个类型
清单配置:
<service
android:name=".MusicPlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="false" />
<service
android:name=".LocationTrackingService"
android:foregroundServiceType="location"
android:exported="false" />
<service
android:name=".DownloadService"
android:foregroundServiceType="dataSync"
android:exported="false" />
可用类型:
- camera
- connectedDevice
- dataSync
- health
- location
- mediaPlayback
- mediaProjection
- microphone
- phoneCall
- remoteMessaging
- shortService
- specialUse
- systemExempted
运行时声明:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startForeground(
NOTIFICATION_ID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
)
} else {
startForeground(NOTIFICATION_ID, notification)
}
组合类型:
<service
android:name=".MultiPurposeService"
android:foregroundServiceType="location|camera"
android:exported="false" />
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startForeground(
NOTIFICATION_ID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION or
ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA
)
}
2. 照片选择器增强(部分访问)
变更说明:用户可选择特定照片/视频授予访问
使用照片选择器(推荐):
// 使用 Photo Picker
val pickMedia = registerForActivityResult(
ActivityResultContracts.PickVisualMedia()
) { uri ->
if (uri != null) {
Log.d(TAG, "Selected URI: uri")
// 使用选中的照片
}
}
// 单选
pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
// 多选
val pickMultipleMedia = registerForActivityResult(
ActivityResultContracts.PickMultipleVisualMedia(5)
) { uris ->
if (uris.isNotEmpty()) {
Log.d(TAG, "Selected{uris.size} items")
}
}
pickMultipleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageAndVideo))
检查部分访问状态:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
when {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.READ_MEDIA_IMAGES
) == PackageManager.PERMISSION_GRANTED -> {
// 完全访问
}
ContextCompat.checkSelfPermission(
this,
Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
) == PackageManager.PERMISSION_GRANTED -> {
// 部分访问
}
else -> {
// 无访问权限
}
}
}
3. Intent 安全性增强
变更说明:隐式和待定 Intent 更严格的规则
隐式 Intent 必须包含操作:
// 错误
val intent = Intent()
intent.setPackage("com.example.app")
startActivity(intent)
//正确
val intent = Intent(Intent.ACTION_VIEW)
intent.setPackage("com.example.app")
intent.data = Uri.parse("https://example.com")
startActivity(intent)
PendingIntent 必须可变或不可变:
// 错误(缺少标志)
val pendingIntent = PendingIntent.getActivity(
context, 0, intent, 0
)
// 正确
val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.getActivity(
context, 0, intent,
PendingIntent.FLAG_IMMUTABLE
)
} else {
PendingIntent.getActivity(
context, 0, intent,
0
)
}
// 如果需要可变
val mutablePendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.getActivity(
context, 0, intent,
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
} else {
PendingIntent.getActivity(
context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
}
4. 运行时注册广播接收器导出行为
变更说明:动态注册的接收器必须指定导出行为
代码示例:
val receiver = MyBroadcastReceiver()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// 导出的接收器(可接收其他应用广播)
registerReceiver(
receiver,
IntentFilter(ACTION_CUSTOM),
Context.RECEIVER_EXPORTED
)
// 非导出的接收器(仅接收自己应用广播)
registerReceiver(
receiver,
IntentFilter(ACTION_INTERNAL),
Context.RECEIVER_NOT_EXPORTED
)
} else {
registerReceiver(receiver, IntentFilter(ACTION_CUSTOM))
}
5. 动态加载代码限制
变更说明:动态加载的文件必须标记为只读
代码示例:
val dexFile = File(getCodeCacheDir(), "dynamic.dex")
// 下载或创建 dex 文件
downloadDexFile(dexFile)
// Android 14+ 必须设置为只读
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
dexFile.setReadOnly()
}
// 加载代码
val dexClassLoader = DexClassLoader(
dexFile.absolutePath,
codeCacheDir.absolutePath,
null,
classLoader
)
6. 后台启动 Activity 限制
变更说明:更严格的后台启动 Activity 限制
例外情况:
// 使用 PendingIntent(推荐)
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
0,
intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
// 在通知中使用
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentIntent(pendingIntent)
.build()
全屏 Intent 通知(需权限):
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
val notificationManager = getSystemService(NotificationManager::class.java)
if (!notificationManager.canUseFullScreenIntent()) {
// 引导用户授权
val intent = Intent(Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT)
intent.data = Uri.parse("package:$packageName")
startActivity(intent)
}
}
val fullScreenIntent = PendingIntent.getActivity(
this, 0, intent, PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setFullScreenIntent(fullScreenIntent, true)
.build()
7. OpenJDK 17 变更
正则表达式变更:
// 可能在 Android 14+ 失败
val pattern = Pattern.compile("\\w+")
// 某些 Unicode 字符行为可能改变
// 明确指定
val pattern = Pattern.compile("[a-zA-Z0-9_]+")
UUID 变更:
// Android 14 之前和之后 UUID.fromString() 的验证可能不同
try {
val uuid = UUID.fromString("invalid-uuid")
} catch (e: IllegalArgumentException) {
// Android 14+ 可能抛出异常
}
Android 15 (API 35)
所有应用的行为变更
1. 包停止状态变更
变更说明:应用进入停止状态时取消所有待定 Intent
检查强制停止状态:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
val am = getSystemService(ActivityManager::class.java)
val startInfo = am.getHistoricalProcessStartReasons(5)
startInfo.forEach { info ->
if (info.wasForceStopped()) {
Log.d(TAG, "App was force stopped")
// 处理强制停止后的逻辑
}
}
}
小部件行为:
// 应用强制停止后,小部件暂时禁用
// 用户下次打开应用时自动重新启用
class MyAppWidgetProvider : AppWidgetProvider() {
override fun onEnabled(context: Context) {
super.onEnabled(context)
// 应用重新启用后的初始化
Log.d(TAG, "Widget re-enabled after force stop")
}
}
2. 16 KB 页面大小支持
变更说明:支持 16 KB 内存页面大小的设备
检查原生库:
# 检查原生库的页面大小对齐
readelf -l libnative.so | grep LOAD
# 确保原生库正确对齐到 16 KB
NDK 配置(CMakeLists.txt):
# 设置最小 Android API
set(CMAKE_ANDROID_API 35)
# 添加 16KB 页面大小支持
set(CMAKE_EXE_LINKER_FLAGS "{CMAKE_EXE_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
set(CMAKE_SHARED_LINKER_FLAGS "{CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
Gradle 配置:
android {
defaultConfig {
ndk {
// 确保支持所有架构
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
}
}
3. Private Space 兼容性
变更说明:应用必须正确处理独立用户配置文件
检测 Private Space:
val userManager = getSystemService(UserManager::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
val isPrivateProfile = userManager.isPrivateProfile()
if (isPrivateProfile) {
// 在 Private Space 中运行
// 某些功能可能需要限制
Log.d(TAG, "Running in Private Space")
}
}
医疗应用建议:
// 医疗应用可能需要在 Private Space 中禁用某些功能
if (isPrivateProfile && isMedicalApp()) {
// 显示警告或限制功能
showPrivateSpaceWarning()
}
4. 敏感通知保护
变更说明:OTP 通知对不受信任的应用隐藏
发送 OTP 通知:
// 系统自动保护 OTP 通知
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("验证码")
.setContentText("您的验证码是: 123456")
.setPriority(NotificationCompat.PRIORITY_HIGH)
.build()
notificationManager.notify(OTP_NOTIFICATION_ID, notification)
// Android 15 会自动检测并保护包含 OTP 的通知
5. 屏幕共享内容保护
变更说明:屏幕共享时隐藏敏感内容
标记敏感内容:
// 方法 1: 在 Activity 级别
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
}
// 方法 2: 在 View 级别
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
sensitiveView.setScreenCaptureCallback(
mainExecutor,
object : ScreenCaptureCallback {
override fun onScreenCaptured() {
// 屏幕共享开始,隐藏敏感内容
sensitiveView.visibility = View.GONE
}
override fun onScreenCaptureEnded() {
// 屏幕共享结束,显示敏感内容
sensitiveView.visibility = View.VISIBLE
}
}
)
}
6. 最低 Target SDK 版本
变更说明:最低可安装 targetSdkVersion 从 23 提升到 24
影响:targetSdkVersion < 24 的应用无法安装
7. 后台网络访问限制
变更说明:后台网络访问更严格的生命周期限制
适配代码:
// 使用 WorkManager 进行后台网络请求
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
.setConstraints(constraints)
.build()
WorkManager.getInstance(context).enqueue(workRequest)
// Worker 实现
class MyWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
// 执行网络请求
performNetworkRequest()
Result.success()
} catch (e: Exception) {
Result.retry()
}
}
}
针对 API 35+ 的行为变更
1. 前台服务超时
变更说明:dataSync 和 mediaProcessing 类型服务有 6 小时限制
清单配置:
<service
android:name=".SyncService"
android:foregroundServiceType="dataSync"
android:exported="false" />
代码示例:
class SyncService : Service() {
private var startTime = 0L
private val maxDuration = 6 * 60 * 60 * 1000 // 6 小时
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startTime = System.currentTimeMillis()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
try {
startForeground(
NOTIFICATION_ID,
createNotification(),
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
)
} catch (e: ForegroundServiceStartNotAllowedException) {
// 超时或其他限制
Log.e(TAG, "Cannot start foreground service", e)
stopSelf()
return START_NOT_STICKY
}
} else {
startForeground(NOTIFICATION_ID, createNotification())
}
// 监控运行时长
handler.postDelayed({
if (System.currentTimeMillis() - startTime >= maxDuration) {
// 接近 6 小时限制,停止服务
stopSelf()
}
}, maxDuration)
return START_STICKY
}
}
替代方案:使用 WorkManager
// 对于长时间同步,使用 WorkManager
val syncWork = PeriodicWorkRequestBuilder<SyncWorker>(
6, TimeUnit.HOURS // 每 6 小时执行一次
).build()
WorkManager.getInstance(context).enqueue(syncWork)
2. BOOT_COMPLETED 广播限制
变更说明:从 BOOT_COMPLETED 启动前台服务受限
错误做法:
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
// ❌ Android 15+ 可能失败
val serviceIntent = Intent(context, MyForegroundService::class.java)
context.startForegroundService(serviceIntent)
}
}
}
正确做法:
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
// 使用 WorkManager 或 JobScheduler
val workRequest = OneTimeWorkRequestBuilder<BootWorker>()
.build()
WorkManager.getInstance(context).enqueue(workRequest)
} else {
context.startForegroundService(
Intent(context, MyForegroundService::class.java)
)
}
}
}
}
3. 音频焦点限制
变更说明:必须是顶部应用或运行前台服务才能请求音频焦点
代码示例:
val audioManager = getSystemService(AudioManager::class.java)
val focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).apply {
setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build()
)
setOnAudioFocusChangeListener { focusChange ->
when (focusChange) {
AudioManager.AUDIOFOCUS_GAIN -> {
// 获得音频焦点
resumePlayback()
}
AudioManager.AUDIOFOCUS_LOSS -> {
// 永久失去音频焦点
pausePlayback()
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
// 暂时失去音频焦点
pausePlayback()
}
}
}
}.build()
// 请求音频焦点
val result = audioManager.requestAudioFocus(focusRequest)
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// 成功获得焦点
startPlayback()
} else {
// Android 15+ 如果不在前台可能失败
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
// 启动前台服务然后请求焦点
startForegroundService()
}
}
4. Edge-to-Edge 强制执行
变更说明:默认强制执行边到边布局
适配代码:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
// 启用边到边
WindowCompat.setDecorFitsSystemWindows(window, false)
}
setContent {
MyAppTheme {
Scaffold(
modifier = Modifier.fillMaxSize()
) { innerPadding ->
// 使用 innerPadding 避免与系统栏重叠
Content(modifier = Modifier.padding(innerPadding))
}
}
}
}
}
处理系统栏插入:
ViewCompat.setOnApplyWindowInsetsListener(mainView) { view, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
view.updatePadding(
left = systemBars.left,
top = systemBars.top,
right = systemBars.right,
bottom = systemBars.bottom
)
insets
}
5. elegantTextHeight 默认值变更
变更说明:TextView 的 elegantTextHeight 默认为 true
影响:文本高度可能增加
如需保持旧行为:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elegantTextHeight="false"
android:text="@string/text" />
或在代码中:
textView.isElegantTextHeight = false
Android 16 (API 36)
所有应用的行为变更
1. JobScheduler 配额优化
变更说明:作业执行运行时配额取决于应用待机存储桶
代码示例:
val jobScheduler = getSystemService(JobScheduler::class.java)
// 检查当前待机存储桶
val usageStatsManager = getSystemService(UsageStatsManager::class.java)
val bucket = usageStatsManager.appStandbyBucket
val constraints = JobInfo.Builder(JOB_ID, componentName)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.apply {
// 根据存储桶调整策略
when (bucket) {
UsageStatsManager.STANDBY_BUCKET_ACTIVE -> {
// 活跃桶:更高配额
setMinimumLatency(0)
}
UsageStatsManager.STANDBY_BUCKET_WORKING_SET -> {
setMinimumLatency(2 * 60 * 1000) // 2 分钟
}
UsageStatsManager.STANDBY_BUCKET_FREQUENT -> {
setMinimumLatency(10 * 60 * 1000) // 10 分钟
}
UsageStatsManager.STANDBY_BUCKET_RARE -> {
setMinimumLatency(60 * 60 * 1000) // 1 小时
}
UsageStatsManager.STANDBY_BUCKET_RESTRICTED -> {
// 受限桶:非常有限的配额
setMinimumLatency(24 * 60 * 60 * 1000) // 24 小时
}
}
}
.build()
jobScheduler.schedule(constraints)
前台服务作业:
// 使用前台服务运行的作业也遵循运行时配额
class MyJobService : JobService() {
override fun onStartJob(params: JobParameters?): Boolean {
if (Build.VERSION.SDK_INT >= 36) {
// Android 16+ 即使在前台服务中也有配额限制
// 确保作业在配额内完成
doWorkWithTimeout(params)
} else {
doWork(params)
}
return true
}
private fun doWorkWithTimeout(params: JobParameters?) {
// 设置超时以避免超出配额
val timeout = 10 * 60 * 1000L // 10 分钟
handler.postDelayed({
jobFinished(params, true) // 需要重新调度
}, timeout)
}
}
2. 无障碍公告弃用
变更说明:不推荐使用 announceForAccessibility()
旧代码(不推荐):
// 弃用
view.announceForAccessibility("内容已更新")
新代码(推荐):
// 使用 Live Region
view.accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_POLITE
// 更新内容时自动宣布
textView.text = "内容已更新"
// 或使用 AccessibilityPaneTitle
view.accessibilityPaneTitle = "结果列表"
复杂场景:
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:accessibilityLiveRegion="polite"
android:accessibilityPaneTitle="搜索结果" />
3. Intent 重定向安全
变更说明:默认防止 Intent 重定向攻击
影响代码:
// 场景:从一个 Activity 传递 Intent 到另一个
val forwardIntent = intent.getParcelableExtra<Intent>("forward_intent")
if (forwardIntent != null) {
if (Build.VERSION.SDK_INT >= 36) {
// Android 16 会验证 Intent 安全性
try {
startActivity(forwardIntent)
} catch (e: SecurityException) {
// Intent 重定向被阻止
Log.e(TAG, "Intent redirection blocked", e)
}
} else {
startActivity(forwardIntent)
}
}
如需退出保护(不推荐):
if (Build.VERSION.SDK_INT >= 36) {
// 仅在绝对必要且验证过安全性时使用
val intent = Intent()
intent.removeLaunchSecurityProtection()
startActivity(intent)
}
安全做法:
// 验证 Intent 来源和目标
val forwardIntent = intent.getParcelableExtra<Intent>("forward_intent")
if (forwardIntent != null) {
// 验证目标包名
val allowedPackages = listOf("com.example.trusted.app")
if (forwardIntent.`package` in allowedPackages) {
startActivity(forwardIntent)
} else {
// 拒绝不受信任的 Intent
Log.w(TAG, "Blocked untrusted intent redirection")
}
}
4. 蓝牙绑定丢失处理
变更说明:改进蓝牙设备绑定丢失的系统处理
监听绑定状态:
val bondStateReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
BluetoothDevice.ACTION_BOND_STATE_CHANGED -> {
val device = intent.getParcelableExtra<BluetoothDevice>(
BluetoothDevice.EXTRA_DEVICE
)
val bondState = intent.getIntExtra(
BluetoothDevice.EXTRA_BOND_STATE,
BluetoothDevice.ERROR
)
when (bondState) {
BluetoothDevice.BOND_BONDED -> {
Log.d(TAG, "Device bonded: {device?.address}")
}
BluetoothDevice.BOND_NONE -> {
// Android 16 会显示用户对话框并保留本地绑定信息
Log.d(TAG, "Bond lost:{device?.address}")
handleBondLoss(device)
}
}
}
}
}
}
registerReceiver(
bondStateReceiver,
IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
)
5. 16 KB 页面兼容模式
变更说明:为 4 KB 页面应用添加兼容模式
清单配置:
<application
android:pageSizeCompat="true"
... >
<!--
android:pageSizeCompat="true" 启用兼容模式
在 16KB 设备上运行 4KB 页面应用时显示对话框
-->
</application>
检测页面大小:
if (Build.VERSION.SDK_INT >= 36) {
val pageSize = Process.getPageSize()
Log.d(TAG, "Device page size: $pageSize bytes")
if (pageSize == 16384) {
// 16 KB 页面设备
// 如果应用原生库未正确对齐,会显示警告对话框
}
}
6. 配套设备配对超时
变更说明:配套应用不再直接收到发现超时通知
旧代码:
// Android 16 之前
override fun onDeviceFound(device: BluetoothDevice) {
// 处理设备发现
}
override fun onDiscoveryTimeout() {
// 直接收到超时通知
showTimeoutMessage()
}
新代码:
// Android 16+
// 系统显示超时对话框,应用间接处理
val deviceManager = getSystemService(CompanionDeviceManager::class.java)
val request = AssociationRequest.Builder()
.addDeviceFilter(
BluetoothDeviceFilter.Builder()
.setNamePattern(Pattern.compile("My Device"))
.build()
)
.build()
deviceManager.associate(
request,
object : CompanionDeviceManager.Callback() {
override fun onDeviceFound(chooserLauncher: IntentSender) {
startIntentSenderForResult(
chooserLauncher,
REQUEST_CODE,
null, 0, 0, 0
)
}
override fun onFailure(error: CharSequence?) {
// 处理失败(包括超时)
// 但不会直接收到超时回调
Log.e(TAG, "Association failed: $error")
}
},
null
)
针对 API 36+ 的行为变更
1. Edge-to-Edge 强制执行(无法退出)
变更说明:应用无法再选择退出边到边布局
必须适配:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Android 16+ 强制边到边
enableEdgeToEdge()
setContent {
MyAppTheme {
Scaffold(
modifier = Modifier.fillMaxSize()
) { innerPadding ->
MainContent(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
)
}
}
}
}
}
处理系统栏:
<!-- 传统 View 系统 -->
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<!-- Content -->
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
2. 预测性返回手势默认启用
变更说明:返回系统动画默认启用
迁移到新 API:
// 使用 OnBackPressedCallback
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requireActivity().onBackPressedDispatcher.addCallback(this) {
// 处理返回
if (canGoBack()) {
navigateBack()
} else {
isEnabled = false
requireActivity().onBackPressedDispatcher.onBackPressed()
}
}
}
}
支持预测性返回动画:
class MyActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
onBackInvokedDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT
) {
// 执行返回操作
finish()
}
}
}
}
暂时退出(不推荐):
<application
android:enableOnBackInvokedCallback="false"
... >
<!-- 仅临时使用,应尽快迁移 -->
</application>
3. 大屏幕方向和纵横比限制移除
变更说明:≥600dp 屏幕忽略方向和纵横比限制
受影响的配置:
<!-- 在大屏幕上被忽略 -->
<activity
android:name=".MainActivity"
android:screenOrientation="portrait"
android:resizeableActivity="false"
... />
适配方案:
// 适配不同屏幕尺寸
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val windowSizeClass = calculateWindowSizeClass(this)
MyAppTheme {
when (windowSizeClass.widthSizeClass) {
WindowWidthSizeClass.Compact -> {
// 手机布局
PhoneLayout()
}
WindowWidthSizeClass.Medium -> {
// 折叠屏展开或小平板
TabletLayout()
}
WindowWidthSizeClass.Expanded -> {
// 大平板或桌面
DesktopLayout()
}
}
}
}
}
}
依赖:
dependencies {
implementation "androidx.compose.material3:material3-window-size-class:1.2.0"
}
暂时退出(2025 年可用,2026 年移除):
<application
android:forceMaxAspectRatioOnLargeScreens="true"
... >
<!--
API 36: 可退出(显示警告)
API 37: 无法退出
-->
</application>
4. 健康与健身细粒度权限
变更说明:替换 BODY_SENSORS 为特定权限
清单配置:
<!-- 新的细粒度权限 -->
<uses-permission android:name="android.permission.READ_HEART_RATE" />
<uses-permission android:name="android.permission.READ_SLEEP" />
<uses-permission android:name="android.permission.READ_STEPS" />
<uses-permission android:name="android.permission.READ_EXERCISE" />
<!-- 保持向后兼容 -->
<uses-permission android:name="android.permission.BODY_SENSORS"
android:maxSdkVersion="35" />
请求权限:
private val requestHealthPermissions = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val heartRateGranted = permissions[Manifest.permission.READ_HEART_RATE] ?: false
val sleepGranted = permissions[Manifest.permission.READ_SLEEP] ?: false
if (heartRateGranted) {
startHeartRateMonitoring()
}
if (sleepGranted) {
loadSleepData()
}
}
fun requestPermissions() {
if (Build.VERSION.SDK_INT >= 36) {
requestHealthPermissions.launch(
arrayOf(
Manifest.permission.READ_HEART_RATE,
Manifest.permission.READ_SLEEP,
Manifest.permission.READ_STEPS
)
)
} else {
requestHealthPermissions.launch(
arrayOf(Manifest.permission.BODY_SENSORS)
)
}
}
5. 本地网络访问权限
变更说明:访问本地网络需要新的运行时权限
清单配置:
<uses-permission android:name="android.permission.ACCESS_LOCAL_NETWORK" />
请求权限:
private val requestLocalNetworkPermission = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
discoverLocalDevices()
} else {
showLocalNetworkPermissionRationale()
}
}
fun checkLocalNetworkPermission() {
if (Build.VERSION.SDK_INT >= 36) {
when {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_LOCAL_NETWORK
) == PackageManager.PERMISSION_GRANTED -> {
discoverLocalDevices()
}
else -> {
requestLocalNetworkPermission.launch(
Manifest.permission.ACCESS_LOCAL_NETWORK
)
}
}
} else {
// 旧版本无需此权限
discoverLocalDevices()
}
}
6. Photo Picker 改进
变更说明:照片选择器预选应用拥有的照片
代码示例:
// Photo Picker 会自动预选应用之前访问过的照片
val pickMedia = registerForActivityResult(
ActivityResultContracts.PickVisualMedia()
) { uri ->
if (uri != null) {
// 使用选中的照片
displayPhoto(uri)
// 保存 URI 以便后续访问
savePhotoUri(uri)
}
}
// 启动照片选择器
pickMedia.launch(
PickVisualMediaRequest(
ActivityResultContracts.PickVisualMedia.ImageOnly
)
)
7. 蓝牙绑定丢失 Intent
变更说明:新的绑定丢失和加密变更 Intent
注册接收器:
val bondLossReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(
BluetoothDevice.EXTRA_DEVICE,
BluetoothDevice::class.java
)
} else {
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
}
when (intent.action) {
"android.bluetooth.device.action.BOND_LOST" -> {
// 处理绑定丢失
Log.w(TAG, "Bond lost with device: {device?.address}")
handleBondLoss(device)
}
"android.bluetooth.device.action.ENCRYPTION_CHANGED" -> {
// 处理加密变更
val encrypted = intent.getBooleanExtra("encrypted", false)
Log.d(TAG, "Encryption changed:encrypted")
}
}
}
}
if (Build.VERSION.SDK_INT >= 36) {
val filter = IntentFilter().apply {
addAction("android.bluetooth.device.action.BOND_LOST")
addAction("android.bluetooth.device.action.ENCRYPTION_CHANGED")
}
registerReceiver(bondLossReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
}
迁移检查清单
Android 12 迁移
- 更新
android:exported所有带 Intent Filter 的组件 - 迁移蓝牙权限到细粒度权限
- 适配精确位置和模糊位置
- 处理前台服务启动限制
- 请求精确闹钟权限
- 移除通知蹦床
- 适配 Splash Screen API
- 测试自定义通知布局
Android 13 迁移
- 请求 POST_NOTIFICATIONS 权限
- 替换 READ_EXTERNAL_STORAGE 为细粒度媒体权限
- 适配 NEARBY_WIFI_DEVICES 权限
- 请求 BODY_SENSORS_BACKGROUND 权限
- 使用 MediaSession 控制媒体播放
- 移除 BluetoothAdapter.enable()/disable() 调用
- 处理任务管理器停止前台服务
Android 14 迁移
- 为所有前台服务指定类型
- 使用照片选择器
- 更新 Intent 和 PendingIntent 使用
- 更新动态注册广播接收器导出行为
- 标记动态加载代码文件为只读
- 适配后台启动 Activity 限制
- 请求 USE_FULL_SCREEN_INTENT 权限
- 测试字体缩放到 200%
- 检查 OpenJDK 17 兼容性
Android 15 迁移
- 处理包停止状态变更
- 适配 16 KB 页面大小(原生代码)
- 支持 Private Space
- 适配前台服务超时(dataSync/mediaProcessing)
- 移除从 BOOT_COMPLETED 启动前台服务
- 适配音频焦点限制
- 启用 Edge-to-Edge 布局
- 测试 elegantTextHeight 变更
- 更新后台网络访问使用 WorkManager
Android 16 迁移
- 适配 JobScheduler 配额限制
- 使用 Live Region 替代 announceForAccessibility()
- 处理 Intent 重定向安全
- 监听蓝牙绑定丢失
- 配置 16 KB 页面兼容性
- 强制 Edge-to-Edge(无法退出)
- 启用预测性返回手势
- 适配大屏幕自适应布局
- 细粒度健康健身权限
- 请求本地网络访问权限
- 适配新的蓝牙 Intent
参考资源
文档更新日期: 2026-02-13