资讯中心

Operit Compose DSL 文件选择器模式统一:从 `photo` 到六合一 `picker` 字段的设计与实现

📅 2026/10/28 23:18:28
Operit Compose DSL 文件选择器模式统一:从 `photo` 到六合一 `picker` 字段的设计与实现
AI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆【免费下载链接】OperitThe most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent项目地址https://gitcode.com/gh_mirrors/op/Operit点击查看免费下载导读本文围绕 Operit 仓库中 Compose DSLToolPkg 的 UI 描述语言文件选择器的一次接口重构展开此前选择器仅支持document选择与一个未发布的photo选项后者把 Android 实现细节暴露给了 ToolPkg 作者。本次修正用一个统一的picker字段承载全部输入源并在请求解析阶段以失败代替静默忽略来处理非法参数组合。读完本文你将掌握picker六种取值的能力边界、各参数mimeTypes/allowMultiple/persistPermission的适用范围与校验规则、底层 Android Activity Result 分发机制以及 ToolPkg 作者侧 TypeScript 类型声明与调用示例。本文核心依据为 Picker Mode Implementation 设计文档 及其任务总览 index.md实现细节来自 ToolPkgComposeDslScreen.kt 与 ComposeDslFilePickerRequestTest.kt。一、背景原有行为与photo选项的问题设计文档 1_PickerModeImplementation.md 首先回顾了既有行为The picker supports document selection and an unreleasedphotooption. The photo option only selects images and uses separate parameter rules that expose Android implementation details to ToolPkg authors.也就是说重构前的选择器有两套互不相通的入口document走 AndroidACTION_OPEN_DOCUMENT文档选择流程支持 MIME 过滤与多选photo未发布unreleased的图片选择入口仅能选图片且它的参数规则与document各自独立。photo模式的核心问题在于参数规则的碎片化ToolPkg 作者必须区分选文档和选照片两套不同的参数约定而其中照片这一概念直接映射到 Android 的相册/媒体选择实现细节属于把平台实现泄漏到了脚本 API 层。这与 Compose DSL 想为脚本作者提供统一、声明式 UI 能力的定位相违背。二、修正目标单一picker字段 参数能力矩阵修正方案用一个picker字段统一选择输入源取代document/photo分立的旧模型。任务总览 index.md 明确列出了期望结果picker接受document、image、video、media、directory、camera六种取值document保留 MIME 过滤与可选的持久 URI 访问权限document、视觉媒体image/video/media、camera返回暂存后的本地文件路径directory仅返回所选 URI不生成无效的暂存文件路径未发布的photo模式被彻底移除。配套的参数能力矩阵来自文档Intended correction一节如下参数适用模式说明picker全部六种单一输入源选择字段缺省为documentmimeTypes仅documentMIME 过滤仅文档选择支持allowMultipledocument、image、video、media多选开关persistPermissiondocument、directory持久 URI 访问权限takePersistableUriPermission关键设计原则不支持的参数组合在请求解析阶段直接失败fail at request parsing而不是被静默忽略。这保证了脚本作者写错配置时能立刻收到明确报错而非得到一份行为不可预期的结果。三、源码级解析parseComposeDslFilePickerRequest解析器的完整实现位于 ToolPkgComposeDslScreen.kt 的parseComposeDslFilePickerRequestL286-L373。它把 ToolPkg 传来的 JSON payload 转换为内部ComposeDslFilePickerRequest数据类。3.1 六种模式的枚举定义internal enum class ComposeDslFilePickerMode(val wireName: String) { DOCUMENT(document), IMAGE(image), VIDEO(video), MEDIA(media), DIRECTORY(directory), CAMERA(camera); val supportsMultiple: Boolean get() this DOCUMENT || this IMAGE || this VIDEO || this MEDIA val supportsPersistablePermission: Boolean get() this DOCUMENT || this DIRECTORY fun visualMediaType(): PickVisualMedia.VisualMediaType when (this) { IMAGE - PickVisualMedia.ImageOnly VIDEO - PickVisualMedia.VideoOnly MEDIA - PickVisualMedia.ImageAndVideo DOCUMENT, DIRECTORY, CAMERA - error($wireName is not a visual media picker) } }这段枚举直接编码了能力矩阵supportsMultiple只有document、image、video、media四种模式允许多选supportsPersistablePermission只有document与directory允许持久 URI 权限visualMediaType()把image/video/media映射到 Android Photo Picker 的PickVisualMedia.VisualMediaType其余模式调用即报错。3.2 逐项校验流程解析器按以下顺序执行任何一步require失败都会抛出带明确文案的IllegalArgumentExceptionexecutionContextKey必填空字符串直接拒绝picker取值缺失时默认DOCUMENT传入的值经trim().lowercase(Locale.ROOT)规范化后查找枚举找不到则抛unsupported file picker mode: $pickerToken——这正是photo被移除后命中报错的地方mimeTypes类型校验必须是 JSON 数组元素必须是^[^\\s/]/[^\\s/]$匹配的 MIME 字符串非空且格式合法mimeTypes适用范围校验require(pickerMode ComposeDslFilePickerMode.DOCUMENT || mimeTypesJson null)即非document模式携带mimeTypes直接报picker: \$pickerToken\ does not support mimeTypesallowMultiple校验必须是布尔值默认false且require(pickerMode.supportsMultiple || !allowMultiple)即directory/camera携带allowMultiple: true时报does not support allowMultiplepersistPermission校验先检查是否显式传入has再require(pickerMode.supportsPersistablePermission || !hasPersistPermission)即image/video/media/camera携带该参数时直接报错。3.3 默认值与归一化解析器不只是校验还完成了参数的归一化填充mimeTypes仅在document模式下生效若未提供则默认listOf(*/*)全类型其余模式强制为空列表persistPermission在document与directory模式下默认true显式传false可关闭在其余模式强制为false。也就是说ToolPkg 作者甚至可以不写任何 options直接获得全类型文档、单文件、持久权限的默认行为。四、分发执行四种底层 Activity Result Launcher解析出的ComposeDslFilePickerRequest通过ComposeDslFilePickerHostRegistry注册表按executionContextKey路由到具体宿主屏幕L375-L429再由宿主屏幕的DisposableEffect中绑定的分发逻辑L1367-L1459按模式选择不同的 Android 启动器模式底层启动器Intent / 参数documentfilePickerLauncherStartActivityForResultACTION_OPEN_DOCUMENTCATEGORY_OPENABLEFLAG_GRANT_READ_URI_PERMISSIONpersistPermissiontrue时追加FLAG_GRANT_PERSISTABLE_URI_PERMISSION单 MIME 写Intent.type多 MIME 写EXTRA_MIME_TYPESallowMultipletrue时写EXTRA_ALLOW_MULTIPLEdirectoryfilePickerLauncherACTION_OPEN_DOCUMENT_TREE 读权限标志可追加持久权限标志image/video/mediavisualMediaPickerSingleLauncher/visualMediaPickerMultiLauncherPickVisualMediaRequest.Builder().setMediaType(...)多选走PickMultipleVisualMedia(maxItems)cameracameraPickerLauncherFileProvider.getUriForFile生成临时拍摄文件 URI TakePicture契约值得注意的两处实现细节视觉媒体多选上限composeDslVisualMediaPickerMaxItems()L553-L565在 Android 13Tiramisu及以上或 Android 11R扩展版本 2 及以上返回MediaStore.getPickImagesMaxLimit()旧版本回退Int.MAX_VALUE即系统 Photo Picker 的上限约束并发保护若同一executionContextKey已有待处理的 picker 请求新请求立即以a compose file picker request is already active失败避免启动器状态错乱宿主销毁时onDispose会以compose file picker disposed终止挂起的请求并解绑注册表。五、结果返回暂存文件与directory的特殊语义选择完成后completeComposeDslSelectedUrisL567-L616统一处理结果allowMultiplefalse时对返回 URI 列表执行take(1)保证单文件语义未选择任何内容时返回cancelled: true的空结果directory模式不生成暂存文件stagedFile null仅携带目录 URI其余模式document/image/video/media/camera通过stageComposeDslPickedFile把内容复制到暂存目录。stageComposeDslPickedFileL515-L541的实现要点暂存目录为OperitPaths.cleanOnExitDir()/compose_file_picker进程退出时清理避免污染用户存储文件名经sanitizeComposeDslPickedFileName清洗非法字符、extractComposeDslPickedFileExtension校验扩展名后以picked_时间戳_12位UUID重命名防冲突防路径注入通过contentResolver.openInputStream(uri)流式复制源 URI 打不开时抛无法打开所选文件。最终 JSON 由buildComposeDslFilePickerResultJsonL471-L490与queryComposeDslPickedFileMetaL436-L469组装每个文件条目含{ uri: content://..., path: /data/user/0/.../compose_file_picker/picked_..._....json, name: example.json, mimeType: application/json, size: 12345 }其中path仅当存在暂存文件时出现directory模式无此字段size优先取OpenableColumns.SIZE取不到时回退暂存文件长度或null。六、ToolPkg 作者视角TypeScript 声明与调用示例本仓库为脚本作者维护了完整的类型声明compose-dsl.d.ts。export type ComposeFilePickerMode | document | image | video | media | directory | camera; export interface ComposeFilePickerOptions { /** Selection source. Defaults to document. */ picker?: ComposeFilePickerMode; /** MIME filter for the document picker only. */ mimeTypes?: string[]; /** Enables multiple selection for document, image, video, and media pickers. */ allowMultiple?: boolean; /** Retains URI access for document and directory pickers. */ persistPermission?: boolean; } export interface ComposePickedFile { /** Selected content URI. Directory results expose this as their only location. */ uri: string; /** Temporary local file for document, visual-media, and camera results. */ path?: string; name?: string; mimeType?: string; size?: number | null; } export interface ComposeFilePickerResult { cancelled: boolean; files: ComposePickedFile[]; }宿主通过openFilePicker(options?: ComposeFilePickerOptions): PromiseComposeFilePickerResultL1241暴露给 ToolPkg。声明注释与 Kotlin 解析器一一对应path明确标注Temporary local file for document, visual-media, and camera results而uri是directory结果的唯一位置信息。典型的 ToolPkg 调用示例// 1) 默认行为全类型单文档、持久权限 const doc await openFilePicker(); // 2) 仅选 JSON/纯文本文档支持多选、不持久化 const docs await openFilePicker({ picker: document, mimeTypes: [application/json, text/plain], allowMultiple: true, persistPermission: false, }); // 3) 单选一张图片走系统 Photo Picker const image await openFilePicker({ picker: image }); // 4) 选择目录仅拿到目录 URI无 path const dir await openFilePicker({ picker: directory }); // 5) 调用相机拍照 const photo await openFilePicker({ picker: camera });需要特别提示的是picker: photo已不存在任何历史脚本若仍使用该值将在解析阶段收到unsupported file picker mode: photo的明确异常。七、验证解析器单元测试文档Verification一节强调通过请求解析器测试与静态 diff 验证不运行本地构建遵循仓库需显式授权的指引。仓库中的 ComposeDslFilePickerRequestTest.kt 完整覆盖了本次重构的四类行为测试用例验证点defaults to document picker with persisted access空 payload 默认DOCUMENT模式、mimeTypes[*/*]、allowMultiplefalse、persistPermissiontrueaccepts document MIME filters and all supported picker modesdocument支持多 MIME 与显式persistPermissionfalse其余五种模式均被接受且mimeTypes为空rejects removed photo modepicker: photo抛unsupported file picker moderejects options that do not apply to the selected sourceimagemimeTypes抛does not support mimeTypesdirectoryallowMultiple:true抛does not support allowMultiplecamerapersistPermission:false抛does not support persistPermissionassertInvalid辅助函数断言IllegalArgumentException的 message 包含期望文案正好印证了在请求解析阶段失败而非静默忽略的设计目标已落地为可回归的测试契约。八、小结本次重构带来的三层收益API 统一一个picker字段覆盖文档、图片、视频、媒体、目录、相机六种输入源ToolPkg 作者无需再区分document/photo两套规则未发布的photo选项彻底移除错误前置所有非法参数组合非 document 带 MIME、directory/camera 带多选、视觉媒体/相机带持久权限在请求解析阶段即抛出带模式名的明确异常杜绝静默忽略导致的不可预期行为语义清晰的结果契约文件型结果返回暂存path目录型结果仅返回uri并由 TypeScript 声明与单元测试双重固化。对于希望在自己的 ToolPkg 包中集成文件选择能力的开发者可直接参考 compose-dsl.d.ts 的类型定义、ToolPkgComposeDslScreen.kt 的解析与分发实现以及 ComposeDslFilePickerRequestTest.kt 的行为契约三者共同构成了这套选择器 API 的完整规格。赞分享AI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆【免费下载链接】OperitThe most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent项目地址https://gitcode.com/gh_mirrors/op/Operit点击查看免费下载相关推荐Operit Compose DSL 文件选择器模式File Picker Modes实现指南从单一 photo 到六模式 picker 的演进Operit Compose DSL 文件选择器模式File Picker Modes实现指南从单一 photo 到六模式 picker 的演进 导读 本AI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆GUI 自动化推荐一款卓越的Android材料设计文件选择器Material File Picker推荐一款卓越的Android材料设计文件选择器Material File Picker 1、项目介绍 Material File Picker是由Arte aWanAndroid网络层实战Retrofit RxJava 拦截器完整指南WanAndroid网络层实战Retrofit RxJava 拦截器完整指南 在Android开发中构建一个健壮、高效的网络层是每个应用成功的关键。移动开发创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

看完文章,想为自己的企业也做一次专业网站诊断?

尧图顾问免费为您评估现有网站,并给出建站/改版建议与报价方案。

免费获取方案