Android - API31到36新特性和行为变更详解(持续更新)

文章目录[x]
  1. 1:Android 12 (API Level 31-32)
  2. 1.1:设计系统
  3. 1.2:隐私与安全
  4. 1.3:用户界面
  5. 1.4:媒体与音频
  6. 1.5:性能与连接
  7. 1.6:开发者功能
  8. 2:Android 13 (API Level 33)
  9. 2.1:个性化与用户界面
  10. 2.2:隐私与安全
  11. 2.3:连接性与音频
  12. 2.4:用户体验
  13. 2.5:平板与大屏幕
  14. 2.6:性能优化
  15. 3:Android 14 (API Level 34)
  16. 3.1:个性化定制
  17. 3.2:相机与媒体
  18. 3.3:隐私与安全
  19. 3.4:电池与性能
  20. 3.5:无障碍功能
  21. 3.6:健康与安全
  22. 3.7:大屏幕功能
  23. 3.8:开发者功能
  24. 4:Android 15 (API Level 35)
  25. 4.1:安全与防盗
  26. 4.2:Private Space(私密空间)
  27. 4.3:相机增强
  28. 4.4:多任务与大屏幕
  29. 4.5:连接性
  30. 4.6:用户界面
  31. 4.7:其他功能
  32. 4.8:技术改进
  33. 5:Android 16 (API Level 36)
  34. 5.1:设计系统
  35. 5.2:通知与实时更新
  36. 5.3:桌面模式
  37. 5.4:Linux 终端
  38. 5.5:大屏幕改进
  39. 5.6:相机增强
  40. 5.7:隐私与安全
  41. 5.8:性能增强
  42. 5.9:媒体功能
  43. 5.10:连接性
  44. 5.11:无障碍功能
  45. 5.12:用户体验
  46. 5.13:开发者功能
  47. 5.14:2026 年规划
  48. 6:版本对比总结
  49. 6.1:设计演进
  50. 6.2:隐私安全演进
  51. 6.3:相机功能演进
  52. 6.4:连接性演进
  53. 6.5:大屏幕功能演进
  54. 6.6:性能优化演进
  55. 7:开发者 API Level 参考
  56. 8:目录
  57. 9:Android 12 (API 31)
  58. 9.1:所有应用的行为变更
  59. 9.2:针对 API 31+ 的行为变更
  60. 10:Android 13 (API 33)
  61. 10.1:所有应用的行为变更
  62. 10.2:针对 API 33+ 的行为变更
  63. 11:Android 14 (API 34)
  64. 11.1:所有应用的行为变更
  65. 11.2:针对 API 34+ 的行为变更
  66. 12:Android 15 (API 35)
  67. 12.1:所有应用的行为变更
  68. 12.2:针对 API 35+ 的行为变更
  69. 13:Android 16 (API 36)
  70. 13.1:所有应用的行为变更
  71. 13.2:针对 API 36+ 的行为变更
  72. 14:迁移检查清单
  73. 14.1:Android 12 迁移
  74. 14.2:Android 13 迁移
  75. 14.3:Android 14 迁移
  76. 14.4:Android 15 迁移
  77. 14.5:Android 16 迁移
  78. 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. 前台服务超时

变更说明dataSyncmediaProcessing 类型服务有 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

点赞

发表评论

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像

Title - Artist
0:00