openharmony北向开发基础之访问公共文件目录

OpenHarmony 公共目录访问 Demo 技术文档

1. 文档目标

本文档用于说明当前 DirAccess 项目在 OpenHarmony 5.0.1 镜像上的公共目录访问实现方案,重点覆盖:

  • 项目适用范围与前提条件
  • 项目级权限配置
  • 核心代码接口说明
  • Desktop/Documents/DownloadPhoto/Videos/Audio 的差异化实现
  • 系统应用与普通应用的能力边界
  • 常见问题与排障建议

2. 适用范围与前提

  • 系统版本:OpenHarmony 5.0.1(当前工程配置使用 API 14)
  • 工程类型:Stage 模型 ArkTS 应用
  • 核心前提 :本 Demo 设计为系统应用场景
    • Desktop/Documents/Download 自动枚举依赖系统能力(fileAccess 相关)
    • 应用需具备系统签名/系统授权能力

说明:普通三方应用在本方案下通常无法稳定实现 Desktop/Documents/Download 的无交互全量枚举。

3. 项目结构(与本能力相关)

  • 页面入口:entry/src/main/ets/pages/Index.ets
  • 目录工具:entry/src/main/ets/utils/CommonDirectoryUtil.ets
  • 模块权限:entry/src/main/module.json5
  • 编译配置:build-profile.json5

4. 项目级配置说明

4.1 OpenHarmony 构建目标

build-profile.json5 中关键配置:

  • runtimeOS: OpenHarmony
  • compileSdkVersion: 14
  • compatibleSdkVersion: 14
  • targetSdkVersion: 14

这保证工程按 OpenHarmony API 能力编译与运行。

4.2 模块权限配置(entry/src/main/module.json5

当前实现使用两类权限:

A. 系统权限(system_grant,系统应用能力)
  • ohos.permission.FILE_ACCESS_MANAGER
  • ohos.permission.GET_BUNDLE_INFO_PRIVILEGED

用途:支持 fileAccess 对文档类公共目录 URI 的自动访问与枚举能力。

B. 用户授权权限(user_grant)
  • ohos.permission.READ_WRITE_DESKTOP_DIRECTORY
  • ohos.permission.READ_WRITE_DOCUMENTS_DIRECTORY
  • ohos.permission.READ_WRITE_DOWNLOAD_DIRECTORY
  • ohos.permission.READ_IMAGEVIDEO
  • ohos.permission.WRITE_IMAGEVIDEO
  • ohos.permission.READ_AUDIO
  • ohos.permission.WRITE_AUDIO
  • ohos.permission.READ_MEDIA

注意事项:

  • user_grant 权限在 module.json5 中必须配置 reasonusedScene
  • 系统权限不走普通用户弹窗授权流程,由系统应用能力与签名体系决定是否生效
C. module.json5
复制代码
{
  "module": {
    "name": "entry",
    "type": "entry",
    "description": "$string:module_desc",
    "mainElement": "EntryAbility",
    "deviceTypes": [
      "default",
      "tablet"
    ],
    "deliveryWithInstall": true,
    "installationFree": false,
    "requestPermissions": [
      {
        "name": "ohos.permission.FILE_ACCESS_MANAGER"
      },
      {
        "name": "ohos.permission.GET_BUNDLE_INFO_PRIVILEGED"
      },
      {
        "name": "ohos.permission.READ_WRITE_DOCUMENTS_DIRECTORY",
        "reason": "$string:perm_reason_documents",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.READ_WRITE_DOWNLOAD_DIRECTORY",
        "reason": "$string:perm_reason_download",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.READ_WRITE_DESKTOP_DIRECTORY",
        "reason": "$string:perm_reason_desktop",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.READ_IMAGEVIDEO",
        "reason": "$string:perm_reason_image_video",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.WRITE_IMAGEVIDEO",
        "reason": "$string:perm_reason_image_video",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.READ_AUDIO",
        "reason": "$string:perm_reason_audio",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.WRITE_AUDIO",
        "reason": "$string:perm_reason_audio",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.READ_MEDIA",
        "reason": "$string:perm_reason_media",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "inuse"
        }
      }
    ],
    "pages": "$profile:main_pages",
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "description": "$string:EntryAbility_desc",
        "icon": "$media:layered_image",
        "label": "$string:EntryAbility_label",
        "startWindowIcon": "$media:startIcon",
        "startWindowBackground": "$color:start_window_background",
        "exported": true,
        "skills": [
          {
            "entities": [
              "entity.system.home"
            ],
            "actions": [
              "action.system.home"
            ]
          }
        ]
      }
    ],
    "extensionAbilities": [
      {
        "name": "EntryBackupAbility",
        "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
        "type": "backup",
        "exported": false,
        "metadata": [
          {
            "name": "ohos.extension.backup",
            "resource": "$profile:backup_config"
          }
        ]
      }
    ]
  }
}

