今天咱们来聊聊 HarmonyOS 里一个非常实用的功能 —— 文件分享 API。不管你是开发社交软件、办公应用还是文件管理器,这套 API 都能帮你实现应用之间安全便捷的文件共享。我会用最通俗的话来讲,每个知识点都配上代码示例,保证你看完就能上手写!

一、文件分享 API 能干啥?先搞懂核心能力

简单来说,这个 API 就像一个 "文件通行证" 管理系统,主要解决两个问题:

  1. 安全授权:让一个应用能把文件权限(比如读、写)临时或长期授权给另一个应用
  2. 权限管理:能激活、停用、撤销这些授权,就像管理钥匙一样灵活

举个例子:你在相册应用里选了一张照片想分享到聊天软件,相册应用就通过这个 API 给聊天软件发一个 "读文件通行证",聊天软件拿到后才能读取这张照片。而且这个通行证还能设置有效期、权限范围,非常安全。

重点总结:文件分享 = 权限授权 + 权限控制 + 安全验证

// 先导入核心模块,这是所有操作的基础
import fileShare from '@ohos.fileshare';

二、核心概念:权限模式和策略信息

在开始写代码前,得先认识几个关键概念,就像开车前要认识油门刹车一样:

1. OperationMode:权限的 "钥匙类型"

这是个枚举,定义了不同的文件操作权限,就像不同钥匙开不同锁:

  • READ_MODE(0b1):读文件的权限,只能看不能改
  • WRITE_MODE(0b10):写文件的权限,可以修改内容
  • CREATE_MODE(0b100):创建文件 / 文件夹的权限
  • DELETE_MODE(0b1000):删除文件 / 文件夹的权限
  • RENAME_MODE(0b10000):重命名文件 / 文件夹的权限

重点代码:权限模式的使用

// 组合多种权限,比如同时给读和写权限
const readWriteMode = fileShare.OperationMode.READ_MODE | fileShare.OperationMode.WRITE_MODE;
console.log(`读写权限组合值:${readWriteMode}`); // 输出3(0b11)

2. PolicyInfo:权限策略的 "通行证信息"

每个权限授权都需要这个对象,包含两个必填项:

  • uri:要授权的文件或目录的 URI 地址
  • operationMode:上面说的权限模式

重点代码:创建权限策略

// 创建一个读权限的策略
const policy: fileShare.PolicyInfo = {
  uri: "file://docs/storage/Users/me/important.txt",
  operationMode: fileShare.OperationMode.READ_MODE
};

3. PolicyErrorResult:错误处理的 "诊断书"

当授权操作失败时,会返回这个对象,告诉你哪里出错了,包含:

  • uri:失败的文件 URI
  • code:错误码(比如 1 是禁止持久化,2 是无效模式)
  • message:错误原因描述

三、核心流程:从授权到撤销的完整操作

现在进入最关键的部分,咱们一步步看怎么实现文件分享的完整流程。

1. 持久化授权:发一张长期有效的 "通行证"

persistPermission 接口就像发一张长期有效的通行证,让其他应用可以长期访问文件。注意:不支持媒体类 URI 和远端 URI 哦。

重点代码:文件持久化授权

import { BusinessError } from '@ohos.base';
import picker from '@ohos.file.picker';

async function persistPermissionDemo() {
  try {
    // 1. 用文件选择器让用户选文件
    const selectOptions = new picker.DocumentSelectOptions();
    const picker = new picker.DocumentViewPicker();
    const uris = await picker.select(selectOptions);
    
    if (!uris || uris.length === 0) {
      console.log('用户未选择文件');
      return;
    }
    
    // 2. 创建权限策略(给第一个文件读权限)
    const policyInfo: fileShare.PolicyInfo = {
      uri: uris[0],
      operationMode: fileShare.OperationMode.READ_MODE
    };
    
    // 3. 发起持久化授权(数组最多500个策略)
    await fileShare.persistPermission([policyInfo]);
    console.log('持久化授权成功');
    
  } catch (error) {
    const err = error as BusinessError<Array<fileShare.PolicyErrorResult>>;
    console.error(`授权失败,错误码:${err.code},信息:${err.message}`);
    
    // 处理批量授权失败的情况
    if (err.code === 13900001 && err.data) {
      console.error('以下文件授权失败:');
      err.data.forEach((item, index) => {
        console.error(`第${index+1}个文件URI:${item.uri}`);
        console.error(`错误码:${item.code},原因:${item.message}`);
        
        // 根据不同错误码给用户提示
        if (item.code === fileShare.PolicyErrorCode.INVALID_PATH) {
          console.error('请检查文件路径是否正确');
        } else if (item.code === fileShare.PolicyErrorCode.INVALID_MODE) {
          console.error('权限模式无效,比如不能单独给创建权限而不给写权限');
        }
      });
    }
  }
}

