openharmony开发基础之5.0.1版本文件管理器复制粘贴框架调用流程

概述

本文梳理 OpenHarmony 5.0.1系统上预置文件管理器应用 在执行复制 / 粘贴 / 剪切操作时,涉及的各框架仓、关键 API 及完整调用链,便于定位复制粘贴相关 Bug。

典型产品形态为:上层使用预编译文件管理器 HAP (产品 vendor 仓集成),底层文件 IO 依赖标准 File Access 框架 ,由 FileAccessExtensionAbility 扩展能力在独立进程中实际执行拷贝或移动。

存储路径与 U 盘复制能力 (能从哪拷到哪、支持哪些文件系统)见 第三章


一、参与组件

1.1 文件管理器应用(用户可见)

项目 说明
形态 预编译 HAP,由 vendor/<product>/app/BUILD.gnohos_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++ 桥接)
类型 FileAccessExtensionAbilitytype: 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.fileAccessFileAccessHelper、扩展连接与 IPC
foundation/filemanagement/file_api filemanagement / file_api 提供 @ohos.file.fscopyFileSynccopyDirSyncmoveFileSyncmoveDirSync
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 5010libfile_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()ExternalVolumeInfoFileExtensionAbility.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.cppAllowToCopy
目标已存在同名文件/目录 默认报错 E_EXISTforce=true 时覆盖(mode=1)
部分文件冲突 返回 COPY_NOEXCEPTIONcopyResult 逐条列出冲突项

注意 :上述文件夹能力仅限 docs 内部 (authority 相同);跨 docsmedia 的文件夹复制同样受 §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_):exfatvfat

不支持挂载的文件系统(如 APFS、部分 exFAT 变体)在文件管理器中不可见,自然无法复制。

