如何使用 Build.VERSION.SDK_INT 判断当前系统版本?

解读

在国内面试中,这道题看似“送分”,实则考察三点:

  1. 是否真正理解“API Level”与“版本号”的区别(面试官常追问“为什么不用 Build.VERSION.RELEASE”);
  2. 是否能在代码里写出“向下兼容”的健壮分支,而非简单 if-else;
  3. 是否清楚国内特殊机型“魔改 SDK”带来的坑,例如部分厂商把 Android 11 刷成 30 却阉割了 30 的 API,导致运行时 Crash。
    答得太浅(只写 if (SDK_INT >= 28))会被认为没做过大型项目;答得太深(把 Compatibility.isAtLeastR() 源码背出来)又显得炫技。最佳策略是:先给“安全写法”,再补“国内踩坑案例”,最后主动抛“如何兜底”。

知识点

  1. Build.VERSION.SDK_INT 返回 int,官方命名“API Level”,与“Android 版本”一对一映射,如 31 对应 Android 12。
  2. 该字段在 boot 阶段由系统 ro.build.version.sdk 写入,应用进程只读,无需权限;但国内某些“保活”ROM 会在 xposed 层 hook 该值,导致判断失真。
  3. 官方推荐用 SDK_INT 做“大于等于”判断,禁止用“等于”做分支,避免新系统直接走进 else。
  4. 从 Android 10 开始,targetSdkVersion < 29 的应用在后台读取设备标识符会被限制,因此版本判断常与“运行行为”而非“功能开关”耦合。
  5. 国内多渠道包常配合 Build.MANUFACTURER 做“双条件”兼容,例如小米 Android 11 上仍用 28 的 File API 需额外判断 MIUI 版本。

答案

private fun doSomethingOnStorage() {
    val target = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        // Android 11(API 30)及以上,使用分区存储新 API
        StorageManager.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION
    } else {
        // 30 以下,走旧逻辑
        Intent(Settings.ACTION_INTERNAL_STORAGE_SETTINGS)
    }
    startActivity(target)
}

// 国内机型兜底:若 SDK_INT 被篡改,捕获异常并降级
private fun safeCheck(block: () -> Unit) {
    try {
        block()
    } catch (ignore: Throwable) {
        // 降级到 API 23 以下逻辑,避免 Crash 被厂商捕获上报
        fallbackForOldApi()
    }
}

面试时口头补充:
“实际项目中我会把版本判断封装到 VersionCompat 单例里,统一暴露 isAtLeastR() 这类语义化方法,内部再套 try-catch,防止小米、华为个别内测版 ROM 把 SDK_INT 打到 33 却缺少 33 的 Symbol。”

拓展思考

  1. 如果业务需要“精确到补丁级别”,可再取 Build.VERSION.SECURITY_PATCH 做字符串比较,但注意国内运营商定制机补丁回退场景。
  2. 对于系统应用,可通过 SystemProperties.getInt("ro.build.version.sdk", 0) 读真实值,绕过被 hook 的 Build.VERSION.SDK_INT;但普通应用无权限,需与厂商白名单配合。
  3. 未来 Android 将引入 “API Level 扩展”(T-API),SDK_INT 可能不变但新增 Feature Flag,届时需改用 PackageManager 的 hasSystemFeature() 做能力探测,而非硬编码版本号。