2. 撤销授权:收回发出去的 "通行证"

revokePermission 接口用于收回已经发出的持久化授权,就像把钥匙收回来。

重点代码:撤销文件授权

async function revokePermissionDemo() {
  try {
    // 假设这个URI是之前授权过的
    const uri = "file://docs/storage/Users/me/authorized.txt";
    
    const policyInfo: fileShare.PolicyInfo = {
      uri,
      operationMode: fileShare.OperationMode.READ_MODE
    };
    
    // 发起撤销授权
    await fileShare.revokePermission([policyInfo]);
    console.log('撤销授权成功');
    
  } catch (error) {
    const err = error as BusinessError<Array<fileShare.PolicyErrorResult>>;
    console.error(`撤销失败,错误码:${err.code},信息:${err.message}`);
    
    // 特别处理权限未持久化的情况
    if (err.code === 13900001 && err.data) {
      const firstError = err.data[0];
      if (firstError.code === fileShare.PolicyErrorCode.PERMISSION_NOT_PERSISTED) {
        console.error('该URI未被持久化授权,无法撤销');
      }
    }
  }
}

3. 激活 / 停用权限:临时开关 "通行证"

有时候我们不想彻底收回钥匙,只是临时禁用,这时候就用 activatePermission 和 deactivatePermission:

  • activate:激活已持久化的权限(打开开关)
  • deactivate:停用已持久化的权限(关闭开关)

重点代码:激活和停用权限

async function activateAndDeactivateDemo() {
  try {
    const uri = "file://docs/storage/Users/me/temp.txt";
    const policyInfo: fileShare.PolicyInfo = {
      uri,
      operationMode: fileShare.OperationMode.READ_MODE
    };
    
    // 1. 先激活权限
    await fileShare.activatePermission([policyInfo]);
    console.log('权限已激活,其他应用可以访问');
    
    // 模拟使用文件...
    
    // 2. 用完后停用权限
    await fileShare.deactivatePermission([policyInfo]);
    console.log('权限已停用,暂时无法访问');
    
  } catch (error) {
    const err = error as BusinessError<Array<fileShare.PolicyErrorResult>>;
    console.error(`操作失败,错误码:${err.code},信息:${err.message}`);
    
    // 处理激活时权限未持久化的情况
    if (err.code === 13900001 && err.data) {
      const firstError = err.data[0];
      if (firstError.code === fileShare.PolicyErrorCode.PERMISSION_NOT_PERSISTED) {
        console.log('先进行持久化授权');
        // 先持久化再激活
        await fileShare.persistPermission([policyInfo]);
        await fileShare.activatePermission([policyInfo]);
      }
    }
  }
}

4. 检查权限:看看 "通行证" 还在不在

checkPersistentPermission 用于检查某个 URI 是否有持久化授权,返回布尔数组,对应每个策略的授权状态。

重点代码:检查权限状态

async function checkPermissionDemo() {
  try {
    // 假设这两个URI是之前授权过的
    const uris = [
      "file://docs/storage/Users/me/authorized1.txt",
      "file://docs/storage/Users/me/authorized2.txt"
    ];
    
    const policies: fileShare.PolicyInfo[] = uris.map(uri => ({
      uri,
      operationMode: fileShare.OperationMode.READ_MODE
    }));
    
    // 检查权限
    const results = await fileShare.checkPersistentPermission(policies);
    
    // 处理结果
    results.forEach((hasPermission, index) => {
      const uri = uris[index];
      if (hasPermission) {
        console.log(`URI ${uri} 有持久化授权`);
      } else {
        console.log(`URI ${uri} 没有持久化授权,重新授权`);
        // 没有授权的话,重新发起授权
        fileShare.persistPermission([policies[index]]);
      }
    });
    
  } catch (error) {
    const err = error as BusinessError;
    console.error(`检查失败,错误码:${err.code},信息:${err.message}`);
  }
}

四、错误处理:遇到问题怎么办?

在使用文件分享 API 时,难免会遇到各种错误,咱们挑几个常见的说说:

  1. 13900001 - 操作不允许:最常见的错误,可能是:

    • URI 格式不对(比如媒体类 URI 不支持持久化)
    • 权限模式组合错误(比如只给 CREATE_MODE 却没给 WRITE_MODE)
    • 没有申请 ohos.permission.FILE_ACCESS_PERSIST 权限
  2. 401 - 参数错误:检查参数是否必填,类型是否正确,比如 policies 数组不能超过 500 个

  3. 201 - 权限验证失败:应用没有获取到足够的权限,检查配置文件和动态申请权限流程

  4. 801 - 功能不支持:设备不支持该功能,比如某些老型号设备可能没有相关系统能力

