Android 版本适配
★ 版本适配是考察 Android 基础扎实度和工程经验的试金石。不要死记硬背每个 API,重点讲清“为什么改”以及“线上怎么平滑过渡”。
一、Android 6.0 - 9.0 核心适配回顾
虽然年代久远,但这是现代 Android 权限和后台限制的基石:
- Android 6.0 (M): 动态权限机制(Runtime Permissions)。核心思想:敏感权限必须在用到时申请,不能全靠安装时授权。
- Android 7.0 (N): FileProvider。严禁在 Intent 中传递
file://URI,必须使用content://,提升跨应用文件共享的安全性。 - Android 8.0 (O):
- 后台执行限制:应用在后台时不能随便启 Service,推荐用 JobScheduler 或 WorkManager。
- 通知渠道(Notification Channels):通知必须分类,把控制权交给用户。
- Android 9.0 (P): 限制 Http 明文请求(需配置 networkSecurityConfig),以及非 SDK 接口限制(深反射受限)。
二、Android 10 - 11:存储与隐私大修
这是近年来适配痛点最集中的版本,核心是分区存储与包可见性。
- Android 10 (Q):
- Scoped Storage (分区存储):应用默认只能访问自己的沙盒目录和公共媒体集合(MediaStore)。不能再随便遍历
/sdcard。 - 后台定位限制:细分了前台定位和后台定位权限。
- Scoped Storage (分区存储):应用默认只能访问自己的沙盒目录和公共媒体集合(MediaStore)。不能再随便遍历
- Android 11 (R):
- 强制分区存储:TargetSDK 30 时
requestLegacyExternalStorage失效,必须彻底适配 MediaStore 或 SAF。 - 包可见性 (Package Visibility):不能再无脑
getInstalledPackages()。必须在 Manifest 中用<queries>声明你要交互的应用包名,防流氓应用拉取用户应用列表。
- 强制分区存储:TargetSDK 30 时
三、Android 12:用户体验与安全加固
- SplashScreen API:系统强制的闪屏规范。启动时自动接管显示 App 图标,需适配新的主题属性,否则会出现“双闪屏”。
- PendingIntent 可变性:必须显式声明
FLAG_IMMUTABLE或FLAG_MUTABLE,防止组件劫持。 - 精确闹钟限制:
AlarmManager.setExact()需要申请SCHEDULE_EXACT_ALARM权限,防止滥用唤醒系统。 - 蓝牙权限细分:分离了定位和蓝牙权限,扫蓝牙不再必须申请定位权限(需声明
neverForLocation)。
四、Android 13:细粒度权限时代
- 通知运行时权限:发通知不再是默认开启的,必须动态申请
POST_NOTIFICATIONS权限。 - 细化的媒体权限:废弃了粗放的
READ_EXTERNAL_STORAGE,拆分为:READ_MEDIA_IMAGESREAD_MEDIA_VIDEOREAD_MEDIA_AUDIO
- 照片选择器 (Photo Picker):无需任何存储权限即可让用户选择图片,强烈建议替换掉自研的图片选择器。
五、Android 14:前台服务与后台行为收紧
- 前台服务类型强制:启动 Foreground Service 必须声明对应的类型(如
location,mediaPlayback,dataSync),且必须附带具体的原因和对应权限。 - 隐式 Intent 限制:内部组件通信必须使用显式 Intent 或指定 package,防止被外部恶意截获。
- 精确闹钟默认拒绝:TargetSDK 34 的非日历/闹钟类应用,精确闹钟权限默认关闭,需引导用户去设置页开启。
六、版本适配速查矩阵与面试策略
为了在面试中快速调取记忆,可以参考以下核心版本适配矩阵:
| 版本与 TargetSDK | 核心变更 | 面试常问痛点 | 常见解决方案与对策 |
|---|---|---|---|
| Android 10 (29) | 分区存储初步引入、后台定位 | 读写 SD 卡崩溃 | 声明 requestLegacyExternalStorage 过渡,切沙盒目录 |
| Android 11 (30) | 强制分区存储、包可见性 | 第三方分享/支付失败 | Manifest 添加 <queries> 标签声明依赖的包名 |
| Android 12 (31) | PendingIntent 可变性、四大组件 Exported | 通知点击崩溃、安装失败 | 补充 FLAG_IMMUTABLE,强声明 android:exported |
| Android 13 (33) | 通知权限、细粒度媒体权限 | 发不出通知、读图失败 | 动态申请 POST_NOTIFICATIONS,接入 PhotoPicker |
| Android 14 (34) | 前台服务类型、精确闹钟收缩 | 后台服务崩溃 | 补充 foregroundServiceType 及对应权限,转用 WorkManager |
面试实战:版本适配的回答结构
当被问到“讲讲你做过哪些版本适配”时,用 STAR 法则 构建你的回答:
- 情境(Situation): “在升级 TargetSDK 到 33 时…”
- 任务(Task): “…我们遇到了图片选择和通知发送失效的问题。”
- 行动(Action): “…我将存储权限拆分请求,并接入了官方的 Photo Picker,同时利用一套版本判断工具类统一封装了通知权限的申请逻辑。”
- 结果(Result): “…不仅解决了崩溃,还把包体积减小了(因为移除了臃肿的第三方相册组件),并且减少了向用户索要危险权限的次数,合规性提升。”
高频面试题
Q1:你们是如何平滑过渡分区存储(Scoped Storage)的?
答:我们分了两步走。首先在 TargetSDK 29 时通过 requestLegacyExternalStorage="true" 作为过渡方案。其次,梳理了所有文件读写场景:将应用内缓存切到 Context.getExternalFilesDir();将需要分享给用户的图片/视频,改为通过 MediaStore API 插入;对于需要用户选择外部文件的场景,接入了 Storage Access Framework (SAF)。
Q2:Android 12 的 PendingIntent 崩溃问题怎么排查和解决?
答:这是因为 TargetSDK 31 后未声明可变性。我们首先通过全局搜索 PendingIntent.getActivity/getBroadcast 找到所有调用点。对于只用于拉起界面的点击通知,统一加上 PendingIntent.FLAG_IMMUTABLE;对于需要回传输入结果(如 Direct Reply)的场景,使用 FLAG_MUTABLE。同时检查了第三方推送 SDK 是否已升级到兼容版本。
Q3:如何处理 Android 14 前台服务的限制?
答:梳理业务中所有的 Foreground Service。比如定位打卡服务,在 Manifest 声明 foregroundServiceType="location",并在启动前确保已获得前台定位权限。如果是数据同步,尽量将其迁移到 WorkManager,因为 Android 14 极度不推荐长驻的后台同步服务。
易错点 / 追问
- TargetSDK 与 CompileSDK 混淆:CompileSDK 是编译时环境,TargetSDK 是运行时的兼容模式开关。只有提升了 TargetSDK,系统才会用新的限制策略约束你。
- 第三方 SDK 拖后腿:自己业务适配了,但旧版第三方 SDK 仍用老 API 导致崩溃。追问解决方案时,可以说“推动 SDK 更新、使用 ASM 字节码插桩拦截修改、或寻找替代方案”。
- 忽略条件判断:在调用新 API 时没有包裹
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU),导致在老机器上发生NoSuchMethodError崩溃。