先看结论与判断条件

  • Android 新版本同时包含适用于所有应用的变化与只在新 targetSdk 下启用的变化,必须用不同候选和用例隔离,不能把全部故障归给 VMP。
  • VMP 清单应逐方法记录平台触点、线程、生命周期、权限、调度、窗口与 IPC 依赖;targetSdk 变化后按触点重新决定保留、排除或专项重验。
  • 系统回调、兼容适配、反射与生成代码通常需要更透明的诊断路径,纯业务状态机也必须重新验证输入契约和异常语义。
  • compatibility framework 的 change ID 可以帮助定位某项平台行为,但只作为诊断变量,最终验收必须在真实 targetSdk 和默认平台配置下完成。
  • build type、flavor、source set、applicationId、依赖、签名和 targetSdk 共同形成候选身份,不能只修改一个数字后复用旧 APK 或旧保护报告。
  • 最小测试梯度是新系统旧 target、未保护新 target、保护后新 target与最终签名候选,所有回执都绑定 APK 摘要、设备和 change 状态。

targetSdk 是平台行为输入,也是 VMP 评估输入

targetSdk 不只是商店提交字段,它会决定平台是否对应用启用一组目标版本行为。Android 16 target behavior changes 说明 targetSdk 36 会涉及大屏、权限、调度和安全等行为。若受保护方法直接处理系统回调、权限结果、窗口状态或调度事件,输入、调用时机和异常路径可能随 target 改变,旧清单的兼容结论就不再完整。

重审不等于把所有 VMP 方法全部删除。正确动作是先建立平台触点清单,再判断每个方法是否仍位于稳定的纯业务边界。系统适配器、生命周期入口、Binder 或 Intent 处理、权限桥、窗口与调度封装需要优先复核;只处理已规范化业务数据的纯函数也要重新跑等价测试,但通常更容易保持边界。

任何 targetSdk 升级都会生成新的发布候选身份。报告至少绑定源码提交、完整 variant、targetSdk、compileSdk、依赖、构建工具、VMP 清单、APK 摘要和签名证书。只把旧报告中的方法名复制到新构建,无法证明 R8 后方法身份、平台触点或保护结果仍相同。

targetSdk 升级后首先重审的对象
对象变化来源VMP 风险重审动作
系统回调平台调用时机与参数异常或状态机变化移到薄适配层并回归
权限路径授权与返回行为旧分支不再成立重建正负用例
窗口和大屏布局与生命周期保护入口难诊断保持 UI 胶水透明
调度任务后台和任务规则触发时机变化设备端专项测试
IPC/Intent平台安全边界输入契约变化重新规范化输入
纯业务核心上游投影变化输入语义漂移绑定新契约重验

先区分所有应用变化和目标版本变化

Android 16 all-app behavior changes 说明部分平台变化不依赖 targetSdk,旧目标版本的应用在新系统上也可能受到影响。于是,某个旧 APK 在 Android 16 首次失败,并不能证明 targetSdk 升级或 VMP 造成问题。第一份必要回执是旧 target 候选在新系统上的运行结果,用于识别仅由操作系统升级触发的基线差异。

第二份回执使用相同业务代码和尽可能一致的 variant,将 targetSdk 升级但先不加入 VMP 或保持可诊断保护关闭。它与旧 target 在同一设备和用例上的差异,才更接近目标版本行为影响。第三份回执再启用新 VMP 清单;若前两层通过而第三层失败,才进入保护变换、目标选择和工具兼容调查。

这三个候选不是同一个发布物,必须分别计算摘要并保存配置。不能在运行时改一个开关后声称完成 target 对照,也不能把 debug 未保护包与 release 保护包直接比较而忽略其他 variant 差异。若无法生成控制变量清晰的候选,报告应说明混杂因素,不给出单一根因结论。

三步隔离矩阵
候选系统target/VMP回答的问题
A新系统旧 target、原保护状态是否属于 all-app 变化
B新系统新 target、未保护或可诊断target 行为是否触发
C新系统新 target、新 VMP 清单保护是否引入差异
D旧支持系统新 target、新 VMP 清单是否存在平台回归
E目标矩阵最终签名候选发布候选真实结果
对照要求相同设备类尽量固定其他输入减少混杂因素

