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 的非日历/闹钟类应用,精确闹钟权限默认关闭,需引导用户去设置页开启。

六、Android 15/16 时代:从“能跑”到“体验与合规默认正确”

新版本适配不再只是补权限和崩溃点,而是系统把更安全、更一致的用户体验变成默认约束。面试中可以把 Android 15/16 作为“升级 targetSdk 的系统工程”来讲。

方向高频变化适配思路
Edge-to-edge更强调沉浸式与系统栏区域处理全局梳理状态栏/导航栏、WindowInsets、底部输入框和列表 padding
Predictive Back返回手势需要预览与可预测动画用 AndroidX Back APIs 统一返回栈,不在页面里散落 onBackPressed
媒体与相册权限部分照片/视频授权成为常态优先 Photo Picker;必须读媒体库时兼容“只授权部分资源”
后台任务前台服务、精确闹钟、后台启动继续收紧能延迟就 WorkManager;必须前台服务就给用户可见通知和明确类型
Privacy Sandbox广告归因、跨 App 跟踪能力继续收缩不依赖单一设备标识;把归因、风控、合规披露拆开设计
大屏与折叠屏面试更常追问适配能力用 WindowSizeClass、响应式布局、状态保存和多窗口测试

Edge-to-edge 的典型坑:只把 decorFitsSystemWindows=false 打开,但没有处理输入法、底部导航栏和 RecyclerView 最后一项遮挡。正确做法是把 Insets 当成布局输入,统一沉淀到基础容器或设计系统。

Predictive Back 的典型坑:页面自己拦截返回键、弹窗/Sheet/导航栈各管各的,导致返回预览和实际结果不一致。应统一到 Navigation/OnBackPressedDispatcher/OnBackInvokedCallback 这类生命周期感知入口。

七、版本适配速查矩阵与面试策略

为了在面试中快速调取记忆,可以参考以下核心版本适配矩阵:

版本与 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
Android 15/16 (35/36)Edge-to-edge、Predictive Back、隐私与后台限制持续收紧页面被系统栏遮挡、返回动画异常、部分媒体授权漏处理统一 Insets/Back 处理,优先系统 Picker,按场景治理后台任务

TargetSDK 升级工程化 Playbook

  1. 盘点风险面:权限、存储、通知、前台服务、后台任务、隐式 Intent、PendingIntent、第三方 SDK。
  2. 先升 CompileSDK 再升 TargetSDK:先解决编译 API 和依赖兼容,再打开新的运行时限制。
  3. 建立兼容矩阵:覆盖 Android 10–最新版本、主流厂商 ROM、深色模式、大屏/折叠屏、弱网和后台场景。
  4. 第三方 SDK 准入:推送、广告、统计、支付、地图、登录 SDK 必须确认支持目标版本,否则灰度期很容易“自己业务没问题,SDK 崩了”。
  5. 埋点与灰度:按系统版本、targetSdk、渠道、机型聚合 crash/ANR/权限拒绝/关键业务转化。
  6. 可回滚设计:客户端二进制无法真正回滚,必须通过配置开关、服务端降级、暂停灰度和热修复预案降低影响。

面试实战:版本适配的回答结构

当被问到“讲讲你做过哪些版本适配”时,用 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 极度不推荐长驻的后台同步服务。

Q4:如果让你负责把 App 升级到最新 targetSdk,你会怎么做? 答:我会按“风险盘点 → 依赖升级 → 灰度验证 → 指标观察 → 降级预案”推进。先列出权限、存储、通知、后台任务、Intent、第三方 SDK 等风险点;再用自动化测试和真机矩阵覆盖主路径;上线时按系统版本和渠道分批灰度,重点看 crash、ANR、权限拒绝率和业务转化。不能只在本地跑通就直接全量。

易错点 / 追问

  • TargetSDK 与 CompileSDK 混淆:CompileSDK 是编译时环境,TargetSDK 是运行时的兼容模式开关。只有提升了 TargetSDK,系统才会用新的限制策略约束你。
  • 第三方 SDK 拖后腿:自己业务适配了,但旧版第三方 SDK 仍用老 API 导致崩溃。追问解决方案时,可以说“推动 SDK 更新、使用 ASM 字节码插桩拦截修改、或寻找替代方案”。
  • 忽略条件判断:在调用新 API 时没有包裹 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU),导致在老机器上发生 NoSuchMethodError 崩溃。