Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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
    • 后台定位限制:细分了前台定位和后台定位权限。
  • Android 11 (R):
    • 强制分区存储:TargetSDK 30 时 requestLegacyExternalStorage 失效,必须彻底适配 MediaStore 或 SAF。
    • 包可见性 (Package Visibility):不能再无脑 getInstalledPackages()。必须在 Manifest 中用 <queries> 声明你要交互的应用包名,防流氓应用拉取用户应用列表。

三、Android 12:用户体验与安全加固

  • SplashScreen API:系统强制的闪屏规范。启动时自动接管显示 App 图标,需适配新的主题属性,否则会出现“双闪屏”。
  • PendingIntent 可变性:必须显式声明 FLAG_IMMUTABLEFLAG_MUTABLE,防止组件劫持。
  • 精确闹钟限制AlarmManager.setExact() 需要申请 SCHEDULE_EXACT_ALARM 权限,防止滥用唤醒系统。
  • 蓝牙权限细分:分离了定位和蓝牙权限,扫蓝牙不再必须申请定位权限(需声明 neverForLocation)。

四、Android 13:细粒度权限时代

  • 通知运行时权限:发通知不再是默认开启的,必须动态申请 POST_NOTIFICATIONS 权限。
  • 细化的媒体权限:废弃了粗放的 READ_EXTERNAL_STORAGE,拆分为:
    • READ_MEDIA_IMAGES
    • READ_MEDIA_VIDEO
    • READ_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 法则 构建你的回答:

  1. 情境(Situation): “在升级 TargetSDK 到 33 时…”
  2. 任务(Task): “…我们遇到了图片选择和通知发送失效的问题。”
  3. 行动(Action): “…我将存储权限拆分请求,并接入了官方的 Photo Picker,同时利用一套版本判断工具类统一封装了通知权限的申请逻辑。”
  4. 结果(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 崩溃。