把平台变化映射到方法,而不是把文档当测试清单

平台行为文档是筛选入口,不是项目已经触发的证明。团队应按应用实际功能过滤大屏、权限、调度、安全、媒体、存储、网络和其他类别,再为每项记录适用条件、受影响组件、调用方法和验证用例。没有使用某项平台能力时可以标记不适用,但需要代码或配置依据,不能只因测试没有崩溃就忽略。

方法触点清单应精确到 post-R8 可追踪身份或稳定 sourceIntentId,并记录 role、thread、callback、platformApis、reflection、JNI、generatedCode 和 currentDisposition。一个方法可能只间接接触平台,例如业务函数接收由权限桥生成的布尔值;这种间接依赖也要记录输入契约,否则平台变化会在业务层表现为看似随机的结果差异。

重验路径按依赖方向展开。先验证平台适配器把新系统输入规范化为旧业务契约,再验证业务核心在同一投影下保护前后等价。若契约本身必须改变,旧 VMP 基线失效,需要新的输入、输出和异常快照。不能要求 VMP 通过保持一个已经不符合平台规则的旧行为。

平台变化到方法清单的映射字段
字段内容来源停线条件
changeId/category平台变化标识和类别官方文档与兼容框架未分类
appliesWhentarget、系统和功能条件候选配置条件未知
componentsActivity、Service 等Manifest 与调用图范围不完整
methodIds受影响方法身份构建方法清单旧名称无法解析
contract输入、输出和异常测试与接口定义契约未版本化
disposition保留、排除或重验审查决定默认继承旧结论

平台适配器、系统回调和纯业务核心采用不同处置

Activity、Service、Receiver、ContentProvider、窗口回调、权限回调和任务入口属于平台适配层。它们承接 Android 对象、生命周期、线程和异常,targetSdk 升级后最需要透明诊断。通常应保持薄而不做大范围 VMP,只负责验证与投影,再把稳定业务数据交给后续函数。若旧清单直接保护这些入口,应优先评估移出。

反射、序列化、JNI、动态代理和生成代码也要重审。平台或依赖升级可能改变保留规则、方法签名和调用时机,VMP 工具对这些结构的支持需要当前候选证据。不能因为旧 target 设备曾通过就沿用。必要时把 VMP 边界移动到不依赖框架元数据的纯函数,并为边界前后的投影增加一致性校验。

纯业务核心并非自动安全。targetSdk 变化可能改变上游权限、调度或窗口路径,导致业务函数接收新的状态组合。它仍需使用新 target 的真实输入回放,检查正常、拒绝、超时、取消和恢复语义。通过后可以保留为 VMP 候选,但报告必须绑定新的方法摘要和设备回执。

方法角色与 VMP 处置
角色平台耦合默认处置重验重点
生命周期入口排除或薄化回调和恢复
权限/调度适配保持透明新平台语义
窗口和 UI 胶水排除大屏与布局行为
反射/JNI 桥中到高谨慎选择签名、keep 与 ABI
数据投影器窄函数可评估输入契约
纯业务状态机优先保留候选全状态回放

完整 variant 和最终候选身份必须重新绑定

Android build variants 由 build type、product flavor、source set、applicationId 和签名配置等组合形成。targetSdk 可能由共享配置、flavor、模块或构建脚本设置,升级时也常伴随依赖和工具链变化。对照构建必须记录完整 variant,不能只比较 targetSdk 数值,否则 source set、证书、资源或 SDK 变化会混入结果。

每个 VMP 方法要绑定新构建的 post-R8 身份、输入字节摘要、选择理由和变换报告。若 R8 或依赖升级导致方法被内联、重命名或删除,旧 target list 不能自动匹配名字相近的方法。未解析、skipped 和 failed 都要进入审查;只有逐条闭合且最终 APK 含有对应变换证据时,才能进入设备门禁。

候选链还要包含 targetSdk、compileSdk、minSdk、Gradle 与插件版本、依赖图、Manifest、VMP 配置、APK 摘要和证书摘要。升级期间若使用 debug 签名或临时配置做定位,应标记为诊断候选,不能转成生产通过。最终发布结论只引用签名候选和真实设备回执。

