一张表看懂 Android 8-15 所有适配要点
Android 系统每年发布一个新版本,每个版本都有行为变更和新的限制。本文以表格形式汇总 Android 8 到 Android 15 所有需要适配的核心要点,方便开发者快速查阅。建议收藏,新版本发布后会持续更新。
一、快速查阅总表
| Android 版本 | API | 发布年份 | 必须适配的核心变更 | 适配优先级 |
|---|---|---|---|---|
| 8.0 Oreo | 26 | 2017 | 通知渠道、后台执行限制 | 中(旧设备) |
| 9.0 Pie | 28 | 2018 | Apache HTTP 移除、前台服务权限 | 中(旧设备) |
| 10 Q | 29 | 2019 | 分区存储、后台启动 Activity 限制 | 高(大量设备) |
| 11 R | 30 | 2020 | 包可见性、单次权限 | 高 |
| 12 S | 31 | 2021 | SplashScreen API、PendingIntent 可变性 | 高 |
| 13 Tiramisu | 33 | 2022 | 通知权限、精细化媒体权限 | 高 |
| 14 Upside Down Cake | 34 | 2023 | 前台服务类型、广播注册导出声明 | 高 |
| 15 Vanilla Ice Cream | 35 | 2024 | Edge-to-Edge、16KB 页面、前台服务超时 | 高(Google Play 强制) |
二、Android 8.0(API 26)适配要点
| 变更 | 影响 | 适配方案 |
|---|---|---|
| 通知渠道(Notification Channel) | targetSdk ≥ 26 必须创建渠道才能显示通知 | 使用 NotificationChannel 创建渠道 |
| 后台执行限制 | 对后台应用限制 Service 和 Broadcast | 改用 JobIntentService、WorkManager |
| 安装 APK 需要权限 | 安装 APK 需要 REQUEST_INSTALL_PACKAGES 权限 | 在 Manifest 中声明权限,并引导用户授权 |
| 透明 Activity 限制 | 不允许透明主题的全屏 Activity | 移除透明主题或改为 Dialog 样式 |
代码示例:创建通知渠道
// Android 8.0+ 必须创建通知渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
"default_channel_id",
"默认通知渠道",
NotificationManager.IMPORTANCE_DEFAULT
).apply {
description = "默认渠道描述"
}
val notificationManager = getSystemService(NotificationManager::class.java)
notificationManager.createNotificationChannel(channel)
}
三、Android 9.0(API 28)适配要点
| 变更 | 影响 | 适配方案 |
|---|---|---|
| Apache HTTP 客户端移除 | 使用了 org.apache.http 的应用会崩溃 | 在 Manifest 中添加 useLibrary 'org.apache.http.legacy' |
| 前台服务需要权限 | 启动前台服务需要 FOREGROUND_SERVICE 权限 | 在 Manifest 中声明权限 |
| 刘海屏适配 | 需要适配 DisplayCutout | 使用 WindowInsets.getDisplayCutout() |
| HTTPS 网络请求限制 | 默认禁止明文流量(HTTP) | 创建 res/xml/network_security_config.xml 允许明文流量,或全量迁移到 HTTPS |
代码示例:允许明文流量(仅调试环境)
<!-- res/xml/network_security_config.xml -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">example.com</domain>
<trust-anchors>
<certificates src="user" />
</trust-anchors>
</domain-config>
</network-security-config>
<!-- AndroidManifest.xml 中 application 标签添加 -->
android:networkSecurityConfig="@xml/network_security_config"
四、Android 10(API 29)适配要点
| 变更 | 影响 | 适配方案 |
|---|---|---|
| Scoped Storage(分区存储) | 外部存储访问受限 | 使用 MediaStore 访问媒体文件 |
| 后台启动 Activity 限制 | 不能直接从后台启动 Activity | 使用通知引导用户点击,或申请 SYSTEM_ALERT_WINDOW 权限 |
| 深色模式(Dark Mode) | 系统级深色模式 | 适配 DayNight 主题 |
| 手势导航适配 | 需要处理 Window Insets | 使用 ViewCompat.setOnApplyWindowInsetsListener |
代码示例:兼容深色模式
// styles.xml(日间主题)
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primaryDark</item>
</style>
// colors.xml 中提供日间和夜间两套颜色
// values/colors.xml → 日间颜色
// values-night/colors.xml → 夜间颜色
五、Android 11(API 30)适配要点
| 变更 | 影响 | 适配方案 |
|---|---|---|
| 包可见性(Package Visibility) | 查询其他应用需要声明 <queries> | 在 Manifest 中添加 <queries> 元素 |
| 单次权限(One-time Permission) | 位置、麦克风、摄像头支持单次授权 | 无需额外适配,系统自动处理 |
| 权限对话框频繁请求限制 | 用户在短时间内拒绝两次后,第三次不再弹出对话框 | 引导用户到设置页面授权 |
| 前台服务类型 | 需要指定前台服务类型(Android 14 强制) | 提前声明 foregroundServiceType |
代码示例:声明包可见性
<!-- 查询所有已安装应用(需要权限) -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<!-- 或精确声明需要查询的应用 -->
<queries>
<package android:name="com.example.targetapp" />
<intent>
<action android:name="android.intent.action.SEND" />
</intent>
</queries>
六、Android 12(API 31)适配要点
| 变更 | 影响 | 适配方案 |
|---|---|---|
| SplashScreen API | 统一启动页规范 | 使用 SplashScreen 兼容库 |
| PendingIntent 必须声明可变性 | 不声明会崩溃 | 显式指定 FLAG_MUTABLE 或 FLAG_IMMUTABLE |
| 前台服务通知延迟 | 前台服务启动后通知会延迟显示 | 无需适配,了解行为变化即可 |
| 蓝牙权限细化 | 旧蓝牙权限废弃 | 使用 BLUETOOTH_SCAN / BLUETOOTH_CONNECT |
| 精确闹钟权限 | 需要 SCHEDULE_EXACT_ALARM 权限 | 请求权限或使用非精确闹钟 |
代码示例:PendingIntent 可变性
val pendingIntent = PendingIntent.getActivity(
context,
requestCode,
intent,
// ✅ 必须显式指定可变性
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
代码示例:SplashScreen 适配
// 在 Activity 的 onCreate 中
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Android 12+ 的 SplashScreen API
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val splashScreen = installSplashScreen()
// 保持启动页,直到应用准备好
splashScreen.setKeepOnScreenCondition { !isAppReady }
}
setContentView(R.layout.activity_main)
}
七、Android 13(API 33)适配要点
| 变更 | 影响 | 适配方案 |
|---|---|---|
| 通知权限 | 需要动态申请 POST_NOTIFICATIONS 权限 | 使用 requestPermissionLauncher 请求权限 |
| 精细化媒体权限 | READ_EXTERNAL_STORAGE 拆分为三个权限 | 使用 READ_MEDIA_IMAGES 等权限 |
| Wi-Fi 权限变更 | 靠近 Wi-Fi 设备不需要位置权限 | 使用 NEARBY_WIFI_DEVICES 权限 |
| Clipboard 内容隐藏 | 敏感内容可以隐藏在剪贴板中 | 使用 ClipDescription.EXTRA_IS_SENSITIVE |
代码示例:请求通知权限
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
showNotification()
} else {
// 引导用户到设置页面开启
}
}
fun checkNotificationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
}
八、Android 14(API 34)适配要点
| 变更 | 影响 | 适配方案 |
|---|---|---|
| 前台服务类型必须声明 | Manifest 中必须指定 foregroundServiceType | 在 <service> 标签中添加 android:foregroundServiceType |
| 动态广播注册必须指定导出行为 | registerReceiver() 必须传 RECEIVER_EXPORTED 或 RECEIVER_NOT_EXPORTED | 显式指定导出行为 |
| OpenJDK 17 行为变更 | 正则表达式、UUID 生成等行为变化 | 回归测试核心逻辑 |
| 更严格的 Intent 过滤规则 | Intent 过滤器必须显式声明 exported | 在 Manifest 中显式声明 |
代码示例:注册广播(Android 14+ 兼容)
val filter = IntentFilter("com.example.MY_ACTION")
// 接收其他应用的广播 → 需要导出
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
context.registerReceiver(myReceiver, filter, Context.RECEIVER_EXPORTED)
} else {
context.registerReceiver(myReceiver, filter)
}
// 只接收应用内部广播 → 不导出(更安全)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
context.registerReceiver(myReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
}
九、Android 15(API 35)适配要点
| 变更 | 影响 | 适配方案 |
|---|---|---|
| Edge-to-Edge 强制适配 | 应用显示区域扩展至全屏 | 使用 WindowInsets 处理系统栏间距 |
| 16KB 页面大小 | 使用 NDK 的应用需要适配 | 升级 AGP 到 8.3+,重新编译 so 库 |
| 前台服务超时限制 | dataSync/mediaProcessing 类型有 24 小时时长限制 | 改用 WorkManager 或处理 onTimeout 回调 |
| 最低 targetSdk 要求 | targetSdk < 24 无法安装 | 升级 targetSdkVersion 到 24+ |
| OpenJDK 17 对齐 | 字符串格式化等行为变更 | 避免使用 %0$s 等不合规格式 |
代码示例:Edge-to-Edge 适配(非 Compose)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.root)) { v, insets ->
val statusBar = insets.getInsets(WindowInsetsCompat.Type.statusBars())
val navigationBar = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
v.setPadding(
navigationBar.left,
statusBar.top,
navigationBar.right,
navigationBar.bottom
)
insets
}
十、Gradle 配置建议(2026 年推荐)
// app/build.gradle
android {
compileSdk 35
defaultConfig {
minSdk 21 // 覆盖 99%+ 设备
targetSdk 35 // 紧跟最新版本
multiDexEnabled true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
}
dependencies {
implementation "androidx.core:core-ktx:1.13.0"
implementation "androidx.activity:activity-ktx:1.9.0"
implementation "androidx.fragment:fragment-ktx:1.7.0"
}
十一、适配优先级建议
根据应用的目标用户和业务需求,按以下优先级进行适配:
| 优先级 | 版本 | 理由 |
|---|---|---|
| P0(立即适配) | Android 15 | Google Play 强制要求 |
| P0(立即适配) | Android 13 | 通知权限影响消息到达 |
| P1(3 个月内) | Android 14 | 前台服务类型、广播注册 |
| P1(3 个月内) | Android 12 | PendingIntent 可变性(安全) |
| P2(6 个月内) | Android 10/11 | 分区存储、包可见性 |
| P3(按需适配) | Android 8/9 | 存量设备较少 |
十二、参考资源
本文会持续更新,建议收藏。如果你发现某个版本的适配要点有遗漏,欢迎在评论区补充。如果本文对你有帮助,欢迎点赞收藏。