5. 页面行为说明(Index.ets

页面提供 6 个按钮:

  • Desktop
  • Documents
  • Download
  • Photo
  • Videos
  • Audio

点击按钮后的统一流程:

  1. 调用 CommonDirectoryUtil.ensureAllPermissions(context) 检查/申请权限
  2. 调用 CommonDirectoryUtil.listDirectoryEntries(context, dirName) 获取目录内容
  3. 展示解析路径、条目列表、最近调试日志

页面主要状态字段:

  • currentDirName: 当前目录名称
  • currentPath: 当前解析到的路径或标识
  • status: 当前操作状态(成功/失败)
  • items: 目录项名称数组
  • debugLogs: 调试日志(来自工具类)

6. 核心工具类接口说明(CommonDirectoryUtil.ets

6.1 公开接口

ensureAllPermissions(context: common.UIAbilityContext): Promise<boolean>

作用:

  • 校验 user_grant 权限是否已授予
  • 对未授予的 user_grant 权限触发 requestPermissionsFromUser
  • 校验系统权限状态并写入日志(不进行用户弹窗申请)

返回:

  • true: 当前必要权限满足
  • false: 存在权限缺失或授权失败

listDirectoryEntries(context: common.UIAbilityContext, dirName: string): Promise<DirListResult>

作用:按目录类型路由到不同实现。

  • Desktop/Documents/Download
    • 优先走 fileAccess URI 自动枚举(系统应用方案)
    • 失败时回退到 fs + 环境路径/候选路径 探测
  • Photo/Videos/Audio
    • mediaLibrary 查询媒体资产并返回 displayName 列表

返回结构 DirListResult

  • resolvedPath: string
    • 成功时:命中的目录路径或 URI
    • 失败时:空字符串
  • items: string[]
    • 目录项名称列表(或媒体名称列表)

getDebugLogs(): string[]

作用:返回最近缓存的调试日志,供页面展示与问题排查。


resetCache(): void

作用:清空各目录缓存路径,强制下次重新探测。

6.2 内部实现策略(关键点)

A. 文档类目录(Desktop/Documents/Download)

优先策略(系统应用)fileAccess URI 访问

  • Desktop: file://docs/storage/Users/currentUser/Desktop
  • Documents: file://docs/storage/Users/currentUser/Documents
  • Download: file://docs/storage/Users/currentUser/Download(含 Downloads 兜底)

执行链路:

  1. fileAccess.createFileAccessHelper(context)
  2. getFileInfoFromUri(uri)
  3. listFile() + 迭代器 next() 收集 fileName

回退策略(兼容)environment + fs 路径探测

  • 先尝试 environment.getUserDesktopDir/getUserDocumentDir/getUserDownloadDir
  • 再尝试预置路径候选并用 fs.listFileSync/statSync/accessSync 验证

B. 媒体类目录(Photo/Videos/Audio)

通过 mediaLibrary 查询对应 MediaType

  • Photo -> IMAGE
  • Videos -> VIDEO
  • Audio -> AUDIO

再提取媒体资产名称列表并返回。

7. 系统应用 vs 普通应用能力边界

7.1 当前方案结论

  • 系统应用:可按当前 Demo 思路自动枚举 6 类目录(文档类依赖系统能力)
  • 普通应用
    • 通常仅能稳定处理 Photo/Videos/Audio(媒体库能力)
    • Desktop/Documents/Download 的"无交互自动枚举"不可靠或不可用

7.2 为什么普通应用受限

文档类目录自动枚举在不同设备镜像上受以下因素影响:

  • 环境能力是否开放(environment 可能返回能力不支持)
  • 目录挂载是否对普通应用可见
  • fileAccess 相关能力是否需要系统权限

因此普通应用一般需要采用"用户选择授权(Picker)"模式,而非固定路径自动扫描。

8. 日志与排障建议

工具类日志统一前缀:[CommonDirectoryUtil],建议重点关注:

  • system permission ... : 0/-1
    判断系统权限是否生效
  • requesting permissionsauthResults
    判断 user_grant 权限是否通过
  • start fileAccess listing ... / fileAccess success / fileAccess failed
    判断文档类目录系统链路是否可用
  • environment path resolve failed ...
    判断环境能力是否支持
  • all candidates failed for dir=...
    判断回退路径是否全部不可达

排障顺序建议:

  1. 先看系统权限状态日志是否为 PERMISSION_GRANTED(0)
  2. 再看 fileAccess 是否命中 URI
  3. 再看 environment/路径候选是否均失败
  4. 最后确认镜像版本、系统应用签名、权限预授权策略

9. 二次开发建议

  • 若目标是产品化系统工具:保留 fileAccess 主链路,增加更多 URI 候选与更细粒度错误码映射
  • 若目标是三方应用上架:建议将文档类目录切换为用户选择授权模式,不建议继续硬编码路径探测
  • 建议将媒体能力逐步迁移到更新 API(减少 deprecated 告警)

10. 快速使用说明(开发者)

  1. 确认工程为 OpenHarmony 构建目标
  2. 确认 module.json5 已包含本文档列出的权限
  3. 确认应用具备系统应用能力(用于文档类目录自动枚举)
  4. 启动应用,点击 6 个目录按钮
  5. 通过页面底部"调试日志(最近)"确认实际访问链路与结果

11. Index.ets

复制代码
import common from '@ohos.app.ability.common';
import { CommonDirectoryUtil } from '../utils/CommonDirectoryUtil';

@Entry
@Component
struct Index {
    @State currentDirName: string = '未选择目录';
    @State currentPath: string = '';
    @State status: string = '请点击按钮查看目录内容';
    @State items: string[] = [];
    @State debugLogs: string[] = [];

    private async showDirectory(dirName: string): Promise<void> {
        const context = getContext(this) as common.UIAbilityContext;
        const permissionGranted = await CommonDirectoryUtil.ensureAllPermissions(context);
        if (!permissionGranted) {
            this.status = '权限未全部授予,无法读取目录';
            this.currentDirName = dirName;
            this.currentPath = '';
            this.items = [];
            this.debugLogs = CommonDirectoryUtil.getDebugLogs();
            return;
        }

        const result = await CommonDirectoryUtil.listDirectoryEntries(context, dirName);

        this.currentDirName = dirName;
        this.currentPath = result.resolvedPath;
        this.debugLogs = CommonDirectoryUtil.getDebugLogs();

        if (!result.resolvedPath) {
            this.status = '未解析到目录路径(请确认设备支持并已授权)';
            this.items = [];
            return;
        }

        this.items = result.items;
        this.status = '共 ' + result.items.length + ' 项';
    }

    @Builder
    private actionButton(name: string) {
        Button(name)
            .fontSize(15)
            .width('30%')
            .height(44)
            .onClick(() => {
                this.showDirectory(name);
            });
    }

    build() {
        Column({ space: 10 }) {
            Text('公共目录浏览')
                .fontSize(24)
                .fontWeight(FontWeight.Bold)
                .margin({ top: 8, bottom: 4 });

            Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
                this.actionButton('Desktop');
                this.actionButton('Documents');
                this.actionButton('Download');
                this.actionButton('Photo');
                this.actionButton('Videos');
                this.actionButton('Audio');
            }
            .width('100%')
            .margin({ bottom: 8 });

            Text('当前目录: ' + this.currentDirName)
                .fontSize(16)
                .fontWeight(FontWeight.Medium)
                .width('100%');

            Text('路径: ' + (this.currentPath || '(空)'))
                .fontSize(13)
                .fontColor('#666666')
                .maxLines(3)
                .textOverflow({ overflow: TextOverflow.Ellipsis })
                .width('100%');

            Text(this.status)
                .fontSize(14)
                .fontColor('#0A59F7')
                .width('100%')
                .margin({ top: 2, bottom: 4 });

            List({ space: 4 }) {
                ForEach(this.items, (item: string) => {
                    ListItem() {
                        Text(item)
                            .fontSize(14)
                            .width('100%')
                            .padding({
                                top: 8,
                                bottom: 8,
                                left: 10,
                                right: 10
                            })
                            .backgroundColor('#F5F7FA')
                            .borderRadius(6);
                    }
                }, (item: string) => item);
            }
            .width('100%')
            .layoutWeight(1)
            .margin({ bottom: 8 });

            Text('调试日志(最近)')
                .fontSize(14)
                .fontColor('#666666')
                .width('100%');

            List({ space: 2 }) {
                ForEach(this.debugLogs, (item: string) => {
                    ListItem() {
                        Text(item)
                            .fontSize(11)
                            .width('100%')
                            .fontColor('#999999')
                            .maxLines(2)
                            .textOverflow({ overflow: TextOverflow.Ellipsis });
                    }
                }, (item: string, index: number) => item + index);
            }
            .width('100%')
            .height(120);
        }
        .padding(12)
        .width('100%')
        .height('100%');
    }
}

12. CommonDirectoryUtil.ets

复制代码
import fs from '@ohos.file.fs';
import environment from '@ohos.file.environment';
import fileAccess from '@ohos.file.fileAccess';
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
import type { Permissions, PermissionRequestResult } from '@ohos.abilityAccessCtrl';
import common from '@ohos.app.ability.common';
import mediaLibrary from '@ohos.multimedia.mediaLibrary';

const TAG = '[CommonDirectoryUtil]';

const DOCUMENT_PATH_CANDIDATES: string[] = [
  '/storage/Users/currentUser/Documents',
  '/storage/Users/currentUser/Document',
  '/storage/media/100/local/files/Docs/Documents',
  '/data/service/el2/100/hmdfs/account/files/Docs/Documents'
];

const DOWNLOAD_PATH_CANDIDATES: string[] = [
  '/storage/Users/currentUser/Download',
  '/storage/Users/currentUser/Downloads',
  '/storage/media/100/local/files/Docs/Download',
  '/data/service/el2/100/hmdfs/account/files/Docs/Download'
];

const DESKTOP_PATH_CANDIDATES: string[] = [
  '/storage/Users/currentUser/Desktop',
  '/storage/media/100/local/files/Docs/Desktop',
  '/data/service/el2/100/hmdfs/account/files/Docs/Desktop'
];

const PHOTO_PATH_CANDIDATES: string[] = [
  '/storage/Users/Photo',
  '/storage/Users/currentUser/Photo',
  '/storage/Users/currentUser/Pictures',
  '/storage/media/100/local/files/Photo',
  '/data/service/el2/100/hmdfs/account/files/Photo'
];

const VIDEOS_PATH_CANDIDATES: string[] = [
  '/storage/Users/Videos',
  '/storage/Users/currentUser/Videos',
  '/storage/media/100/local/files/Videos',
  '/data/service/el2/100/hmdfs/account/files/Videos'
];

const AUDIO_PATH_CANDIDATES: string[] = [
  '/storage/Users/Audio',
  '/storage/Users/currentUser/Audio',
  '/storage/Users/currentUser/Music',
  '/storage/media/100/local/files/Audio',
  '/data/service/el2/100/hmdfs/account/files/Audio'
];

const DESKTOP_URI_CANDIDATES: string[] = [
  'file://docs/storage/Users/currentUser/Desktop'
];

const DOCUMENT_URI_CANDIDATES: string[] = [
  'file://docs/storage/Users/currentUser/Documents'
];

const DOWNLOAD_URI_CANDIDATES: string[] = [
  'file://docs/storage/Users/currentUser/Download',
  'file://docs/storage/Users/currentUser/Downloads'
];

const USER_GRANT_PERMISSIONS: Permissions[] = [
  'ohos.permission.READ_WRITE_DOCUMENTS_DIRECTORY' as Permissions,
  'ohos.permission.READ_WRITE_DOWNLOAD_DIRECTORY' as Permissions,
  'ohos.permission.READ_WRITE_DESKTOP_DIRECTORY' as Permissions,
  'ohos.permission.READ_IMAGEVIDEO' as Permissions,
  'ohos.permission.WRITE_IMAGEVIDEO' as Permissions,
  'ohos.permission.READ_AUDIO' as Permissions,
  'ohos.permission.WRITE_AUDIO' as Permissions,
  'ohos.permission.READ_MEDIA' as Permissions
];

const SYSTEM_PERMISSIONS: Permissions[] = [
  'ohos.permission.FILE_ACCESS_MANAGER' as Permissions,
  'ohos.permission.GET_BUNDLE_INFO_PRIVILEGED' as Permissions
];

export class CommonDirectoryUtil {
  private static documentsPath: string = '';
  private static downloadsPath: string = '';
  private static desktopPath: string = '';
  private static photoPath: string = '';
  private static videosPath: string = '';
  private static audioPath: string = '';

  static readonly DEBUG_LOGS: string[] = [];

  static resetCache(): void {
    CommonDirectoryUtil.documentsPath = '';
    CommonDirectoryUtil.downloadsPath = '';
    CommonDirectoryUtil.desktopPath = '';
    CommonDirectoryUtil.photoPath = '';
    CommonDirectoryUtil.videosPath = '';
    CommonDirectoryUtil.audioPath = '';
  }

  private static log(message: string): void {
    const line = `${TAG} ${message}`;
    console.info(line);
    CommonDirectoryUtil.DEBUG_LOGS.push(line);
    if (CommonDirectoryUtil.DEBUG_LOGS.length > 80) {
      CommonDirectoryUtil.DEBUG_LOGS.shift();
    }
  }

  static getDebugLogs(): string[] {
    return [...CommonDirectoryUtil.DEBUG_LOGS];
  }

  private static getCandidatesByDirName(dirName: string): string[] {
    if (dirName === 'Desktop') {
      return DESKTOP_PATH_CANDIDATES;
    }
    if (dirName === 'Documents') {
      return DOCUMENT_PATH_CANDIDATES;
    }
    if (dirName === 'Download') {
      return DOWNLOAD_PATH_CANDIDATES;
    }
    if (dirName === 'Photo') {
      return PHOTO_PATH_CANDIDATES;
    }
    if (dirName === 'Videos') {
      return VIDEOS_PATH_CANDIDATES;
    }
    if (dirName === 'Audio') {
      return AUDIO_PATH_CANDIDATES;
    }
    return [];
  }

  private static setCachedPath(dirName: string, path: string): void {
    if (dirName === 'Desktop') {
      CommonDirectoryUtil.desktopPath = path;
    } else if (dirName === 'Documents') {
      CommonDirectoryUtil.documentsPath = path;
    } else if (dirName === 'Download') {
      CommonDirectoryUtil.downloadsPath = path;
    } else if (dirName === 'Photo') {
      CommonDirectoryUtil.photoPath = path;
    } else if (dirName === 'Videos') {
      CommonDirectoryUtil.videosPath = path;
    } else if (dirName === 'Audio') {
      CommonDirectoryUtil.audioPath = path;
    }
  }

  private static getCachedPath(dirName: string): string {
    if (dirName === 'Desktop') {
      return CommonDirectoryUtil.desktopPath;
    }
    if (dirName === 'Documents') {
      return CommonDirectoryUtil.documentsPath;
    }
    if (dirName === 'Download') {
      return CommonDirectoryUtil.downloadsPath;
    }
    if (dirName === 'Photo') {
      return CommonDirectoryUtil.photoPath;
    }
    if (dirName === 'Videos') {
      return CommonDirectoryUtil.videosPath;
    }
    if (dirName === 'Audio') {
      return CommonDirectoryUtil.audioPath;
    }
    return '';
    }

  static async ensureAllPermissions(context: common.UIAbilityContext): Promise<boolean> {
    try {
      const atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
      const tokenId: number = context.applicationInfo.accessTokenId;
      const needRequest: Permissions[] = [];

      for (let i = 0; i < USER_GRANT_PERMISSIONS.length; i++) {
        const perm = USER_GRANT_PERMISSIONS[i];
        try {
          const status = atManager.checkAccessTokenSync(tokenId, perm);
          if (status !== abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
            needRequest.push(perm);
          }
        } catch (_) {
          needRequest.push(perm);
        }
      }

      for (let i = 0; i < SYSTEM_PERMISSIONS.length; i++) {
        const perm = SYSTEM_PERMISSIONS[i];
        try {
          const status = atManager.checkAccessTokenSync(tokenId, perm);
          CommonDirectoryUtil.log(`system permission ${perm}: ${status}`);
        } catch (e) {
          CommonDirectoryUtil.log(`system permission check failed ${perm}: ${JSON.stringify(e)}`);
        }
      }

      if (needRequest.length === 0) {
        CommonDirectoryUtil.log('all permissions already granted');
        return true;
      }

      CommonDirectoryUtil.log(`requesting permissions: ${JSON.stringify(needRequest)}`);
      const allGranted = await new Promise<boolean>((resolve) => {
        atManager.requestPermissionsFromUser(context, needRequest, (err, data: PermissionRequestResult) => {
          if (err) {
            CommonDirectoryUtil.log(`requestPermissionsFromUser error: ${JSON.stringify(err)}`);
            resolve(false);
            return;
          }
          if (!data || !data.authResults) {
            CommonDirectoryUtil.log('requestPermissionsFromUser returned empty result');
            resolve(false);
            return;
          }
          CommonDirectoryUtil.log(`permission authResults: ${JSON.stringify(data.authResults)}`);
          for (let i = 0; i < data.authResults.length; i++) {
            if (data.authResults[i] !== 0) {
              resolve(false);
              return;
            }
          }
          resolve(true);
        });
      });

      CommonDirectoryUtil.resetCache();
      return allGranted;
    } catch (_) {
      CommonDirectoryUtil.log('ensureAllPermissions exception');
      return false;
    }
  }

  static async listDirectoryEntries(context: common.UIAbilityContext, dirName: string): Promise<DirListResult> {
    if (dirName === 'Desktop' || dirName === 'Documents' || dirName === 'Download') {
      const byFileAccess = await CommonDirectoryUtil.listByFileAccess(context, dirName);
      if (byFileAccess.resolvedPath) {
        return byFileAccess;
      }
      CommonDirectoryUtil.log(`fileAccess fallback to fs path probing for ${dirName}`);
    }

    if (dirName === 'Photo') {
      return await CommonDirectoryUtil.listMediaByType(context, mediaLibrary.MediaType.IMAGE, 'Photo');
    }
    if (dirName === 'Videos') {
      return await CommonDirectoryUtil.listMediaByType(context, mediaLibrary.MediaType.VIDEO, 'Videos');
    }
    if (dirName === 'Audio') {
      return await CommonDirectoryUtil.listMediaByType(context, mediaLibrary.MediaType.AUDIO, 'Audio');
    }

    const cached = CommonDirectoryUtil.getCachedPath(dirName);
    const candidates = cached ? [cached, ...CommonDirectoryUtil.getCandidatesByDirName(dirName)] : CommonDirectoryUtil.getCandidatesByDirName(dirName);
    let uniqueCandidates = candidates.filter((item, idx) => candidates.indexOf(item) === idx);
    uniqueCandidates = CommonDirectoryUtil.prependEnvironmentPath(dirName, uniqueCandidates);

    CommonDirectoryUtil.log(`start listDirectoryEntries dir=${dirName}`);
    CommonDirectoryUtil.log(`candidates=${JSON.stringify(uniqueCandidates)}`);
    for (let i = 0; i < uniqueCandidates.length; i++) {
      const candidate = uniqueCandidates[i];
      try {
        const names = fs.listFileSync(candidate);
        CommonDirectoryUtil.setCachedPath(dirName, candidate);
        CommonDirectoryUtil.log(`candidate success: ${candidate}, count=${names.length}`);
        const result = new DirListResult();
        result.resolvedPath = candidate;
        result.items = names;
        return result;
      } catch (e) {
        CommonDirectoryUtil.log(`listFileSync failed: ${candidate}, error=${JSON.stringify(e)}`);
      }

      try {
        const stat = fs.statSync(candidate);
        CommonDirectoryUtil.log(`statSync: ${candidate}, isDir=${stat.isDirectory()}`);
      } catch (e) {
        CommonDirectoryUtil.log(`statSync failed: ${candidate}, error=${JSON.stringify(e)}`);
      }

      try {
        const accessResult = fs.accessSync(candidate);
        CommonDirectoryUtil.log(`accessSync: ${candidate}, result=${accessResult}`);
      } catch (e) {
        CommonDirectoryUtil.log(`accessSync failed: ${candidate}, error=${JSON.stringify(e)}`);
      }
    }
    CommonDirectoryUtil.log(`all candidates failed for dir=${dirName}`);
    const failed = new DirListResult();
    return failed;
  }

  private static getUriCandidatesByDirName(dirName: string): string[] {
    if (dirName === 'Desktop') {
      return DESKTOP_URI_CANDIDATES;
    }
    if (dirName === 'Documents') {
      return DOCUMENT_URI_CANDIDATES;
    }
    if (dirName === 'Download') {
      return DOWNLOAD_URI_CANDIDATES;
    }
    return [];
  }

  private static async listByFileAccess(context: common.UIAbilityContext, dirName: string): Promise<DirListResult> {
    const result = new DirListResult();
    const candidates = CommonDirectoryUtil.getUriCandidatesByDirName(dirName);
    CommonDirectoryUtil.log(`start fileAccess listing dir=${dirName}, uriCandidates=${JSON.stringify(candidates)}`);
    try {
      const helper = fileAccess.createFileAccessHelper(context);
      for (let i = 0; i < candidates.length; i++) {
        const uri = candidates[i];
        try {
          const fileInfo = await helper.getFileInfoFromUri(uri);
          const iterator = fileInfo.listFile();
          const names: string[] = [];
          let done = false;
          while (!done) {
            const node = iterator.next();
            if (node.value) {
              names.push(node.value.fileName);
            }
            done = node.done;
          }
          result.resolvedPath = uri;
          result.items = names;
          CommonDirectoryUtil.log(`fileAccess success dir=${dirName}, uri=${uri}, count=${names.length}`);
          return result;
        } catch (e) {
          CommonDirectoryUtil.log(`fileAccess failed uri=${uri}, error=${JSON.stringify(e)}`);
        }
      }
      CommonDirectoryUtil.log(`fileAccess all uri candidates failed for ${dirName}`);
      return result;
    } catch (e) {
      CommonDirectoryUtil.log(`fileAccess helper create failed for ${dirName}: ${JSON.stringify(e)}`);
      return result;
    }
  }

  private static prependEnvironmentPath(dirName: string, candidates: string[]): string[] {
    let envPath = '';
    try {
      if (dirName === 'Desktop') {
        envPath = environment.getUserDesktopDir();
      } else if (dirName === 'Documents') {
        envPath = environment.getUserDocumentDir();
      } else if (dirName === 'Download') {
        envPath = environment.getUserDownloadDir();
      }
      if (envPath) {
        CommonDirectoryUtil.log(`environment path for ${dirName}: ${envPath}`);
      }
    } catch (e) {
      CommonDirectoryUtil.log(`environment path resolve failed for ${dirName}: ${JSON.stringify(e)}`);
    }

    if (!envPath) {
      return candidates;
    }
    const merged = [envPath, ...candidates];
    return merged.filter((item, idx) => merged.indexOf(item) === idx);
  }

  private static async listMediaByType(context: common.UIAbilityContext, mediaType: mediaLibrary.MediaType, label: string): Promise<DirListResult> {
    const result = new DirListResult();
    result.resolvedPath = `MediaLibrary:${label}`;
    CommonDirectoryUtil.log(`start media query type=${label}`);
    try {
      const media: mediaLibrary.MediaLibrary = mediaLibrary.getMediaLibrary(context);
      const options: mediaLibrary.MediaFetchOptions = {
        selections: `${mediaLibrary.FileKey.MEDIA_TYPE}=?`,
        selectionArgs: [String(mediaType)]
      };
      const fetch: mediaLibrary.FetchFileResult = await media.getFileAssets(options);
      const assets: mediaLibrary.FileAsset[] = await fetch.getAllObject();
      const names: string[] = [];
      for (let i = 0; i < assets.length; i++) {
        const item = assets[i];
        names.push(item.displayName);
      }
      fetch.close();
      result.items = names;
      CommonDirectoryUtil.log(`media query success type=${label}, count=${names.length}`);
      return result;
    } catch (e) {
      CommonDirectoryUtil.log(`media query failed type=${label}, error=${JSON.stringify(e)}`);
      result.resolvedPath = '';
      return result;
    }
  }
}

class DirListResult {
  resolvedPath: string = '';
  items: string[] = [];
}

附1

运行截图:

附2

配置应用为系统应用

在你的sdk目录下找到如下路径:OpenHarmony\Sdk\14\toolchains\lib

修改这两个文件内容如下:

复制代码
{
    "version-name": "2.0.0",
    "version-code": 2,
    "uuid": "fe686e1b-3770-4824-a938-961b140a7c98",
    "validity": {
        "not-before": 1610519532,
        "not-after": 1705127532
    },
    "type": "debug",
    "bundle-info": {
        "developer-id": "OpenHarmony",
        "development-certificate": "-----BEGIN CERTIFICATE-----\nMIICMzCCAbegAwIBAgIEaOC/zDAMBggqhkjOPQQDAwUAMGMxCzAJBgNVBAYTAkNO\nMRQwEgYDVQQKEwtPcGVuSGFybW9ueTEZMBcGA1UECxMQT3Blbkhhcm1vbnkgVGVh\nbTEjMCEGA1UEAxMaT3Blbkhhcm1vbnkgQXBwbGljYXRpb24gQ0EwHhcNMjEwMjAy\nMTIxOTMxWhcNNDkxMjMxMTIxOTMxWjBoMQswCQYDVQQGEwJDTjEUMBIGA1UEChML\nT3Blbkhhcm1vbnkxGTAXBgNVBAsTEE9wZW5IYXJtb255IFRlYW0xKDAmBgNVBAMT\nH09wZW5IYXJtb255IEFwcGxpY2F0aW9uIFJlbGVhc2UwWTATBgcqhkjOPQIBBggq\nhkjOPQMBBwNCAATbYOCQQpW5fdkYHN45v0X3AHax12jPBdEDosFRIZ1eXmxOYzSG\nJwMfsHhUU90E8lI0TXYZnNmgM1sovubeQqATo1IwUDAfBgNVHSMEGDAWgBTbhrci\nFtULoUu33SV7ufEFfaItRzAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFPtxruhl\ncRBQsJdwcZqLu9oNUVgaMAwGCCqGSM49BAMDBQADaAAwZQIxAJta0PQ2p4DIu/ps\nLMdLCDgQ5UH1l0B4PGhBlMgdi2zf8nk9spazEQI/0XNwpft8QAIwHSuA2WelVi/o\nzAlF08DnbJrOOtOnQq5wHOPlDYB4OtUzOYJk9scotrEnJxJzGsh/\n-----END CERTIFICATE-----\n",
        "bundle-name": "com.OpenHarmony.app.test",
        "apl": "system_core",
        "app-feature": "hos_system_app"
    },
    "acls": {
        "allowed-acls": [
            ""
        ]
    },
    "permissions": {
        "restricted-permissions": [
            ""
        ]
    },
    "debug-info": {
        "device-ids": [
            "69C7505BE341BDA5948C3C0CB44ABCD530296054159EFE0BD16A16CD0129CC42",
            "7EED06506FCE6325EB2E2FAA019458B856AB10493A6718C7679A73F958732865"
        ],
        "device-id-type": "udid"
    },
    "issuer": "pki_internal"
}

{
	"version-name":"2.0.0",
	"version-code":2,
	"app-distribution-type":"os_integration",
	"uuid":"5027b99e-5f9e-465d-9508-a9e0134ffe18",
	"validity":{
		"not-before":1594865258,
		"not-after":1689473258
	},
	"type":"release",
	"bundle-info":{
		"developer-id":"OpenHarmony",
		"distribution-certificate":"-----BEGIN CERTIFICATE-----\nMIICFDCCAZugAwIBAgIIf129GJfWkSAwCgYIKoZIzj0EAwMwYzELMAkGA1UEBhMC\nQ04xFDASBgNVBAoTC09wZW5IYXJtb255MRkwFwYDVQQLExBPcGVuSGFybW9ueSBU\nZWFtMSMwIQYDVQQDExpPcGVuSGFybW9ueSBBcHBsaWNhdGlvbiBDQTAeFw0yNjA2\nMDIxMjQ0NTNaFw0zNjA1MzAxMjQ0NTNaMEoxFTATBgNVBAMMDGlkZV9kZW1vX2Fw\ncDENMAsGA1UECxMEVW5pdDEVMBMGA1UEChMMT3JnYW5pemF0aW9uMQswCQYDVQQG\nEwJDTjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAadneFnMAy4jsLRp9zBpuc+\nulvu2PMMJEMsucX34EmzyLooD0a0xXxR/eEInAtdwCOKH8wD/vY6oG8PlfkCQ9Cj\nUjBQMB0GA1UdDgQWBBS4XOVPp6HOAJqVIfOPgcLM2A/WXDAOBgNVHQ8BAf8EBAMC\nB4AwHwYDVR0jBBgwFoAU24a3IhbVC6FLt90le7nxBX2iLUcwCgYIKoZIzj0EAwMD\nZwAwZAIwBr5aBax0JNhhCV9G9fN+sJ48Jwb7l0taE336iq0An7EYPH/1xRDOa64S\nPAPkB8zEAjBzMxXPM2LQ4+EliVZh5tb+4RCwkYJ64MPJgsqaDN2js9lPL7cXHAWL\n1O0mLzdaJ/g=\n-----END CERTIFICATE-----\n",
		"bundle-name":"com.example.diraccess",
		"apl":"system_core",
		"app-feature":"hos_system_app"
	},
	"acls":{
		"allowed-acls":[
			""
		]
	},
	"permissions":{
		"restricted-permissions":[
			
		]
	},
	"issuer":"pki_internal"
}
相关推荐
特立独行的猫a1 小时前
OHOS (OpenHarmony) 鸿蒙的Rust 交叉编译环境搭建指南
华为·rust·harmonyos·鸿蒙pc
Swift社区1 小时前
HarmonyOS鸿蒙PC平台三方库移植使用vcpkg 移植 Crashpad 过程实战总结
华为·harmonyos
再见6581 小时前
鸿蒙原生开发实战:从零打造一款涂鸦板应用
华为·harmonyos
大雷神2 小时前
第42篇|拍摄预览浮层:让用户确认刚拍的成果
harmonyos
再见6588 小时前
【HarmonyOS】 Todo 应用开发实战
harmonyos
爱吃大芒果9 小时前
面向大型鸿蒙原生应用的工程基建:核心路由、全局样式库与状态管理设计图纸
华为·harmonyos
轻口味13 小时前
HarmonyOS 6.1.1 全栈实战录 - 91 实战 Call Service Kit 扩展企服来去电智慧
华为·harmonyos·鸿蒙
木斯佳14 小时前
鸿蒙开发入门指南:前端开发者快速理解视频编码概念——输入模式
华为·音视频·harmonyos
不羁的木木16 小时前
《HarmonyOS技术精讲》二:用户动作与状态感知实战
华为·harmonyos