概述
本文梳理 OpenHarmony 5.0.1系统上预置文件管理器应用 在执行复制 / 粘贴 / 剪切操作时,涉及的各框架仓、关键 API 及完整调用链,便于定位复制粘贴相关 Bug。
典型产品形态为:上层使用预编译文件管理器 HAP (产品 vendor 仓集成),底层文件 IO 依赖标准 File Access 框架 ,由 FileAccessExtensionAbility 扩展能力在独立进程中实际执行拷贝或移动。
存储路径与 U 盘复制能力 (能从哪拷到哪、支持哪些文件系统)见 第三章。
一、参与组件
1.1 文件管理器应用(用户可见)
| 项目 | 说明 |
|---|---|
| 形态 | 预编译 HAP,由 vendor/<product>/app/BUILD.gn 以 ohos_prebuilt_etc 集成 |
| 安装分区 | 常见为 sys_prod(/sys_prod/app/<bundleName>) |
| 源码 | 产品仓通常无 ArkTS 源码,可通过 HAP 内 module.json、反编译 ABC 字符串分析行为 |
从预置文件管理器 HAP 的 ABC 字符串可确认,复制粘贴相关模块包括:
| 模块 / 类 | 作用 |
|---|---|
FileToolBar.copyFile / pasteFile |
工具栏复制、粘贴入口 |
SearchList.copyFile / pasteFile |
搜索列表中的复制粘贴 |
PasteboardUtil |
剪贴板读写(@ohos.pasteboard) |
pasteFileFromCopy / pasteFileFromCut |
区分「复制后粘贴」与「剪切后粘贴」 |
copyDirectory / copyDir |
应用侧目录复制辅助逻辑(部分场景可能直接调 @ohos.file.fs) |
compressMultipleFiles |
多文件批量处理(粘贴前可能做临时目录整理) |
fileAccessHelper / createFileAccessHelper |
File Access 框架入口 |
GetExternalStorageFile |
外部存储路径处理 |
DistributedDeviceManager |
分布式设备管理(跨设备传文件,与本地粘贴独立) |
1.2 系统外部存储扩展(文档路径主要执行者)
| 项目 | 说明 |
|---|---|
| 包名 | com.ohos.UserFile.ExternalFileManager |
| 源码路径 | foundation/filemanagement/user_file_service/services/file_extension_hap/ |
| 实现语言 | ArkTS(经 JsFileAccessExtAbility C++ 桥接) |
| 类型 | FileAccessExtensionAbility(type: fileAccess) |
| 扩展 URI | fileAccess://docs |
| 文件 URI 前缀 | file://docs/... |
| 进程名 | com.ohos.UserFile.ExternalFileManager:fileAccess |
| 安装位置 | /system/app/com.ohos.UserFile.ExternalFileManager |
1.3 媒体库文件扩展(媒体路径场景)
| 项目 | 说明 |
|---|---|
| 包名 | com.ohos.medialibrary.medialibrarydata |
| 源码路径 | foundation/multimedia/media_library/frameworks/services/media_file_extension/ |
| 实现语言 | Native C++(MediaFileExtAbility,不经 JS 桥接) |
| 文件 URI 前缀 | file://media/... |
| 基类 | FileAccessExtAbility |
两个扩展共用 File Access IPC 框架(FileAccessExtStub / FileAccessExtProxy),但实现层差异很大:ExternalFileManager 走 ArkTS + fs.*Sync,MediaLibraryData 走媒体库数据库与原生文件操作。
二、涉及的框架仓总览
2.1 核心仓(必看)
| 仓路径 | 子系统 / Part | 在复制粘贴中的作用 |
|---|---|---|
foundation/filemanagement/user_file_service |
filemanagement / user_file_service | 核心 :@ohos.file.fileAccess、FileAccessHelper、扩展连接与 IPC |
foundation/filemanagement/file_api |
filemanagement / file_api | 提供 @ohos.file.fs(copyFileSync、copyDirSync、moveFileSync、moveDirSync) |
foundation/filemanagement/storage_service |
filemanagement / storage_service | 外部存储卷挂载、路径管理 |
foundation/ability/ability_runtime |
ability / ability_runtime | AbilityManagerClient::ConnectAbility 连接扩展能力进程 |
foundation/bundlemanager/bundle_framework |
bundlemanager | IBundleMgr::QueryExtensionAbilityInfos 枚举已注册 FileAccess 扩展 |
2.2 辅助仓
| 仓路径 | 子系统 / Part | 在复制粘贴中的作用 |
|---|---|---|
foundation/distributeddatamgr/pasteboard |
distributeddatamgr / pasteboard | 系统剪贴板(@ohos.pasteboard),复制阶段仅缓存元数据 |
base/security/access_token |
security / access_token | 权限校验;FileAccessHelper 要求调用方为系统应用 |
base/security/selinux_adapter |
security / selinux_adapter | 文件路径 SELinux 策略 |
user_file_service 编译产物 file_access_extension_ability_kit |
--- | JS 扩展桥接库(libfile_access_extension_ability_kit.z.so) |
2.3 FileAccessService(SA 5010)的角色
重要 :copy() / move() / moveItem() 的 IPC 不经过 FileAccessService 转发,而是应用进程通过 AbilityManagerClient 直连扩展能力进程。
FileAccessService(SA 5010 ,libfile_access_service.z.so)主要负责:
| 职责 | 说明 |
|---|---|
| 文件变更通知 | RegisterNotify / UnregisterNotify 等,经 SA 转发到扩展 |
| SA 生命周期 | 按需加载、卸载(file_access_service_proxy.cpp) |
| 通知路径的扩展连接 | SA 侧 GetExtensionProxy / ConnectFileExtAbility(与 Copy 直连路径独立) |
注意 :createFileAccessHelper 建立的 Copy/Move 用 Binder 代理 缓存在应用进程的 FileAccessHelper::cMap_ 中,不是 经 SA 5010 转发;SA 侧另有独立 cMap_,供通知等需走 SA 的接口使用。
配置:user_file_service/services/5010.json
2.4 场景相关仓
| 仓路径 | 场景 |
|---|---|
foundation/multimedia/media_library |
媒体库 URI 的复制 / 移动(media_file_extension) |
extension/distributed/distributed_agent |
跨设备文件传输(厂商扩展,非本地剪贴板粘贴路径) |
applications/standard/filepicker |
文件选择器,不直接参与复制粘贴 |
foundation/filemanagement/dfs_service |
分布式文件系统,本地复制一般不经过 |
vendor/<product> |
预置 HAP 集成、安装列表与权限白名单 |
三、存储路径与复制粘贴能力范围
本章说明文件/文件夹能从哪复制到哪 、U 盘场景的限制,以及框架层与应用层的差异。能力划分以 OpenHarmony 源码为准(FileExtensionAbility.getRoots()、ExternalVolumeInfo、FileExtensionAbility.copy())。
3.1 文件管理器可见的存储根目录
ExternalFileManager 通过 getRoots() 暴露以下逻辑根(均为 file://docs/...,authority = docs):
| 逻辑路径 | URI 示例 | 设备类型 | 说明 |
|---|---|---|---|
/storage/Users/currentUser |
file://docs/storage/Users/currentUser |
本地磁盘 | 当前用户文档目录(下载、文档等) |
/storage/Share |
file://docs/storage/Share |
共享磁盘 | 共享存储区 |
/storage/External/{卷名} |
file://docs/storage/External/{卷名} |
USB 外部存储 | U 盘/移动硬盘,插入后动态出现 |
/storage/hmdfs/{节点名} |
file://docs/storage/hmdfs/{节点名} |
共享终端 | 分布式文件(HMDFS),若系统启用 |
物理挂载关系(storage_service):
U 盘设备 → 挂载到 /mnt/data/external/{UUID}
→ 文件管理器进程内映射为 /storage/External/{卷名}
→ URI 为 file://docs/storage/External/{卷名}/...
媒体库为独立根 (authority = media),不在上述 getRoots() 列表中,由 MediaLibraryData 扩展管理(相册、音乐等)。
3.2 复制:支持的源 → 目标组合
框架层 fileAccessHelper.copy(sourceUri, destParentUri, force) 按源 URI 选择扩展;单文件与文件夹共用此入口。docs 路径下复制/剪切均由 ExternalFileManager 执行,单文件底层为 fs.copyFileSync,目录为 fs.copyDirSync,不按扩展名白名单过滤。
3.2.1 docs 路径内部(推荐路径,完全支持)
以下路径 authority 均为 docs,复制与剪切均支持:
| 源 | 目标(目录) | 复制 | 剪切(moveItem) |
|---|---|---|---|
本地用户目录 Users/currentUser |
本地用户目录 | ✅ | ✅ |
| 本地用户目录 | U 盘 External/{卷名} |
✅ | ✅ |
U 盘 External/{卷名} |
本地用户目录 | ✅ | ✅ |
| U 盘 | U 盘(同卷或不同卷) | ✅ | ✅ |
共享区 Share |
本地 / U 盘 | ✅ | ✅ |
| 本地 / U 盘 | 共享区 Share |
✅ | ✅ |
HMDFS hmdfs/... |
本地 / U 盘 / Share | ✅(同 docs) | ✅ |
3.2.2 涉及媒体库(authority = media)
| 源 | 目标 | 复制 | 剪切 |
|---|---|---|---|
媒体库 file://media/... |
docs 路径(Users / External / Share) | ❌ File Access copy() 不支持 :CopyOperation 按源 URI 选 MediaLibrary 代理,其 GetRelativePathByUri() 仅查媒体库 DB,无法解析 file://docs/... 目标 |
❌ 框架层 errno EPERM(authority 不同,见 §4.3) |
| docs 路径 | 媒体库 file://media/... |
❌ 同样不支持 :ExternalFileManager 的 getPath() 仅处理 authority=docs,无法解析 file://media/... 目标 |
❌ errno EPERM |
| 媒体库 | 媒体库 | ✅(MediaLibrary 扩展内部,InsertFileOperation 写入媒体库索引) |
✅(同 authority) |
结论:
- 「U 盘 ↔ 本地目录」属于 docs 内部 互拷,是 File Access 框架完整支持的场景。
- 「相册/媒体库 → U 盘/本地目录」若走
fileAccessHelper.copy(mediaUri, docsDestUri),会在 MediaLibrary 扩展内失败 ;产品侧若支持该场景,通常需走其他 API (如@ohos.file.photoAccessHelper、@ohos.filemanagement.userFileManager等),而非标准 File Access 跨 authority 复制。 - 「U 盘 → 相册」同理,
fileAccessHelper.copy(docsUri, mediaDestUri)在 ExternalFileManager 内无法解析 media 目标。
3.3 文件夹复制
文件夹粘贴与单文件走同一 copy() 入口,目录分支调用 fs.copyDirSync(srcDir, destParentDir, mode),递归复制全部子文件和子目录。
| 能力 | 说明 |
|---|---|
| U 盘文件夹 → 本地目录 | ✅ 支持,copyDirSync 递归 |
| 本地文件夹 → U 盘 | ✅ 支持 |
| U 盘文件夹 → U 盘另一目录 | ✅ 支持 |
| 嵌套多层子目录 | ✅ 支持(递归) |
| 复制到源目录的子目录 | ❌ 禁止(checkCopyArguments + copydir.cpp 的 AllowToCopy) |
| 目标已存在同名文件/目录 | 默认报错 E_EXIST;force=true 时覆盖(mode=1) |
| 部分文件冲突 | 返回 COPY_NOEXCEPTION,copyResult 逐条列出冲突项 |
注意 :上述文件夹能力仅限 docs 内部 (authority 相同);跨 docs ↔ media 的文件夹复制同样受 §3.2.2 限制。框架不限制文件夹内的文件类型(见 3.4);若粘贴失败,常见原因是 U 盘文件系统限制(见 3.4.3)而非扩展名过滤。
3.4 U 盘文件「格式」说明(框架层 vs 应用层)
3.4.1 框架层:不按业务类型过滤
ExternalFileManager.copy() / copyFile() 没有 MIME 类型或扩展名白名单,只要路径在已挂载的 U 盘卷上且可读,任意文件均可复制,例如:
- 文档:
.txt、.pdf、.doc、.xlsx等 - 媒体:
.jpg、.png、.mp4、.mp3等 - 压缩包:
.zip、.rar等 - 二进制/未知扩展名文件
复制的是文件字节流,与扩展名无关。
3.4.2 U 盘可识别的前提:文件系统格式
U 盘能否在文件管理器中显示并复制,取决于 storage_service 能否挂载。支持的文件系统(external_volume_info.h):
| 文件系统 | 挂载支持 | 典型 U 盘场景 |
|---|---|---|
| vfat / fat32 | ✅ | 最常见 FAT32 U 盘 |
| exfat | ✅ | 大文件 U 盘 |
| ntfs | ✅ | Windows NTFS 移动硬盘 |
| ext2 / ext3 / ext4 | ✅ | Linux 格式化磁盘 |
| 其他类型 | ⚠️ 尝试 DoMount4OtherType,不保证成功 |
--- |
出厂格式化支持(supportFormatType_):exfat 、vfat。
不支持挂载的文件系统(如 APFS、部分 exFAT 变体)在文件管理器中不可见,自然无法复制。
3.4.3 文件系统带来的隐性限制(非框架过滤)
| 限制来源 | 表现 |
|---|---|
| FAT32 单文件 ≤ 4GB | 大文件复制失败 |
| FAT 文件名长度 / 非法字符 | 含 `:*?"<> |
| U 盘只读挂载 | 粘贴到 U 盘失败(E_PERM) |
| U 盘拔出 / 卷卸载 | GetProxyByUri 或 accessSync 失败 |
| SELinux | 特定路径拒绝访问 |
3.4.4 应用层可能的额外过滤
预置文件管理器 HAP 在 UI 层可能按 MIME 做预览/图标 (ABC 中有 application/vnd.* 等字符串),但不等于复制接口过滤。若出现「某种格式无法复制」,需区分:
- 框架
copy()返回错误码 → 查user_file_service/file_api - 仅 UI 不显示该类型 → 查应用层列表过滤
- U 盘未挂载或文件系统不支持 → 查
storage_service挂载日志
3.5 媒体库路径的文件类型(对比)
若复制源为相册/音乐 (file://media/...),由 MediaFileExtentionUtils::Copy 处理,面向媒体库数据库中的资源:
| 类型 | 框架处理 |
|---|---|
| 图片 / 视频 / 音频 | ✅ 媒体库 Copy 主场景 |
| 普通文档文件 | ❌ 不在媒体库索引中,不走此路径 |
媒体库目录复制会先 Mkdir 再 CopyDirectoryOperation,与 ExternalFileManager 的纯 copyDirSync 实现不同。
3.6 复制能力速查表
┌─────────────────────────────────────────┐
│ 粘贴目标(目录) │
├──────────┬──────────┬─────────┬────────┤
复制源 │ Users │ U 盘 │ Share │ media │
──────────────────┼──────────┼──────────┼─────────┼────────┤
本地 Users │ ✅复制/剪切 │ ✅复制/剪切 │ ✅复制/剪切 │ ❌跨authority │
U 盘 External │ ✅复制/剪切 │ ✅复制/剪切 │ ✅复制/剪切 │ ❌跨authority │
Share │ ✅复制/剪切 │ ✅复制/剪切 │ ✅复制/剪切 │ ❌跨authority │
media 媒体库 │ ❌跨authority │ ❌跨authority │ ❌跨authority │ ✅复制/剪切 │
└──────────┴──────────┴─────────┴────────┘
✅复制/剪切 = docs 内部,copy + moveItem 均可用
❌跨authority = fileAccessHelper.copy/moveItem 在框架层无法完成(见 §3.2.2、§4.3)
产品若支持「相册→U盘」等场景,需确认是否走了 File Access 以外的 API
四、URI 规范与扩展路由
4.1 URI 格式
框架要求 URI scheme 为 file(FILE_SCHEME_NAME),通过 authority 区分扩展:
| authority 别名 | 映射包名 | 典型路径 |
|---|---|---|
docs |
com.ohos.UserFile.ExternalFileManager |
file://docs/storage/Users、file://docs/storage/External |
media |
com.ohos.medialibrary.medialibrarydata |
file://media/...(媒体库文件) |
解析逻辑见 file_access_helper.cpp 中 GetBundleNameFromPath() 与 GetProxyByUri():
cpp
// 拼接路径:"/" + uri.GetAuthority() + uri.GetPath()
// 例:file://docs/storage/Users/currentUser → /docs/storage/Users/currentUser
// 取首段 authority:docs → ExternalFileManager,media → medialibrarydata
sptr<IFileAccessExtBase> proxy = GetProxyByUri(sourceUri);
4.2 createFileAccessHelper 初始化
createFileAccessHelper(context) 调用链:
FileAccessHelper::Creator(context)--- 校验IsSystemApp()GetRegisteredFileAccessExtAbilityInfo()--- 通过 BMS 查询所有FILEACCESS_EXTENSION类型扩展DoCreatorHelper()--- 对每个扩展调用ConnectFileExtAbility(want),建立 Binder 连接- 以 bundleName 为 key 存入
cMap_,后续GetProxyByUri按 URI 查表
连接实现:services/native/file_access_service/src/file_access_ext_connection.cpp
cpp
AbilityManagerClient::GetInstance()->ConnectAbility(want, this, userId);
// 回调 OnAbilityConnectDone 获取 FileAccessExtProxy
因此粘贴前必须成功完成扩展连接;应用侧常见 isFileAccessServiceReady / ensureFileAccessDirsReady 等就绪检查。
4.3 跨扩展复制 vs 跨扩展移动
| 操作 | 框架层限制 | 说明 |
|---|---|---|
| Copy | CopyOperation 仅按源 URI 选 Proxy,不校验 authority 是否相同 |
框架不拦截跨 authority,但各扩展只能解析本 authority 的 URI (docs 的 getPath()、media 的 GetRelativePathByUri()),故 docs ↔ media 的 copy() 实际会失败 |
| Move / MoveItem | sourceUri.GetAuthority() != targetParentUri.GetAuthority() 时返回 errno EPERM(通常为 1) |
不支持 跨 authority 移动;此处的 EPERM 是 POSIX errno,不是 框架业务码 E_PERM/13900001 |
cpp
// file_access_helper.cpp - Move / MoveItem 均有此检查
if (sourceFileUri.GetAuthority() != targetParentUri.GetAuthority()) {
HILOG_WARN("Operation failed, move not supported");
return EPERM; // errno EPERM(通常 1),非 13900001
}
五、复制粘贴完整调用链
5.1 关键前提
- 复制(Copy)与粘贴(Paste)是两次独立操作 :点击「复制」时不会 调用
fileAccessHelper.copy(),仅将源 URI 与操作类型写入剪贴板。 - 实际文件 IO 发生在粘贴阶段 ,在扩展能力进程(
:fileAccess)中执行。 FileAccessHelper的Creator、Copy、Move、MoveItem等均要求IsSystemApp()。Copy()在FileAccessHelper层通过IsDirectory(destUri)校验目标为目录;MoveItem()在扩展内通过stat.isDirectory()校验(Helper 层无此检查)。- IPC 路径为:应用进程 → FileAccessExtProxy → 扩展进程 FileAccessExtStub,不经 SA 5010 中转。
5.2 流程总览
#mermaid-svg-PNck31iVF678PbLL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PNck31iVF678PbLL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PNck31iVF678PbLL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PNck31iVF678PbLL .error-icon{fill:#552222;}#mermaid-svg-PNck31iVF678PbLL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PNck31iVF678PbLL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PNck31iVF678PbLL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PNck31iVF678PbLL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PNck31iVF678PbLL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PNck31iVF678PbLL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PNck31iVF678PbLL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PNck31iVF678PbLL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PNck31iVF678PbLL .marker.cross{stroke:#333333;}#mermaid-svg-PNck31iVF678PbLL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PNck31iVF678PbLL p{margin:0;}#mermaid-svg-PNck31iVF678PbLL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PNck31iVF678PbLL .cluster-label text{fill:#333;}#mermaid-svg-PNck31iVF678PbLL .cluster-label span{color:#333;}#mermaid-svg-PNck31iVF678PbLL .cluster-label span p{background-color:transparent;}#mermaid-svg-PNck31iVF678PbLL .label text,#mermaid-svg-PNck31iVF678PbLL span{fill:#333;color:#333;}#mermaid-svg-PNck31iVF678PbLL .node rect,#mermaid-svg-PNck31iVF678PbLL .node circle,#mermaid-svg-PNck31iVF678PbLL .node ellipse,#mermaid-svg-PNck31iVF678PbLL .node polygon,#mermaid-svg-PNck31iVF678PbLL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PNck31iVF678PbLL .rough-node .label text,#mermaid-svg-PNck31iVF678PbLL .node .label text,#mermaid-svg-PNck31iVF678PbLL .image-shape .label,#mermaid-svg-PNck31iVF678PbLL .icon-shape .label{text-anchor:middle;}#mermaid-svg-PNck31iVF678PbLL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PNck31iVF678PbLL .rough-node .label,#mermaid-svg-PNck31iVF678PbLL .node .label,#mermaid-svg-PNck31iVF678PbLL .image-shape .label,#mermaid-svg-PNck31iVF678PbLL .icon-shape .label{text-align:center;}#mermaid-svg-PNck31iVF678PbLL .node.clickable{cursor:pointer;}#mermaid-svg-PNck31iVF678PbLL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PNck31iVF678PbLL .arrowheadPath{fill:#333333;}#mermaid-svg-PNck31iVF678PbLL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PNck31iVF678PbLL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PNck31iVF678PbLL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PNck31iVF678PbLL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PNck31iVF678PbLL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PNck31iVF678PbLL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PNck31iVF678PbLL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PNck31iVF678PbLL .cluster text{fill:#333;}#mermaid-svg-PNck31iVF678PbLL .cluster span{color:#333;}#mermaid-svg-PNck31iVF678PbLL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PNck31iVF678PbLL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PNck31iVF678PbLL rect.text{fill:none;stroke-width:0;}#mermaid-svg-PNck31iVF678PbLL .icon-shape,#mermaid-svg-PNck31iVF678PbLL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PNck31iVF678PbLL .icon-shape p,#mermaid-svg-PNck31iVF678PbLL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PNck31iVF678PbLL .icon-shape .label rect,#mermaid-svg-PNck31iVF678PbLL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PNck31iVF678PbLL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PNck31iVF678PbLL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PNck31iVF678PbLL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 底层
扩展能力进程 :fileAccess
Binder IPC
应用进程 · FileAccessHelper
文件管理器应用进程
复制后粘贴
剪切后粘贴
docs
media
初始化(粘贴前)
createFileAccessHelper(context)
BMS 查询 FILEACCESS_EXTENSION
ConnectAbility 连接各扩展
cMap_ 缓存 FileAccessExtProxy
复制/剪切
PasteboardUtil → @ohos.pasteboard
粘贴
读取剪贴板 URI 列表
操作类型
fileAccessHelper.copy(uri, destDir, force)
fileAccessHelper.moveItem(uri, destDir, force)
GetProxyByUri(源URI)
FileAccessExtProxy::Copy / MoveItem
扩展进程 FileAccessExtStub::CmdCopy / CmdMoveItem
扩展类型
JsFileAccessExtAbility → FileExtensionAbility.ts
MediaFileExtAbility → MediaFileExtentionUtils.cpp
fs.copyFileSync / copyDirSync / moveFileSync / moveDirSync
媒体库 DB + 原生文件 IO
file_api Native
内核 + storage_service 卷挂载
5.3 复制操作(仅写剪贴板)
- 用户选中文件/文件夹,点击「复制」或「剪切」。
PasteboardUtil通过@ohos.pasteboard写入源 URI 列表及操作类型标记。- 此阶段无文件 IO,磁盘内容不变。
5.4 粘贴操作(复制后粘贴)
-
pasteFileFromCopy从剪贴板读取源 URI。 -
确保
fileAccessHelper已创建且扩展已连接。 -
对每个源 URI 调用:
typescriptawait fileAccessHelper.copy(sourceUri, destParentUri, force) -
完整调用链(docs / ExternalFileManager 路径 ;media 扩展不经
JsFileAccessExtAbility,由MediaFileExtAbilityC++ 直连):@ohos.file.fileAccess (NAPI_Copy) → FileAccessHelper::Copy() → IsSystemApp() / CheckUri() / IsDirectory(dest) → CopyOperation() → GetProxyByUri(sourceUri) → FileAccessExtProxy::Copy() [应用进程 → Binder] → FileAccessExtStub::CmdCopy() [扩展进程] → FileAccessExtStubImpl::Copy() → JsFileAccessExtAbility::Copy() [仅 JS 扩展] → FileExtensionAbility.copy() [ArkTS] → fs.copyFileSync / fs.copyDirSync -
ExternalFileManager.copy() 核心逻辑:
checkCopyArguments():URI 合法性、源存在、目标是目录、禁止复制到源子目录- 单文件:目标已存在且
force=false→ 返回E_EXIST(COPY_NOEXCEPTION) - 单文件:否则
copyFile()→ 先unlink已存在目标,再copyFileSync - 目录:
copyDirectory()→copyDirSync(src, destParent, mode)force=false→mode=0(THROWEXCEPTION,冲突抛E_EXIST并收集err.data)force=true→mode=1(FILEOVERWRITE,覆盖冲突文件)
5.5 粘贴操作(剪切后粘贴)
剪切后粘贴应使用 moveItem (与 copy 对称,支持 force 和 vector<Result>),而非单纯的 move:
typescript
await fileAccessHelper.moveItem(sourceUri, destParentUri, force)
| API | 签名特点 | 典型用途 |
|---|---|---|
move(source, destParent, newFile) |
输出新文件 URI,同 authority 限制 | 单文件移动 |
moveItem(source, destParent, force) |
返回 Result[],支持 force |
粘贴/批量剪切(与 copy 对称) |
moveFile(source, destParent, fileName) |
指定目标文件名 | 重命名式移动 |
扩展侧 FileExtensionAbility.moveItem() 分发到 moveForFile() / moveDirectory(),底层 fs.moveFileSync / fs.moveDirSync。
注意 :move / moveItem 均要求源与目标 authority 相同 ,跨 docs ↔ media 的剪切在框架层直接返回 errno EPERM(通常 1)。
5.6 同名冲突与部分失败
框架定义两种复制返回语义(file_access_helper.cpp):
| 返回值 | 含义 |
|---|---|
COPY_EXCEPTION (-1) |
致命错误,终止后续操作 |
COPY_NOEXCEPTION (-2) |
非致命错误(如单个文件 E_EXIST),copyResult 中携带逐项 Result |
目录复制冲突时,copyDirSync 抛出 E_EXIST,FileExtensionAbility.copyDirectory() 遍历 err.data 逐条写入 copyResult,应用可弹窗让用户选择覆盖(force=true 重试)。
常见错误码(file_access_framework_errno.h / FileExtensionAbility.ts):
| 错误码 | 值 | 含义 |
|---|---|---|
E_PERM / ERR_PERM |
13900001 | 权限不足(扩展 ArkTS 层业务码) |
errno EPERM |
通常 1 | 跨 authority 的 Move/MoveItem 在 FileAccessHelper 层直接返回 |
E_EXIST / ERR_EXIST |
13900015 | 目标已存在 |
E_URIS |
14300002 | URI 格式错误 |
E_PERMISSION_SYS |
--- | 非系统应用 |
E_FAULT |
13900013 | 复制到源目录子树 |
六、各仓关键源码路径
6.1 user_file_service(File Access 框架)
foundation/filemanagement/user_file_service/
├── frameworks/js/napi/file_access_module/
│ ├── native_fileaccess_module.cpp # 模块注册
│ └── napi_fileaccess_helper.cpp # createFileAccessHelper / copy / moveItem
│
├── interfaces/inner_api/file_access/
│ ├── src/file_access_helper.cpp # Creator / Copy / Move / MoveItem / GetProxyByUri
│ ├── src/file_access_ext_proxy.cpp # IPC 客户端(CMD_COPY / CMD_MOVE_ITEM)
│ ├── src/file_access_ext_stub.cpp # IPC 服务端 Stub
│ ├── src/file_access_ext_stub_impl.cpp # Stub → Extension 转发
│ └── include/js_file_access_ext_ability.h
│
├── services/
│ ├── native/file_access_service/
│ │ ├── src/file_access_service.cpp # SA 5010(通知、生命周期)
│ │ └── src/file_access_ext_connection.cpp # ConnectAbility
│ ├── 5010.json
│ └── file_extension_hap/ # ExternalFileManager
│ └── .../FileExtensionAbility.ts
│
└── utils/
├── file_access_framework_errno.h
└── file_util.h # CopyAndDeleteFile(rename 失败时 copy+delete)
6.2 file_api(底层文件 IO)
foundation/filemanagement/file_api/interfaces/kits/js/src/mod_fs/properties/
├── prop_n_exporter.cpp # copyFileSync / copyDirSync / moveFileSync / moveDirSync
├── copy.cpp # 单文件复制
└── copydir.cpp # 递归目录复制;AllowToCopy() 禁止复制到源子树
copyDirSync 的 mode:0 = 冲突抛错,1 = 覆盖(与 ExternalFileManager 中 THROWEXCEPTION / FILEOVERWRITE 对应)。
6.3 media_library(媒体库扩展)
foundation/multimedia/media_library/frameworks/services/media_file_extension/
├── src/media_file_ext_ability.cpp # Copy / Move 入口
└── src/media_file_extention_utils.cpp # Copy / CopyDirectoryOperation / CopyFileOperation
媒体库目录复制会先 Mkdir 建目标目录,再递归 CopyDirectoryOperation;与 ExternalFileManager 的纯 fs.copyDirSync 路径不同。
6.4 pasteboard / storage_service
foundation/distributeddatamgr/pasteboard/ # @ohos.pasteboard
foundation/filemanagement/storage_service/ # 外部卷挂载(external_volume_info.cpp)
七、权限与预置配置
7.1 文件管理器应用常见权限
| 权限 | 用途 |
|---|---|
ohos.permission.FILE_ACCESS_MANAGER |
调用 File Access 框架(必需) |
ohos.permission.STORAGE_MANAGER |
存储管理 |
ohos.permission.READ_IMAGEVIDEO / WRITE_IMAGEVIDEO |
图片视频 |
ohos.permission.READ_AUDIO / WRITE_AUDIO |
音频 |
ohos.permission.READ_MEDIA |
媒体文件 |
ohos.permission.ACCESS_DLP_FILE |
DLP 加密文件 |
ohos.permission.PROXY_AUTHORIZATION_URI |
URI 代理授权 |
ohos.permission.DISTRIBUTED_DATASYNC |
分布式同步 |
预置白名单:vendor/<product>/preinstall_config/*/install_list_permissions.json
install_list_capability.json 中需配置 allowAppUsePrivilegeExtension,否则无法正常使用扩展特权。
7.2 ExternalFileManager 扩展权限
扩展 HAP(user_file_service/services/file_extension_hap/)在 module.json 中声明存储相关权限,扩展进程 :fileAccess 实际执行 fs.copyFileSync / copyDirSync 等 IO。若粘贴失败且错误来自扩展进程,需同时检查扩展侧权限与 SELinux 上下文。
7.3 系统应用校验
FileAccessHelper::Creator 与 Copy / Move / MoveItem 均调用 IsSystemAppByFullTokenID()。预置应用若签名或预装配置不正确,会在框架层返回 E_PERMISSION_SYS,表现为 helper 创建失败或粘贴无响应。
八、应用层可能的双路径
部分文件管理器除 fileAccessHelper.copy() 外,在应用进程内直接调用 @ohos.file.fs 的 copyDir / copyDirectory(ABC 字符串可见)。此路径:
- 不经过 File Access 扩展进程
- 受应用沙箱与权限约束,通常只能操作已授权路径
- 与扩展路径行为、错误处理不一致,排查时需确认实际走的是哪条路径
九、已知问题线索(复制粘贴文件夹崩溃)
| 项目 | 典型值 |
|---|---|
| 崩溃进程 | com.ohos.UserFile.ExternalFileManager:fileAccess |
| 异常类型 | std::bad_function_call |
| 涉及库 | libfile_access_extension_ability_kit.z.so |
| 并发线索 | 其他线程可能处于 JsFileAccessExtAbility::Mkdir |
说明:文件夹粘贴时,问题更可能出在 JS 扩展桥接层 (JsFileAccessExtAbility 调用 ArkTS 回调时 std::function 未绑定),而非应用 UI 本身。
9.1 建议排查顺序
FileExtensionAbility.ts---copy()、copyDirectory()、checkCopyArguments()js_file_access_ext_ability(bad_function_call高发区)file_api/.../copydir.cpp--- 递归复制与冲突处理- 应用侧
PasteboardUtil、pasteFile传参及force重试逻辑
9.2 调试建议
bash
hilog | grep -iE "ExternalFileManager|FileAccess|copyFile|copyDir|moveItem|MoveItem|bad_function"
# 关注进程
# <文件管理器 bundleName> --- UI 进程
# com.ohos.UserFile.ExternalFileManager:fileAccess --- External 扩展进程
# com.ohos.medialibrary.medialibrarydata:fileAccess --- 媒体扩展进程(若涉及)
十、与标准示例的对比
官方示例路径:applications/standard/app_samples/code/BasicFeature/FileManagement/FileManager/
| 对比项 | 官方示例 | 典型预置文件管理器 |
|---|---|---|
| 主要 API | @ohos.filemanagement.userFileManager |
@ohos.file.fileAccess |
| 访问范围 | 媒体库 | 文档存储、外部存储、用户目录 |
| 底层执行 | MediaLibrary 数据能力 | FileAccessExtensionAbility |
| 剪贴板 | 未涉及 | @ohos.pasteboard |
| 扩展连接 | 不需要 | createFileAccessHelper + ConnectAbility |
十一、总结
| 层次 | 组件 | 仓 |
|---|---|---|
| 应用 UI | 预置文件管理器 HAP | vendor/<product> |
| 剪贴板 | @ohos.pasteboard |
foundation/distributeddatamgr/pasteboard |
| JS API | @ohos.file.fileAccess |
user_file_service |
| 客户端调度 | FileAccessHelper |
user_file_service |
| 扩展连接 | AppFileAccessExtConnection |
user_file_service + ability_runtime |
| 扩展注册查询 | BMS QueryExtensionAbilityInfos |
bundlemanager |
| 扩展执行(docs) | ExternalFileManager(ArkTS) | user_file_service |
| 扩展执行(media) | MediaLibraryData(C++) | media_library |
| JS 桥接 | libfile_access_extension_ability_kit.z.so |
user_file_service |
| 底层 IO | @ohos.file.fs |
file_api |
| 存储挂载 | 外部卷 | storage_service |
| 文件变更通知 | FileAccessService SA 5010 | user_file_service |
| 权限 | Token / SELinux | access_token / selinux_adapter |
本地复制粘贴核心链路:
剪贴板(复制阶段)→
createFileAccessHelper(连接扩展)→fileAccessHelper.copy()(粘贴)→GetProxyByUri→ Binder IPC → 扩展能力 →@ohos.file.fs→ 内核
剪切粘贴 将 copy() 换为 moveItem(),且源/目标必须同一 authority(docs 或 media)。docs ↔ media 的复制在 File Access 框架内同样不可行(各扩展无法解析对端 URI),产品侧跨域复制需走其他 API。
修 Bug 时优先排查 user_file_service 与 file_api;跨路径场景需区分 docs / media 扩展、是否跨 authority,以及是否走了应用侧直连 fs 的旁路。