3.4.3 文件系统带来的隐性限制(非框架过滤)
限制来源 表现
FAT32 单文件 ≤ 4GB 大文件复制失败
FAT 文件名长度 / 非法字符 含 `:*?"<>
U 盘只读挂载 粘贴到 U 盘失败(E_PERM
U 盘拔出 / 卷卸载 GetProxyByUriaccessSync 失败
SELinux 特定路径拒绝访问
3.4.4 应用层可能的额外过滤

预置文件管理器 HAP 在 UI 层可能按 MIME 做预览/图标 (ABC 中有 application/vnd.* 等字符串),但不等于复制接口过滤。若出现「某种格式无法复制」,需区分:

  1. 框架 copy() 返回错误码 → 查 user_file_service / file_api
  2. 仅 UI 不显示该类型 → 查应用层列表过滤
  3. U 盘未挂载或文件系统不支持 → 查 storage_service 挂载日志

3.5 媒体库路径的文件类型(对比)

若复制源为相册/音乐file://media/...),由 MediaFileExtentionUtils::Copy 处理,面向媒体库数据库中的资源:

类型 框架处理
图片 / 视频 / 音频 ✅ 媒体库 Copy 主场景
普通文档文件 ❌ 不在媒体库索引中,不走此路径

媒体库目录复制会先 MkdirCopyDirectoryOperation,与 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 为 fileFILE_SCHEME_NAME),通过 authority 区分扩展:

authority 别名 映射包名 典型路径
docs com.ohos.UserFile.ExternalFileManager file://docs/storage/Usersfile://docs/storage/External
media com.ohos.medialibrary.medialibrarydata file://media/...(媒体库文件)

解析逻辑见 file_access_helper.cppGetBundleNameFromPath()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) 调用链:

  1. FileAccessHelper::Creator(context) --- 校验 IsSystemApp()
  2. GetRegisteredFileAccessExtAbilityInfo() --- 通过 BMS 查询所有 FILEACCESS_EXTENSION 类型扩展
  3. DoCreatorHelper() --- 对每个扩展调用 ConnectFileExtAbility(want),建立 Binder 连接
  4. 以 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 关键前提

  1. 复制(Copy)与粘贴(Paste)是两次独立操作 :点击「复制」时不会 调用 fileAccessHelper.copy(),仅将源 URI 与操作类型写入剪贴板。
  2. 实际文件 IO 发生在粘贴阶段 ,在扩展能力进程(:fileAccess)中执行。
  3. FileAccessHelperCreatorCopyMoveMoveItem 等均要求 IsSystemApp()
  4. Copy()FileAccessHelper 层通过 IsDirectory(destUri) 校验目标为目录;MoveItem() 在扩展内通过 stat.isDirectory() 校验(Helper 层无此检查)。
  5. 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 复制操作(仅写剪贴板)

  1. 用户选中文件/文件夹,点击「复制」或「剪切」。
  2. PasteboardUtil 通过 @ohos.pasteboard 写入源 URI 列表及操作类型标记。
  3. 此阶段无文件 IO,磁盘内容不变。

5.4 粘贴操作(复制后粘贴)

  1. pasteFileFromCopy 从剪贴板读取源 URI。

  2. 确保 fileAccessHelper 已创建且扩展已连接。

  3. 对每个源 URI 调用:

    typescript 复制代码
    await fileAccessHelper.copy(sourceUri, destParentUri, force)
  4. 完整调用链(docs / ExternalFileManager 路径 ;media 扩展不经 JsFileAccessExtAbility,由 MediaFileExtAbility C++ 直连):

    复制代码
    @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
  5. ExternalFileManager.copy() 核心逻辑

    • checkCopyArguments():URI 合法性、源存在、目标是目录、禁止复制到源子目录
    • 单文件:目标已存在且 force=false → 返回 E_EXISTCOPY_NOEXCEPTION
    • 单文件:否则 copyFile() → 先 unlink 已存在目标,再 copyFileSync
    • 目录:copyDirectory()copyDirSync(src, destParent, mode)
      • force=falsemode=0THROWEXCEPTION,冲突抛 E_EXIST 并收集 err.data
      • force=truemode=1FILEOVERWRITE,覆盖冲突文件)

5.5 粘贴操作(剪切后粘贴)

剪切后粘贴应使用 moveItem (与 copy 对称,支持 forcevector<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 相同 ,跨 docsmedia 的剪切在框架层直接返回 errno EPERM(通常 1)

5.6 同名冲突与部分失败

框架定义两种复制返回语义(file_access_helper.cpp):

返回值 含义
COPY_EXCEPTION (-1) 致命错误,终止后续操作
COPY_NOEXCEPTION (-2) 非致命错误(如单个文件 E_EXIST),copyResult 中携带逐项 Result

目录复制冲突时,copyDirSync 抛出 E_EXISTFileExtensionAbility.copyDirectory() 遍历 err.data 逐条写入 copyResult,应用可弹窗让用户选择覆盖(force=true 重试)。

常见错误码(file_access_framework_errno.h / FileExtensionAbility.ts):

错误码 含义
E_PERM / ERR_PERM 13900001 权限不足(扩展 ArkTS 层业务码)
errno EPERM 通常 1 跨 authority 的 Move/MoveItemFileAccessHelper 层直接返回
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() 禁止复制到源子树

copyDirSyncmode0 = 冲突抛错,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::CreatorCopy / Move / MoveItem 均调用 IsSystemAppByFullTokenID()。预置应用若签名或预装配置不正确,会在框架层返回 E_PERMISSION_SYS,表现为 helper 创建失败或粘贴无响应。


八、应用层可能的双路径

部分文件管理器除 fileAccessHelper.copy() 外,在应用进程内直接调用 @ohos.file.fscopyDir / 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 建议排查顺序

  1. FileExtensionAbility.ts --- copy()copyDirectory()checkCopyArguments()
  2. js_file_access_ext_abilitybad_function_call 高发区)
  3. file_api/.../copydir.cpp --- 递归复制与冲突处理
  4. 应用侧 PasteboardUtilpasteFile 传参及 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(docsmedia)。docs ↔ media 的复制在 File Access 框架内同样不可行(各扩展无法解析对端 URI),产品侧跨域复制需走其他 API。

修 Bug 时优先排查 user_file_servicefile_api;跨路径场景需区分 docs / media 扩展、是否跨 authority,以及是否走了应用侧直连 fs 的旁路。

相关推荐
国服第二切图仔2 小时前
HarmonyOS APP《画伴梦工厂》开发第31篇-语音识别实战——SpeechRecognitionEngine+AudioCapturer
语音识别·xcode·harmonyos
TrisighT4 小时前
Electron 鸿蒙 PC 上点外链唤醒应用,我试了 6 种写法只有 1 种能跑
前端·electron·harmonyos
TrisighT5 小时前
Electron 跑鸿蒙 PC 上,这 4 个 API 的行为跟 Windows 完全不一样——但文档一行都没写
windows·electron·harmonyos
蓝速科技7 小时前
蓝速科技 RISC-V 鸿蒙信创工控终端深度评测
科技·harmonyos·risc-v
TrisighT1 天前
DevEco Code 写鸿蒙 ArkTS 确实快,但我试了三天后把默认引擎换成了 Cursor
ai编程·harmonyos·cursor
liz7up1 天前
鸿蒙原生流程图 & 审批流组件 hmflowkit
harmonyos
网易云信2 天前
全框架覆盖!网易智企IM鸿蒙生态适配再进一步
人工智能·aigc·harmonyos
TrisighT2 天前
我用 AI 逆向了 ArkTS @Builder 的编译产物,看完再也不敢乱写嵌套了
ai编程·harmonyos·arkts
ONEDAY3 天前
HarmonyOS 深色模式适配实践:从资源、WebView 到网络图统一处理
harmonyos