SuperAPI AI 工具集

restricted-marker-is-not-an-opt-in

编程与开发

查看项目

更新于 10-17 16:17

技能介绍

用途

本资料面向 Kotlin/Android 开发者,帮助其区分两种名字相近、但作用机制完全不同的注解标记:@RequiresOptIn 与 @RestrictTo。文章的核心目的是避免在添加 @OptIn 抑制注解时出现误判,并纠正“代码能编译就等于 API 公开稳定”的错觉。

两种标记的差异

  • @RequiresOptIn:由编译器强制执行。调用方在没有显式确认前,编译器会拒绝编译该调用。
  • @RestrictTo:编译器完全不检查,它只是一个 lint 可见的声明,表示该 API 属于库自身内部使用。

文章指出,在同一库包中同时存在这两种标记时,仅凭相邻文件或组件推断约定是不可靠的,必须逐个 API 读取其实际携带的标记。

常见陷阱

  • 没有抑制注解也能编译,只能证明该标记不是 opt-in 标记,不能证明其他任何事。
  • 多余的 @OptIn 只产生警告,但外观与必要的确认完全一致;若它命名的是真实标记,还会导致该作用域内未来新增的实验性 API 被静默预先豁免。
  • @RestrictTo 是更严重的发现而非更轻的:此类 API 并非公开 API,库保留在补丁版本中重命名或删除的权利,且不承诺废弃周期。
  • 是否为 lint 强制执行取决于模块类型,多平台或非 Android 模块往往根本不运行该检查。
  • 以上事实与版本相关,API 会毕业转正,标记会随版本变化,任何版本升级后都应重新核查。

使用建议

  • 优先使用函数级确认而非文件级 @file:OptIn,避免未来新增代码被无意豁免。
  • 不要通过同时确认两个标记来“修复”问题;确认非 opt-in 标记会触发 OPT_IN_ARGUMENT_IS_NOT_MARKER 警告,本身不解决问题。
  • 若 lint 报错,诚实的做法是停止使用该 API,或针对具体 lint 检查附理由抑制。

验证方式

资料提供了若干只读命令:

  1. 统计当前代码库中所有被确认的标记及出现次数:
grep -rhoE "@(file:)?OptIn\([^)]*\)" --include='*.kt' --exclude-dir=build . \
  | tr ',' '\n' | grep -oE "[A-Za-z0-9_]+::class" | sort | uniq -c | sort -rn
  1. 通过反编译库 JAR 识别各标记类的真实类型(kotlin/RequiresOptIn 或 androidx/annotation/RestrictTo):
JAR=$(find ~/.gradle/caches/modules-2 -name '*material3*.jar' ! -name '*sources*' | sort -V | tail -1)
echo "$JAR"; D=$(mktemp -d); unzip -oq "$JAR" -d "$D"
for C in "$D"/androidx/compose/material3/*Api.class; do B=$(basename "$C" .class)
  printf '%-42s %s\n' "$B" "$(javap -v -p -cp "$D" "androidx.compose.material3.$B" 2>/dev/null \
    | grep -oE 'kotlin/RequiresOptIn|androidx/annotation/RestrictTo' | head -1)"
done
  1. 查看具体 API 携带哪个标记,以及确认是否存在模块级 optIn 掩盖证据:
for K in WavyProgressIndicatorKt MaterialThemeKt LoadingIndicatorKt; do
  printf '%-26s %s\n' "$K" "$(javap -v -p -cp "$D" "androidx.compose.material3.$K" 2>/dev/null \
    | grep -oE 'androidx[/.]compose[/.]material3[/.][A-Za-z0-9]*Api' | sed 's|.*[/.]||' | sort -u | paste -sd' ')"
done
grep -rn "optIn\|freeCompilerArgs" --include="build.gradle.kts" --exclude-dir=build .
  1. 验证错误确认在本构建中仅是警告,并检查是否将所有警告提升为错误:
J=$(find ~/.gradle/caches/modules-2 -name 'kotlin-compiler-embeddable*.jar' ! -name '*sources*' | sort -V | tail -1)
echo "$J"; C=$(mktemp -d); unzip -oq "$J" -d "$C"
javap -c -p -cp "$C" org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors \
  | awk '/ldc(_w)? +#[0-9]+ +\/\/ String OPT_IN/ {n=$NF; getline
         if ($0 ~ /Severity\./) {split($0,a,"Severity."); split(a[2],b,":"); printf "%-52s %s\n", n, b[1]}}'
grep -rn "allWarningsAsErrors\|-Werror" --include='*.gradle.kts' --exclude-dir=build .

适用场景

本资料适用于维护或审查 Kotlin/Android 代码库、处理 Compose 等库中 opt-in 与 restrict-to 注解、排查多余或错误的抑制注解、以及评估所依赖 API 稳定性时参考。

限制

文章中的结论基于当时代码库固定的库版本,并非永久事实;不同编译器版本与构建配置下诊断级别可能不同,若构建启用了警告提升为错误,则文中部分推理不再成立。