先看结论与判断条件
- SDK 边界必须从准备发布的最终 APK 二进制 Manifest 读取,Gradle 配置和源码清单只能说明构建意图。
- minSdk 变化主要改变安装资格与最低系统覆盖,targetSdk 变化主要改变平台对应用采用的兼容与安全行为。
- 同一仓库的不同 build type、product flavor、source set 和 applicationId 组合可能生成不同边界,必须逐发布变体验收。
- targetSdk 不变也不能跳过新系统回归,因为平台存在面向所有应用生效的行为变化。
- 差分门禁要将 SDK 变化、包名、versionCode、签名责任和真实升级路径绑定,避免比较两个无关候选。
- 静态差异负责生成风险问题和测试矩阵,最终放行依赖目标设备上的安装、升级、组件、权限和核心业务回执。
先区分安装边界与运行语义
minSdk 与 targetSdk 都写在应用的 SDK 约束中,但它们回答不同问题。minSdk 用于表达应用声明的最低平台要求,变化会直接改变哪些系统版本具备安装资格;targetSdk 表示应用已经面向哪个平台行为级别完成适配,系统会据此决定部分兼容和安全行为。把两者合并成一个“SDK 升级”告警,会让测试人员不知道该补最低版本安装,还是该审查权限、窗口、调度和组件语义。
发布门禁的输入必须来自前后两个最终 APK,而不是 `build.gradle` 中的默认值。Manifest 会由主工程、依赖和变体 source set 合并,CI 也可能选择不同任务或属性。应从二进制 Manifest 提取 minSdk、targetSdk、applicationId、versionCode 和版本名,再绑定文件摘要、variant、签名责任及生成时间。若任一候选身份不完整,差分没有可靠比较基础,应先阻断而不是用仓库配置补值。
本文只回答 minSdk 与 targetSdk 变化如何形成差分门禁,不写成一般的 Android 新版本适配清单,也不重复 AAB 与 split APK 的发布身份问题。静态值变化可以决定检查方向,不能证明某项平台行为一定被应用触发。没有真实候选、功能清单和设备回执时,只能报告边界变化与待验证项,不能宣称兼容通过或全部用户不受影响。
| 字段 | 直接改变 | 静态检查 | 设备验证 |
|---|---|---|---|
| minSdk 上调 | 最低安装系统范围收窄 | 前后值与发布策略 | 旧边界和新边界安装结果 |
| minSdk 下调 | 声明支持范围扩大 | 依赖 API 与兼容分支 | 新增低版本功能矩阵 |
| targetSdk 上调 | 部分平台兼容行为切换 | 跨越行为级别与功能映射 | 目标系统组件和权限回归 |
| targetSdk 下调 | 发布配置异常或策略回退 | 来源、渠道与允许策略 | 不得以设备通过掩盖配置错误 |
| 字段不变 | SDK 边界未变化 | 候选身份与清单摘要 | 仍覆盖面向所有应用的变化 |
| 字段缺失 | 无法形成可靠边界 | 提取器与候选完整性 | 不进入设备放行阶段 |
从最终 Manifest 固定比较对象
Android Manifest 定义组件、权限、intent filter、SDK 约束和应用元数据。差分工具应读取最终二进制清单的 `<uses-sdk>` 结果,并同时盘点可能受 target 行为影响的组件、权限和元数据。只打印两个数字会丢失解释上下文,例如目标级别上调后,应用是否使用对应权限、后台任务、窗口模式或导出组件,决定了测试优先级。门禁需要把 SDK 差异关联到真实功能,不是机械生成整个平台文档。
比较前要验证两个 APK 确实属于连续发布链。applicationId 应一致,versionCode 应符合目标渠道的升级条件,签名需要由独立流程验证兼容或具备轮换证明。若包名不同,它们可能是两个产品或环境包;若版本码方向异常,所谓“新候选”可能拿错。SDK 差异脚本可以提示这些身份字段,但不能替代签名验证,也不能根据文件名猜测先后顺序。
提取回执要保留工具版本和原始字段,避免解析器升级造成静默变化。部分旧包可能没有显式声明某个值,工具会展示平台或构建默认行为;门禁不能把默认值与显式值混为一谈,应记录值的来源和是否显式。若无法从最终产物取得确定值,结果应标为不可比较,返回构建链查明,而不是为了让报告完整而填入当前项目配置。
- 前后 APK 均保存 SHA-256 摘要和提取工具版本
- applicationId、versionCode、版本名和 variant 同时读取
- minSdk 与 targetSdk 标明显式值或解析来源
- 签名兼容性由独立发布身份门禁验证
- 组件、权限和元数据差异与 target 变化关联
- 不可比较字段不会被源码默认值静默补齐
minSdk 上调与下调需要完全不同的测试
minSdk 上调意味着旧于新下界的设备不再属于声明支持范围。发布决策要先回答这是计划中的用户范围调整,还是依赖升级、变体串用或构建配置意外带来的结果。静态门禁应核对产品支持策略、商店渠道和依赖最低要求,并在设备矩阵中保留旧 minSdk、旧下界附近版本、新 minSdk 和一个较新版本。真正的影响还需要当前安装基础与渠道数据,不能从 APK 数字推断用户数量。
minSdk 下调看似扩大覆盖,风险却常被低估。源码可能调用了高版本 API,依赖可能只在较新系统经过验证,资源限定、加密提供者、文件系统和后台限制也可能不同。门禁应要求新增低版本设备上的安装、冷启动、核心页面、网络、存储、权限、升级和异常恢复测试,并检查 API 守卫与替代路径。构建成功只说明字节可产出,不说明低版本运行时能够安全执行。
minSdk 不变也要关注依赖和构建工具的最低运行假设。某个 SDK 可能保留相同 Manifest 下界,却在特定路径调用更高 API;R8 或 desugaring 配置变化也可能改变低版本行为。差分报告应把 minSdk 数字与依赖图、核心功能 API 使用和设备失败记录并列。本文不宣称静态工具可以证明所有 API 调用有正确守卫,最终仍需要目标设备或等价运行证据。
| 变化方向 | 最低必测位置 | 主要问题 | 放行证据 |
|---|---|---|---|
| 上调 | 旧下界与新下界 | 旧设备是否按计划退出支持 | 产品决策和安装回执 |
| 上调 | 新下界的真实设备 | 首次安装与核心功能是否可用 | 安装、启动和业务用例 |
| 下调 | 新增最低系统 | API 守卫和依赖是否兼容 | 低版本完整回归 |
| 下调 | 新增范围内中间版本 | 兼容分支是否覆盖 | 代表设备和失败路径 |
| 不变 | 原最低支持系统 | 依赖与工具链是否改变假设 | 基线设备回归 |
| 来源不明 | 暂不进入矩阵 | 是否拿错 variant 或候选 | 先恢复可追溯提取 |
targetSdk 上调要按行为边界筛选功能
targetSdk 上调不是把一个数字改大后重新编译。平台会针对达到特定目标级别的应用启用一组兼容、安全、权限、调度或界面行为,实际风险取决于应用使用了哪些能力。以 Android 16 为例,面向 targetSdk 36 的行为文档覆盖大屏、权限、调度和安全等方面。门禁应记录跨越了哪些目标级别,再将官方变化条目映射到应用组件和业务路径,不能把整页列表无差别复制成测试计划。
映射需要明确“平台事实”和“项目判断”。官方资料可以说明某项行为在什么条件下生效,项目清单和代码可以说明应用是否可能使用相关 API,只有设备用例才能说明候选在目标场景的结果。报告应为每项保留适用条件、触发组件、验证步骤、预期结果和未覆盖限制。若项目没有使用某能力,可以标记不适用,但要保存判断依据,不能仅凭负责人印象删除。
targetSdk 下调通常不是普通兼容策略,而是发布配置、渠道任务或回退决策需要审查的信号。即使某个设备测试暂时通过,也不能证明下调符合商店政策和安全基线。门禁应要求解释来源、审批和后续计划,并核对是否选择了错误 variant。本文不提供某个渠道当前 target 要求的时效性结论,具体政策需要在发布时以目标渠道官方要求为准。
- 列出前后 targetSdk 跨越的每个行为级别
- 官方行为条目映射到具体组件和业务功能
- 每项记录生效条件、测试步骤和预期结果
- 不适用结论保存代码或配置依据
- targetSdk 下调触发配置来源和发布策略审查
- 渠道政策在实际发布日期单独核对
新系统回归不能只盯着 targetSdk 变化
平台还存在面向所有应用生效的行为变化,因此 targetSdk 未变并不等于新系统无需测试。Android 16 面向所有应用的行为文档说明,旧目标版本也可能受到部分系统变化影响。差分门禁应把“候选 target 变化触发的测试”和“目标操作系统本身触发的测试”分成两条轴。否则 target 不变的维护版本容易漏掉新系统问题,target 上调时又可能把所有失败错误归因于目标级别。
设备矩阵至少覆盖最低支持系统、主要使用区间、跨越的 target 行为边界和计划支持的最新系统。具体设备和厂商范围应依据真实用户与风险选择,不能用一个模拟器代表全部环境。对依赖组件、系统权限、后台执行、通知、文件访问、窗口和网络等行为,要从应用功能清单选取真实入口。没有使用的能力无需制造测试,但适用性判断必须可复核。
失败归因需要对照组。可以在同一系统上比较前后 APK,也可以在同一 APK 上比较不同系统,但要保持签名、数据状态、账号、网络和测试步骤一致。若同时更换 targetSdk、依赖、加固配置和业务代码,仅凭一次失败无法确定根因。门禁应把变化集合写入报告,优先用最小差分候选或可控开关定位,不应把平台文档中的某条变化直接当作已证实根因。
| target 变化 | 系统变化 | 测试重点 | 结论边界 |
|---|---|---|---|
| 不变 | 不变 | 候选自身回归 | 只能覆盖代码与配置变化 |
| 上调 | 不变 | 目标级别触发行为 | 按实际功能筛选 |
| 不变 | 升级 | 面向所有应用的系统变化 | 不能归因于 target |
| 上调 | 升级 | 两类变化交叉 | 需要对照候选拆分根因 |
| 下调 | 任意 | 发布配置和策略异常 | 设备通过不等于允许发布 |
| 未知 | 任意 | 候选身份或提取失败 | 停止生成兼容结论 |
更新连续性字段要和 SDK 差异同时绑定
Android 应用更新要求包名保持一致、签名兼容或具备合法轮换证明,并使用可接受的 versionCode。SDK 边界差分应把这些身份字段一起保存,因为只有同一更新链中的前后候选,minSdk 和 targetSdk 比较才有业务意义。包名不同可能是独立安装的渠道包,签名不兼容可能导致无法覆盖安装,versionCode 异常则可能让新候选无法成为更新。SDK 测试不能绕过这些前置事实。
但更新条件不等于业务层升级安全。即使系统允许覆盖安装,minSdk 上调后的旧设备策略、target 行为变化下的数据迁移、权限状态、通知设置和后台任务仍需验证。反过来,SDK 数字没有变化也不能证明升级连续性,因为签名、包名、组件和数据结构可能发生其他变化。本文只将身份字段作为差分对象绑定条件,不形成第二套 APK 更新连续性指南。
测试前应设计两条路径:干净安装用于验证当前边界和首次运行,真实旧版升级用于验证数据、权限和任务状态。若 minSdk 上调,旧系统上的已安装版本如何继续服务或退出支持,需要产品和渠道决策;若 targetSdk 上调,则同一设备上前后版本的权限与系统行为对比更有解释力。所有用例必须记录安装前状态和最终 APK 摘要,避免把残留数据当作候选差异。
- 前后候选 applicationId 一致且更新关系明确
- versionCode 方向和目标渠道要求相符
- 签名兼容或轮换证明由独立门禁确认
- 干净安装和真实旧版升级分别测试
- 权限、数据和后台任务状态在升级前后记录
- 更新身份条件不替代 SDK 行为回归
用差异脚本生成最小回归任务
下面的 Python 示例读取前后两个 APK 的脱敏元数据 JSON,字段来自最终二进制 Manifest 和独立身份提取。脚本验证包名、variant、versionCode、minSdk 与 targetSdk 的完整性,根据变化方向输出必须执行的回归任务。它不解析签名私钥、不修改 APK,也不声称设备测试通过;输出只是测试计划输入,真正结果要由设备和用例回执补充。
输入必须与候选摘要绑定,并由同一版本的提取工具生成。示例对缺失文件、非整数 SDK、包名不一致、variant 不一致、versionCode 未增加和 SDK 值异常设置可达失败路径。targetSdk 下调会被标为阻断,minSdk 上调或下调则生成不同安装边界任务。对 targetSdk 上调,脚本要求评审跨越行为级别和最新系统;它不会内置某个平台文档的全部变化,避免规则过期。
生产门禁可将官方行为清单按 target 级别版本化,再由功能所有者映射到组件和用例。生成器应输出任务键、触发差异、所需设备、证据字段和责任人,不能自动把未执行任务标成通过。若同一批次还改变依赖或加固配置,应把变化摘要附在任务中,为失败归因保留上下文。脚本通过只说明元数据差异可解释,不代表发布完成。
from pathlib import Path
import json
import sys
if len(sys.argv) != 3:
raise SystemExit("usage: sdk_diff.py before.json after.json")
def load(path_text):
path = Path(path_text).resolve()
if not path.is_file():
raise SystemExit(f"input missing: {path.name}")
data = json.loads(path.read_text(encoding="utf-8"))
for key in ("apk_sha256", "application_id", "variant"):
if not str(data.get(key) or "").strip():
raise SystemExit(f"required field missing: {key}")
for key in ("version_code", "min_sdk", "target_sdk"):
if not isinstance(data.get(key), int) or data[key] < 1:
raise SystemExit(f"invalid integer field: {key}")
return data
before = load(sys.argv[1])
after = load(sys.argv[2])
if before["application_id"] != after["application_id"]:
raise SystemExit("application_id differs; packages are not comparable updates")
if before["variant"] != after["variant"]:
raise SystemExit("variant differs; compare equivalent release artifacts")
if after["version_code"] <= before["version_code"]:
raise SystemExit("version_code did not increase")
tasks = []
def add(kind, reason, checks):
tasks.append({"kind": kind, "reason": reason, "checks": checks})
old_min, new_min = before["min_sdk"], after["min_sdk"]
old_target, new_target = before["target_sdk"], after["target_sdk"]
if new_min > old_min:
add("min-sdk-raised", f"{old_min} -> {new_min}", ["support-policy-review", "old-boundary-install", "new-boundary-install"])
elif new_min < old_min:
add("min-sdk-lowered", f"{old_min} -> {new_min}", ["new-minimum-device", "api-guard-review", "dependency-compatibility"])
else:
add("min-sdk-unchanged", str(new_min), ["minimum-supported-device-baseline"])
if new_target > old_target:
add("target-sdk-raised", f"{old_target} -> {new_target}", ["crossed-behavior-review", "permission-and-component-tests", "latest-os-test"])
elif new_target < old_target:
raise SystemExit("target_sdk decreased; release policy review required")
else:
add("target-sdk-unchanged", str(new_target), ["all-app-os-changes-review"])
report = {"before_sha256": before["apk_sha256"], "after_sha256": after["apk_sha256"], "application_id": after["application_id"], "variant": after["variant"], "tasks": tasks}
print(json.dumps(report, ensure_ascii=False, indent=2))以设备回执和明确边界完成放行
依赖真实 Android 运行时、组件和系统 API 的语义应通过设备端 instrumented test 验证。差分门禁生成矩阵后,每项回执要绑定设备型号、系统版本、ABI、安装方式、前后 APK 摘要、账号与数据状态、步骤、断言和日志时间。最低系统只跑启动页不够,target 行为也不能只看应用未崩溃;测试必须进入真实组件、权限、后台任务、窗口和核心业务路径。
放行记录要区分静态事实、项目判断和设备结果。静态事实包括最终 SDK 值与候选身份;项目判断包括哪些平台变化适用、哪些设备代表用户范围;设备结果只覆盖实际执行的环境和用例。一个设备通过不能代表所有 API、ABI 和厂商矩阵,一个官方行为列表也不能证明项目触发了全部变化。任何未覆盖项要显式保留,不得用“风险可控”替代证据。
完整证据包应包含前后 APK 摘要、Manifest 提取、variant、包名、versionCode、签名门禁引用、SDK 差异、行为映射、设备矩阵、失败与豁免。需要评估实际候选时,可通过御盾中央平台提交脱敏的前后摘要、最终 Manifest 字段、发布变体和现有设备范围,先生成差分问题,再执行针对性回归。没有真实设备或项目数据时,本文不登记任何兼容通过结论。
- 每条设备回执绑定前后候选摘要和安装状态
- 最低系统、目标行为边界和最新系统均有代表用例
- 组件、权限、后台任务和核心业务使用真实入口
- 静态事实、适用性判断和设备结果分层登记
- 未覆盖设备与功能明确列入发布限制
- 失败归因保留依赖、配置和业务代码变化上下文
事实依据与适用边界
以下内容区分官方事实、本文工程判断和不能外推的范围,避免把设计建议写成未经验证的产品结论。
| 本文判断 | 事实或工程依据 | 适用限制 |
|---|---|---|
| Manifest 定义应用组件、权限、intent filter、SDK 约束和应用元数据。 | Android app manifest 说明应用清单的核心声明范围。 | 静态 Manifest 不能证明运行时组件、权限和业务授权行为正确。 |
| build type、product flavor、source set、applicationId 和签名配置会组合成不同发布变体。 | Android build variants 说明构建类型、产品风味、source set 和应用标识的变体机制。 | 同一代码仓库不代表所有 variant 具有相同 SDK、资源、证书和运行行为。 |
| 面向 targetSdk 36 的应用会遇到 Android 16 大屏、权限、调度和安全等行为变化。 | Android 16 target behavior changes 列出面向 Android 16 目标级别的行为变化。 | 文档条目会更新,必须按实际 targetSdk、组件和功能筛选,不能假定全部适用。 |
| Android 16 存在不依赖 targetSdk、面向所有应用生效的行为变化。 | Android 16 all-app behavior changes 列出面向所有应用的系统行为变化。 | 平台变化不意味着每个候选都会触发同一问题,仍需目标功能回归。 |
| Android 应用更新需要保持 applicationId、兼容签名或轮换证明,并使用可接受的 versionCode。 | How Android app updates work 说明 Android 应用覆盖更新的身份条件。 | 平台更新条件不等于业务数据迁移、权限状态和防重放策略已经正确。 |
| 依赖真实 Android 运行时、组件和系统 API 的语义应通过设备端测试验证。 | Android instrumented tests 说明 instrumented test 适用于需要真实 Android 环境的行为。 | 单一设备通过不能代表完整 API、ABI 和厂商矩阵。 |
| minSdk 与 targetSdk 差异应触发不同的发布问题和设备矩阵。 | 工程判断:安装资格变化和平台兼容行为变化需要不同证据,合并告警会掩盖测试责任。 | 差分规则不能预测实际用户影响,也不能替代渠道数据和设备结果。 |
| SDK 差分必须绑定前后最终 APK 的身份、variant 和版本信息。 | 工程判断:只有同一发布链的可追溯候选才具备可解释的边界比较关系。 | 字段一致不能替代签名验证,也不能证明应用内升级和数据迁移安全。 |
工程常见问题
只比较 build.gradle 中的 minSdk 和 targetSdk 可以吗?
不可以。变体、source set 和 Manifest 合并会改变最终值,发布门禁必须读取前后最终 APK 的二进制 Manifest。
minSdk 上调后只测试新的最低系统够吗?
不够。还要确认旧支持范围的产品与渠道决策,并覆盖新下界、主要系统区间、升级路径和核心功能。
minSdk 下调为什么风险可能更大?
它扩大了声明支持范围,低版本 API、依赖、资源、权限和系统服务差异都需要新增设备回归,构建成功不能证明运行兼容。
targetSdk 没变化是否可以跳过最新 Android 回归?
不可以。平台存在面向所有应用生效的行为变化,旧目标版本的应用仍要测试真实功能入口。
targetSdk 上调后是否要测试官方文档中的每一项?
应先按应用组件和功能筛选,保存不适用依据;适用项再建立设备用例,不能无差别复制列表或凭印象删除。
准备 SDK 边界差分需要哪些材料?
准备前后最终 APK、摘要、Manifest 提取、variant、包名、versionCode、签名门禁引用、功能清单、目标渠道和设备范围。