新 target 候选的身份字段
字段组必须记录用途禁止复用
SDKmin、target、compile平台条件只写 target
varianttype、flavor、source set固定控制变量用文件名猜测
依赖工具解析图与版本解释方法和行为变化旧锁文件回执
VMPpost-R8 目标与报告确认保护对象旧方法名清单
产物APK 摘要和证书绑定最终候选中间包结果
设备系统、API、ABI、厂商界定覆盖范围单机外推

compatibility framework 只用于定位 change ID

Android 16 compatibility framework 可以按 change ID 辅助启用或禁用部分行为,用于判断某个失败是否与特定平台变化相关。诊断时应记录设备、APK、命令、change ID、修改前后状态和用例结果,并在隔离实验后恢复。compat flag 不是生产修复,也不能把最终 target 配置下的失败改成通过。

一次只改变一个可控变量。若同时切换多个 change ID、关闭 VMP、换 debug variant 和修改依赖,即使问题消失也无法确定原因。建议先在未保护新 target 候选上隔离平台 change,再在相同 change 状态下比较保护前后;最后恢复平台默认状态,使用最终保护候选完成验收。

有些 Android 16 行为适用于所有应用,不一定提供可用于绕开的目标开关;有些 target 行为也可能涉及多个组件。compatibility framework 结果只能作为归因线索。若 change 关闭后问题消失,仍需调整应用或保护边界,使它在默认启用状态下正确运行,而不是依赖测试设备上的临时覆盖。

compat change 诊断记录
字段要求用途错误做法
candidateSha256确切 APK防止包错配只记版本名
changeId单项标识隔离平台变化一次切换多项
before/after状态和结果建立因果线索只保存成功截图
VMP state保护开关和清单摘要区分变换影响临时配置不记录
restore恢复平台默认最终验收准备把 override 留在设备
final gate默认状态真机结果形成发布证据compat 关闭即通过

设备测试按最早失败层归因,不把平台变化算作保护故障

Android instrumented tests 适合验证依赖真实 Android 运行时、组件和系统 API 的语义。targetSdk 升级回归需要覆盖平台触点、业务核心和保护边界,记录正常、拒绝、权限变化、后台恢复、进程死亡、配置变化和异常路径。用例必须运行在当前候选的真实入口,不能只调用被保护纯函数后宣称整条平台链通过。

故障按最早失败层处理。旧 target 在新系统已经失败,先调查 all-app 行为;未保护新 target 失败,先调查 target 行为和应用适配;只有未保护新 target 通过、保护新 target 失败,才进入 VMP 目标、工具支持和保护前后异常语义。最终签名包失败则继续检查打包、签名和发布配置。

NIST SP 800-218 SSDF 强调来源、构建、验证和变更证据。每次修复都产生新 APK 和新回执,不能把旧结果改日期。发布报告列出已覆盖 API、ABI、厂商和 change 状态,以及明确未覆盖范围。没有真实项目数据时,只能给出测试设计和决策门槛,不写兼容率或通过结论。

  • 旧 target 在新系统上的 all-app 基线已验证
  • 未保护新 target 已隔离平台行为变化
  • 保护后候选使用同一用例和平台状态比较
  • compat change 只用于诊断且最终恢复默认
  • 平台适配、业务核心和最终 APK 分层取证
  • 所有结论绑定新候选摘要与设备覆盖范围

用范围差分脚本生成 targetSdk 升级后的重验路径

下面的 Python 示例读取旧、新两个候选清单。它要求 variantKey、applicationId 和签名证书保持可比较,新 targetSdk 必须升高,并检查 APK、VMP 清单和方法摘要格式。每个新平台 change 需要说明适用类型、受影响方法和证据状态;方法按 platform-callback、compat-adapter、generated、reflection-jni 或 business-core 分类,生成 remove-or-isolate、revalidate 或 retain-with-regression。

脚本不读取 Android 官方文档,也不执行 compat 命令或设备测试。behaviorChanges 必须由团队根据实际 target 和功能从官方资料整理,method 列表必须由当前构建自动导出。若为了通过脚本把未知影响写成 not-applicable,静态一致也没有证据价值。输出只是重验计划,不能登记兼容或 VMP 通过。