重点代码:统一错误处理

// 封装一个错误处理函数
function handleFileShareError(error: any, operation: string) {
  console.error(`[${operation}] 操作出错`, error);
  
  if (error.code) {
    switch (error.code) {
      case 13900001:
        console.log('操作不允许,可能是URI或权限模式有问题');
        if (error.data && error.data.length > 0) {
          const firstError = error.data[0];
          if (firstError.code === fileShare.PolicyErrorCode.INVALID_PATH) {
            console.log('建议操作:检查URI是否为有效文件路径,且不是媒体类或远端URI');
          } else if (firstError.code === fileShare.PolicyErrorCode.INVALID_MODE) {
            console.log('建议操作:检查权限模式是否正确,比如创建文件需要WRITE_MODE+CREATE_MODE');
          }
        }
        break;
      case 401:
        console.log('参数错误,检查是否有必填参数缺失或类型错误');
        break;
      case 201:
        console.log('权限验证失败,检查是否申请了file.access.persist权限');
        break;
      case 801:
        console.log('设备不支持该功能,检查设备系统版本');
        break;
      default:
        console.log(`错误码 ${error.code},详细信息:${error.message}`);
    }
  } else {
    console.log('未知错误:', error.message);
  }
}

// 使用示例
fileShare.persistPermission(policies).then(...).catch((err) => {
  handleFileShareError(err, '持久化授权');
});

五、实际应用场景:文件分享功能实战

现在把前面的知识点串起来,看看怎么在实际应用中实现一个完整的文件分享功能,比如一个简单的文件管理器应用:

1. 场景需求:

  • 用户选择文件后,可以分享给其他应用
  • 分享时可以选择权限(只读、读写等)
  • 能查看已授权的文件列表
  • 可以撤销不需要的授权

2. 核心流程:

import { BusinessError } from '@ohos.base';
import picker from '@ohos.file.picker';
import fileShare from '@ohos.fileshare';

export default class FileSharingApp {
  private authorizedUris: string[] = []; // 存储已授权的URI
  
  // 启动应用时检查已授权的文件
  async init() {
    await this.checkAllAuthorizedPermissions();
    console.log('应用初始化完成,已授权文件数:', this.authorizedUris.length);
  }
  
  // 文件选择与授权
  async shareFile() {
    try {
      // 1. 打开文件选择器
      const selectOptions = new picker.DocumentSelectOptions();
      selectOptions.multiple = true; // 支持多选
      const picker = new picker.DocumentViewPicker();
      const uris = await picker.select(selectOptions);
      
      if (!uris || uris.length === 0) {
        console.log('用户未选择文件');
        return;
      }
      
      // 2. 显示权限选择界面(这里简化处理,实际应用中会有UI交互)
      const operationMode = await this.showPermissionDialog(); // 假设返回选择的权限模式
      
      // 3. 发起持久化授权
      const policies = uris.map(uri => ({
        uri,
        operationMode
      }));
      
      await fileShare.persistPermission(policies);
      console.log('授权成功,文件已分享');
      
      // 4. 更新已授权列表
      this.authorizedUris = [...this.authorizedUris, ...uris];
      
      // 5. 通知其他应用可以访问这些文件(实际应用中通过Intent或其他方式通知)
      this.notifyOtherApps(uris);
      
    } catch (error) {
      handleFileShareError(error, '文件分享');
    }
  }
  
  // 显示权限选择对话框(模拟函数)
  private showPermissionDialog(): Promise<number> {
    return new Promise(resolve => {
      // 实际应用中这里会有UI对话框让用户选择权限
      // 这里简化返回读权限
      resolve(fileShare.OperationMode.READ_MODE);
    });
  }
  
  // 通知其他应用(模拟函数)
  private notifyOtherApps(uris: string[]) {
    console.log('通知其他应用可以访问这些URI:', uris);
    // 实际应用中可能通过系统广播或自定义接口通知
  }
  
