- AI Agent
- 人工智能
- 大模型
- AI 应用
- 工具调用
- 本地部署
- MCP Clients
- Agent 记忆
【免费下载链接】Operit
The most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent
本文聚焦 Operit 开源项目中"市场(Marketplace)条目版本兼容性"这一主题,讲解市场条目如何通过minAppVer/maxAppVer元数据声明自身支持的客户端版本区间,以及 Operit 2 与 Android 客户端如何从"应用不一致"走向"下载前强制校验 + 明确失败原因"的完整治理过程。读完本文,你将掌握市场安装命令的版本参数约定、版本区间校验的底层比较算法、Android 端不可用状态与详情页横幅的实现方式,并能在自己的包发布与安装流程中复刻这套校验模型。
一、问题背景:两个客户端对版本区间的"不一致应用"
在 market_version_compatibility_20260805/index.md 所记录的需求提出之前,市场条目其实已经发布minAppVer与maxAppVer两个字段,用于声明该条目(某个发布版本)所支持的 Operit 客户端版本下限与上限。然而,Operit 生态中的两个客户端对这一元数据的应用并不一致:
- Operit 2(Flutter 客户端,对应
assistance2代号):在界面上渲染了发布方声明的版本区间,但下载前并没有真正做版本比较——也就是说,即使当前客户端版本不在支持区间内,它依然会继续执行下载流程。 - Android 客户端(Kotlin):虽然做了版本比较,但用户看到的只是被禁用的操作按钮和一个笼统的警告状态,不知道具体原因——是自己的版本太低,还是太高。
这种割裂导致两类典型问题:Operit 2 会接受不兼容的下载(下载后无法正常使用),Android 端则"静默禁用"却不解释原因,用户无从判断如何升级或降级。
本次治理的目标(Scope)明确为三点:
- 为 Operit 2 的市场安装命令强制要求传入客户端版本参数,并在下载资源之前拒绝不兼容条目;
- Flutter 把自身构建版本号传给该命令,在版本选择与直接安装两个入口被拦截时展示不兼容原因;
- Android 端在保持禁用下载行为的同时,用当前版本 + 被违反的边界值解释禁用原因。
二、预期行为与完成标准
根据 1-install-version-enforcement.md 的规划,两端最终的"预期行为"如下:
- Rust 侧(市场命令实现):市场安装命令接收真实客户端版本,在任何下载开始之前校验所选条目的版本是否处于支持区间内;不兼容即失败,不产生任何网络资源下载。
- Flutter 侧:将自身构建版本号传给 Rust 命令;在版本选择界面屏蔽不兼容的条目并给出原因;直接安装(绕过选择界面)时同样返回该原因。
- Kotlin 侧:保留"禁用操作"的行为,但在详情页明确显示客户端是低于最低版本还是高于最高版本。
最终的完成结果(Result [DONE])确认了三件事:
- Rust 命令要求调用方携带客户端版本,并在解析任何资源之前检查请求的市场版本;
- Flutter 以
2.0.0+5作为当前版本传入,对不兼容的选项给出原因并拦截,直接安装时返回同一原因; - Android 保留不可用操作按钮,同时在详情页为"低于下限 / 高于上限"两类违规分别展示横幅。
其中2.0.0+5是 Flutter 构建版本x.y.z+n格式的实例——+5表示构建序号(build number),这个格式也正是 Android 端版本解析所要求的输入格式。
三、Android 端版本解析与比较:源码级实现
Android 端的能力集中在 ArtifactMarketModels.kt 中。这套实现把"版本字符串 -> 结构化版本 -> 区间比较 -> 违规类型"的整条链路串了起来。
3.1 版本字符串解析
private fun parseAppVersionOrNull(value: String?): AppVersion? { val normalized = value?.trim().orEmpty() if (normalized.isBlank()) return null val match = APP_VERSION_REGEX.matchEntire(normalized) ?: throw IllegalArgumentException("App version must use x.y.z or x.y.z+n format") return AppVersion( major = match.groupValues[1].toInt(), minor = match.groupValues[2].toInt(), patch = match.groupValues[3].toInt(), build = match.groupValues[4].takeIf { it.isNotBlank() }?.toInt() ) }关键约束:
- 版本字符串必须是
x.y.z或x.y.z+n格式,其中n为可选的构建号(build); - 空白输入返回
null(视为"未声明",不参与校验),格式非法则直接抛出IllegalArgumentException; - 解析结果被拆成
major / minor / patch / build四个整数,为后续逐段比较提供结构化基础。
3.2 逐段比较算法
fun compareAppVersions(left: String, right: String): Int { val leftVersion = requireNotNull(parseAppVersionOrNull(left)) val rightVersion = requireNotNull(parseAppVersionOrNull(right)) if (leftVersion.major != rightVersion.major) { return leftVersion.major.compareTo(rightVersion.major) } if (leftVersion.minor != rightVersion.minor) { return leftVersion.minor.compareTo(rightVersion.minor) } if (leftVersion.patch != rightVersion.patch) { return leftVersion.patch.compareTo(rightVersion.patch) } val leftBuild = leftVersion.build ?: 0 val rightBuild = rightVersion.build ?: 0 return leftBuild.compareTo(rightBuild) }比较顺序为major → minor → patch → build,与语义化版本(SemVer)的优先级规则一致;当一侧缺省 build 时按0参与比较,保证x.y.z与x.y.z+0等价。
3.3 区间校验与违规判定
fun resolveAppVersionCompatibility( appVersion: String, minSupportedAppVersion: String?, maxSupportedAppVersion: String? ): MarketAppVersionCompatibility? { val current = requireNotNull(parseAppVersionOrNull(appVersion)) { "Current app version must use x.y.z or x.y.z+n format" } val minimum = parseAppVersionOrNull(minSupportedAppVersion) if (minimum != null && compareAppVersions(current.toString(), minimum.toString()) < 0) { return MarketAppVersionCompatibility( kind = MarketAppVersionCompatibilityKind.BELOW_MINIMUM, currentVersion = current.toString(), requiredVersion = minimum.toString() ) } val maximum = parseAppVersionOrNull(maxSupportedAppVersion) if (maximum != null && compareAppVersions(current.toString(), maximum.toString()) > 0) { return MarketAppVersionCompatibility( kind = MarketAppVersionCompatibilityKind.ABOVE_MAXIMUM, currentVersion = current.toString(), requiredVersion = maximum.toString() ) } return null }判定结果使用一个枚举 + 数据类建模:
enum class MarketAppVersionCompatibilityKind { BELOW_MINIMUM, ABOVE_MAXIMUM } data class MarketAppVersionCompatibility( val kind: MarketAppVersionCompatibilityKind, val currentVersion: String, val requiredVersion: String )即返回结果要么是null(兼容),要么携带"当前版本 + 被违反的边界值"以及违规方向,这正好对应需求中"两端都必须点名当前版本与被违反的支持边界"的完成标准。
3.4 与当前 App 版本的集成
fun MarketV2Entry.resolveCurrentAppVersionCompatibility(): MarketAppVersionCompatibility? { val version = latestVersion ?: return null return resolveAppVersionCompatibility( appVersion = BuildConfig.VERSION_NAME, minSupportedAppVersion = version.minAppVer, maxSupportedAppVersion = version.maxAppVer ) } fun MarketV2Entry.isUnsupportedByCurrentAppVersion(): Boolean { return resolveCurrentAppVersionCompatibility() != null }这里有两个要点:
- 当前客户端版本直接取自
BuildConfig.VERSION_NAME,即市场校验与 App 实际构建版本天然绑定,无需额外传参; - 校验对象是条目的
latestVersion(最新发布版本)的minAppVer/maxAppVer字段,与 ArtifactMarketModels.kt 中resolveCurrentAppVersionCompatibility的实现一一对应。
此外,同一文件还提供了formatSupportedAppVersions(...)与normalizeAppVersionOrNull(...)等辅助函数,分别用于把区间格式化为"min - max"展示文本、以及把非标准写法归一化为规范的x.y.z/x.y.z+n字符串。
3.5 发布侧的区间合法性校验
版本区间不仅在安装侧校验,发布侧同样有约束。validateSupportedAppVersions要求最低支持版本必填,并且当上下限同时存在时,min ≤ max:
fun validateSupportedAppVersions( minSupportedAppVersion: String?, maxSupportedAppVersion: String? ) { val normalizedMin = normalizeAppVersionOrNull(minSupportedAppVersion) val normalizedMax = normalizeAppVersionOrNull(maxSupportedAppVersion) require(normalizedMin != null) { "Minimum supported app version is required" } if (normalizedMin != null && normalizedMax != null) { require(compareAppVersions(normalizedMin, normalizedMax) <= 0) { "Minimum supported app version cannot be greater than maximum supported app version" } } }发布侧与安装侧共用同一套compareAppVersions比较逻辑,保证了"发布时声明的区间合法,安装时校验才可信"这一闭环。minAppVer/maxAppVer字段还出现在市场发布页面(ArtifactPublishScreen.kt)、发布 ViewModel(RepoMarketPublishViewModel.kt)以及发布服务(GitHubForgePublishService.kt)等整条发布链路中。
四、Android 详情页:禁用操作 + 原因横幅
Android 端的行为体现在 UnifiedMarketDetailEntryScreen.kt 中,核心是"保留不可用操作 + 用横幅解释原因"。
4.1 禁用状态从何而来
val currentAppVersionCompatibility = entry.resolveCurrentAppVersionCompatibility()详情页在组装时调用扩展函数resolveCurrentAppVersionCompatibility()得出当前兼容性结论,并以此把主操作标记为警告/禁用(isWarning = isCurrentAppVersionUnsupported),从而"保留禁用下载行为"。
4.2 原因横幅
当兼容性结论非空时,详情页渲染一个警告横幅(banner):
banner = if (currentAppVersionCompatibility != null) { UnifiedMarketDetailBanner( title = stringResource(R.string.market_version_incompatible_title), message = when (currentAppVersionCompatibility.kind) { MarketAppVersionCompatibilityKind.BELOW_MINIMUM -> stringResource( R.string.market_version_too_low_message, currentAppVersionCompatibility.currentVersion, currentAppVersionCompatibility.requiredVersion ) MarketAppVersionCompatibilityKind.ABOVE_MAXIMUM -> stringResource( R.string.market_version_too_high_message, currentAppVersionCompatibility.currentVersion, currentAppVersionCompatibility.requiredVersion ) }, icon = Icons.Default.Warning, containerColor = MaterialTheme.colorScheme.errorContainer, contentColor = MaterialTheme.colorScheme.onErrorContainer ) } else { ... }可以看到:
- 两类违规分别给出不同文案:
market_version_too_low_message(低于最低版本)与market_version_too_high_message(高于最高版本),两条文案都带currentVersion与requiredVersion两个占位参数,正好呈现"当前版本 + 被违反的边界"; - 视觉上使用 errorContainer / onErrorContainer,即 Material 3 错误语义配色,强化"此操作不可执行"的提示;
- 版本区间的展示文本(
minAppVer - maxAppVer)也在版本行中渲染,用户可以在同一页面对比"声明的区间"与"自己的版本"。
另外,详情页还会展示toolpkgApiVersion(工具包 API 版本)等附加信息,说明市场详情页是"版本兼容性 + 工具包 API 能力"的集中呈现入口。
五、Operit 2(Flutter/Rust)侧:安装前强制校验
按文档记录,Rust 侧的市场命令是校验的第一道闸门:
- 命令要求携带客户端版本参数——调用方(Flutter)必须显式传入自身版本,未携带时命令无法正确执行;
- 解析任何资源之前先校验版本——被选中的市场条目版本只有在通过区间检查后,才会进入资源解析与下载阶段,从而保证"不兼容的安装在任何网络资产下载之前失败";
- Flutter 传入
2.0.0+5——这是 Operit 2 在当前迭代中的构建版本号,格式与 Android 端要求的x.y.z+n一致,说明两个客户端共享同一版本语义; - 两个入口都返回原因——无论是从版本选择列表中选择安装,还是绕过列表直接指定版本安装,被拦截时都会把不兼容原因原样返回给用户界面。
这四点共同构成"Operit 2 拒绝不兼容下载"的行为闭环。从整体架构看,Rust 命令承担"服务端权威校验",Flutter 负责"版本号传递与原因展示",Android 走"本地 Kotlin 校验",三者协同实现了双端一致化。
六、设计与交付要点总结
- 校验前移:把版本比较放在"下载任何资源之前",是本次治理最关键的时序约束——无论哪一端,都不允许不兼容的条目进入网络下载阶段。
- 原因显性化:两端的完成标准都要求"点名当前版本 + 被违反的边界"。Android 通过
MarketAppVersionCompatibility(currentVersion, requiredVersion)携带两个版本号,Operit 2 则在安装被拦截时返回同一原因,杜绝了"静默禁用"。 - 单一声明、多处消费:
minAppVer/maxAppVer由发布侧写入并校验合法性(min ≤ max),安装侧(Rust 命令、Flutter UI、Kotlin 详情页)共同消费,形成"发布-安装"闭环。 - 共享版本语义:两端统一使用
x.y.z/x.y.z+n格式(Flutter 实例2.0.0+5),Kotlin 侧以BuildConfig.VERSION_NAME作为当前版本基准,保证比较结果与真实 App 版本一致。
七、延伸阅读
- 需求总览与范围定义:market_version_compatibility_20260805/index.md
- 安装侧强制校验的完整规划与验收结果:1-install-version-enforcement.md
- Android 端版本解析、比较与兼容性判定核心实现:ArtifactMarketModels.kt
- Android 详情页禁用状态与原因横幅:UnifiedMarketDetailEntryScreen.kt
- 市场发布侧版本区间输入与校验:ArtifactPublishScreen.kt、RepoMarketPublishViewModel.kt、GitHubForgePublishService.kt
- AI Agent
- 人工智能
- 大模型
- AI 应用
- 工具调用
- 本地部署
- MCP Clients
- Agent 记忆
【免费下载链接】Operit
The most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent
相关推荐
Operit 市场客户端版本兼容性强制:安装前版本校验的端到端落地
Operit 市场客户端版本兼容性强制:安装前版本校验的端到端落地 导读 本文基于 Operit 仓库中「市场客户端版本兼容性(Marketplace clie
AI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆GUI 自动化Elasticsearch Go客户端安装与版本兼容性指南
Elasticsearch Go客户端安装与版本兼容性指南 前言 Elasticsearch Go客户端(elastic/go elasticsearch)是官
后端搜索引擎Apache Thrift Delphi 版本兼容性测试:SkipTest 双版本客户端/服务端实战指南
Apache Thrift Delphi 版本兼容性测试:SkipTest 双版本客户端/服务端实战指南 导读 本文围绕 Apache Thrift 仓库中 l
后端RPC框架序列化代码生成
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考