申请 targetSdk 升级后的商业加固复评时,可准备新旧 APK、完整 variant、SDK 与依赖差分、平台 change 清单、post-R8 方法身份、VMP 报告、compat 诊断和分层设备回执,再从御盾中央平台提交申请。真实候选和回执齐备前,不宣称新平台兼容、旧保护清单仍有效或任何攻击已被阻断。

  • 新旧候选的完整 variant 与签名身份可比较
  • behaviorChanges 来自当前 target 与实际功能筛选
  • 方法清单由新构建 post-R8 产物自动导出
  • 平台角色默认移出或隔离后专项重验
  • 纯业务核心也绑定新摘要和输入契约回归
  • 脚本输出只作为计划,设备通过另行取证
比较 targetSdk 候选并生成 VMP 方法重验计划
from pathlib import Path
import json
import re
import sys

DIGEST = re.compile(r"^[0-9a-f]{64}$")
PLATFORM_ROLES = {"platform-callback", "compat-adapter", "generated", "reflection-jni"}
ALLOWED_ROLES = PLATFORM_ROLES | {"business-core"}

def load_manifest(path_text):
    path = Path(path_text)
    if not path.is_file():
        raise SystemExit(2)
    try:
        value = json.loads(path.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError):
        raise SystemExit(2)
    if not isinstance(value, dict):
        raise SystemExit(2)
    return value

def valid_digest(value):
    return isinstance(value, str) and DIGEST.fullmatch(value.lower()) is not None

def methods_by_id(document):
    methods = document.get("vmpMethods")
    if not isinstance(methods, list):
        raise SystemExit(3)
    result = {}
    for method in methods:
        required = ("sourceIntentId", "postR8Digest", "role")
        if not isinstance(method, dict) or any(field not in method for field in required):
            raise SystemExit(3)
        method_id = method["sourceIntentId"]
        if not isinstance(method_id, str) or method_id in result or not valid_digest(method["postR8Digest"]):
            raise SystemExit(3)
        if method["role"] not in ALLOWED_ROLES:
            raise SystemExit(3)
        result[method_id] = method
    return result

if len(sys.argv) != 3:
    raise SystemExit(2)
old = load_manifest(sys.argv[1])
new = load_manifest(sys.argv[2])
for field in ("variantKey", "applicationId", "signingCertificateSha256"):
    if old.get(field) != new.get(field):
        raise SystemExit(3)
if not valid_digest(old.get("apkSha256")) or not valid_digest(new.get("apkSha256")):
    raise SystemExit(3)
if not isinstance(old.get("targetSdk"), int) or not isinstance(new.get("targetSdk"), int) or new["targetSdk"] <= old["targetSdk"]:
    raise SystemExit(3)
old_methods = methods_by_id(old)
new_methods = methods_by_id(new)
changes = new.get("behaviorChanges")
if not isinstance(changes, list) or not changes:
    raise SystemExit(4)
affected = set()
for change in changes:
    required = ("changeId", "appliesTo", "affectedMethodIds", "evidenceStatus")
    if not isinstance(change, dict) or any(field not in change for field in required):
        raise SystemExit(4)
    if change["appliesTo"] not in {"all-apps", "new-target"} or change["evidenceStatus"] not in {"applicable", "not-applicable-with-evidence"}:
        raise SystemExit(4)
    if not isinstance(change["affectedMethodIds"], list):
        raise SystemExit(4)
    for method_id in change["affectedMethodIds"]:
        if method_id not in new_methods:
            raise SystemExit(4)
        affected.add(method_id)
plan = []
for method_id, method in sorted(new_methods.items()):
    if method["role"] in PLATFORM_ROLES:
        action = "remove-or-isolate"
    elif method_id in affected or method_id not in old_methods or old_methods[method_id]["postR8Digest"] != method["postR8Digest"]:
        action = "revalidate"
    else:
        action = "retain-with-regression"
    plan.append({"sourceIntentId": method_id, "role": method["role"], "action": action})
if not plan or any(item["action"] == "remove-or-isolate" and new_methods[item["sourceIntentId"]].get("reviewStatus") != "pending" for item in plan):
    raise SystemExit(5)