  // 检查所有已授权的权限
  async checkAllAuthorizedPermissions() {
    if (this.authorizedUris.length === 0) return;
    
    const policies = this.authorizedUris.map(uri => ({
      uri,
      operationMode: fileShare.OperationMode.READ_MODE // 任意模式都可以,只检查是否授权
    }));
    
    try {
      const results = await fileShare.checkPersistentPermission(policies);
      
      // 过滤掉未授权的URI
      this.authorizedUris = this.authorizedUris.filter((_, index) => results[index]);
      console.log('已授权文件数更新为:', this.authorizedUris.length);
      
    } catch (error) {
      handleFileShareError(error, '检查所有授权');
    }
  }
  
  // 显示已授权文件列表
  showAuthorizedFiles() {
    if (this.authorizedUris.length === 0) {
      console.log('当前没有已授权的文件');
      return;
    }
    
    console.log('===== 已授权文件列表 =====');
    this.authorizedUris.forEach((uri, index) => {
      console.log(`${index+1}. ${uri}`);
    });
  }
  
  // 撤销单个文件授权
  async revokeSinglePermission(uri: string) {
    try {
      const policyInfo: fileShare.PolicyInfo = {
        uri,
        operationMode: fileShare.OperationMode.READ_MODE
      };
      
      await fileShare.revokePermission([policyInfo]);
      console.log(`已撤销URI ${uri} 的授权`);
      
      // 更新列表
      this.authorizedUris = this.authorizedUris.filter(u => u !== uri);
      
    } catch (error) {
      handleFileShareError(error, '撤销单个授权');
    }
  }
  
  // 批量撤销授权
  async revokeMultiplePermissions(uris: string[]) {
    try {
      const policies = uris.map(uri => ({
        uri,
        operationMode: fileShare.OperationMode.READ_MODE
      }));
      
      await fileShare.revokePermission(policies);
      console.log(`已撤销 ${uris.length} 个文件的授权`);
      
      // 更新列表
      this.authorizedUris = this.authorizedUris.filter(u => !uris.includes(u));
      
    } catch (error) {
      handleFileShareError(error, '撤销多个授权');
    }
  }
}

// 使用示例
const fileSharing = new FileSharingApp();
fileSharing.init();

// 模拟用户点击分享按钮
fileSharing.shareFile();

// 显示已授权列表
fileSharing.showAuthorizedFiles();

// 撤销某个文件授权
fileSharing.revokeSinglePermission("file://docs/storage/Users/me/oldfile.txt");

六、高级技巧:权限组合与批量操作

1. 权限组合使用

有时候需要同时给多个权限,比如读写 + 创建,这时候可以用按位或操作:

// 组合读写和创建权限
const complexMode = 
  fileShare.OperationMode.READ_MODE | 
  fileShare.OperationMode.WRITE_MODE | 
  fileShare.OperationMode.CREATE_MODE;

console.log(`复杂权限模式值:${complexMode}`); // 输出7(0b111)

2. 批量授权与错误处理

当需要授权多个文件时,要特别注意错误处理,因为只要有一个文件授权失败,整个操作就会抛出异常:

async function batchAuthorize(uris: string[], operationMode: number) {
  try {
    const policies = uris.map(uri => ({ uri, operationMode }));
    await fileShare.persistPermission(policies);
    console.log(`成功授权 ${uris.length} 个文件`);
    
  } catch (error) {
    const err = error as BusinessError<Array<fileShare.PolicyErrorResult>>;
    console.error(`批量授权失败,错误码:${err.code}`);
    
    if (err.data) {
      const successCount = uris.length - err.data.length;
      console.log(`成功授权 ${successCount} 个文件,以下 ${err.data.length} 个失败:`);
      
      err.data.forEach((item, index) => {
        console.error(`第${index+1}个失败文件:${item.uri}`);
        console.error(`错误码:${item.code},原因:${item.message}`);
      });
    }
  }
}

七、总结:文件分享 API 的三大优势和使用建议

1. 三大核心优势

  • 安全可控:通过权限模式和持久化管理,精确控制其他应用的文件访问范围
  • 灵活方便:支持激活 / 停用权限,就像开关一样灵活,不需要彻底撤销
  • 批量操作:支持一次授权 / 撤销多个文件,提高批量处理效率

2. 开发建议

  • 先小后大:先实现单个文件的授权撤销,再扩展到批量操作
  • 完善错误处理:线上应用一定要处理各种错误情况,给用户清晰的提示
  • 权限最小化:授权时只给必要的权限,比如只读就不要给写权限
  • 定期检查:应用启动时检查已授权的文件,清理无效或过期的授权

通过这套文件分享 API,我们可以在 HarmonyOS 应用中实现安全、灵活的文件共享功能,无论是相册分享、文档协作还是文件传输,都能轻松搞定。赶紧动手试试吧,有问题咱们可以一起讨论!

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