print(json.dumps({"status": "review-plan-only", "oldTargetSdk": old["targetSdk"], "newTargetSdk": new["targetSdk"], "plan": plan}, ensure_ascii=False, indent=2))

事实依据与适用边界

以下内容区分官方事实、本文工程判断和不能外推的范围,避免把设计建议写成未经验证的产品结论。

本文判断事实或工程依据适用限制
targetSdk 36 会改变大屏、权限、调度和安全等平台行为。Android 16 target behavior changes 列出以 Android 16 为目标的应用行为变化。列表会随平台文档更新,必须按实际 targetSdk、应用功能和当前候选筛选。
部分 Android 16 行为变化不依赖 targetSdk,旧目标应用也需要新系统回归。Android 16 all-app behavior changes 列出适用于所有应用的平台变化。平台变化不意味着所有加固应用都会触发同一问题,也不能直接归因到 VMP。
compatibility framework 可以按 change ID 辅助隔离 Android 16 行为变化。Android 16 compatibility framework 描述兼容 change 的标识与调试方式。compat flag 只用于定位,不能替代最终 targetSdk 和默认平台状态下的验收。
build type、product flavor、source set、applicationId 和签名配置能够组成不同发布变体。Android build variants 描述 variant 的组成和相关构建配置。同一仓库不代表新旧 target 候选具有相同 SDK、依赖、资源、证书和运行行为。
依赖真实 Android 运行时、组件和系统 API 的语义需要设备端测试。Android instrumented tests 说明设备测试用于验证依赖 Android 运行环境的行为。单一设备通过不能代表完整 API、ABI、厂商和平台 change 矩阵。
安全发布流程应保留来源、构建、验证和变更证据。NIST SP 800-218 SSDF 描述安全软件开发中的来源、构建、验证和变更实践。SSDF 是组织级框架,不定义具体 VMP 工具、targetSdk 适配或御盾产品功能。
应使用新系统旧 target、未保护新 target 和保护后新 target 三层候选隔离根因。工程判断:三个控制变量分别暴露 all-app、target 行为和 VMP 变换差异。其他 variant、依赖和签名差异仍需固定;无法固定时只能报告混杂因素。
平台适配器和系统回调通常应移出宽 VMP 边界,纯业务核心也需重新绑定并回归。工程判断:平台角色需要透明诊断,纯业务函数仍可能接收由新平台行为改变的输入投影。最终范围由当前工具支持、调用图、性能和真实设备证据决定,不能直接外推。

工程常见问题

targetSdk 只改一个数字,为什么 VMP 清单不能直接复用?

targetSdk 会改变平台行为,升级也可能伴随 variant、依赖、R8 和方法身份变化。旧清单没有绑定新候选、post-R8 摘要和设备回执,不能继承通过结论。

应用在 Android 16 失败,是否说明 targetSdk 或 VMP 有问题?

不能直接说明。部分 Android 16 变化适用于所有应用。先测试旧 target 候选,再测试未保护新 target,最后比较保护后候选,按最早失败层归因。

compat change 关闭后问题消失,是否可以这样发布?

不应把诊断 override 当作最终方案。它只帮助定位 change ID,应用仍需在真实 targetSdk 和平台默认状态下修复并完成最终候选验收。

哪些 VMP 方法在 targetSdk 升级后最需要重审?

优先检查生命周期入口、权限和调度适配、窗口与 UI 胶水、IPC、反射、JNI 和生成代码。纯业务核心也要用新输入契约重跑保护前后测试。

新 target 的未保护包通过,能否证明 VMP 包一定兼容?

不能。它只隔离了平台适配层。还需要对保护后候选使用同一用例和平台状态回归,并最终在签名 APK 和目标设备矩阵上验证。

提交 targetSdk 升级加固复评需要哪些资料?

准备新旧 APK、完整 variant、SDK 与依赖差分、平台 change 清单、post-R8 方法、VMP 报告、compat 诊断和分层设备回执,再通过御盾中央平台提交申请。

想用自己的 App 验证?

提交候选包、目标系统和关键业务路径,申请御盾 PoC 与兼容性评估。

继续阅读: VMP 加固是什么,如何选择保护范围