今天咱们来聊聊 HarmonyOS 里一个非常实用的功能 —— 文件选择器 API。不管你是开发办公软件、文件管理器还是多媒体应用,这套 API 都能帮你实现用户选择和保存文件的功能。我会用最通俗的话来讲,每个知识点都配上代码示例,保证你看完就能上手写!

一、文件选择器 API 能干啥?先搞懂核心能力

文件选择器就像一个 "文件管家",主要帮我们解决两个问题:

  1. 让用户选择文件:可以打开系统文件界面,让用户选择文档、音频、图片等文件
  2. 让用户保存文件:支持指定文件名和保存位置,把数据保存到设备上

举个例子:当你在编辑文档时,点击 "打开" 按钮弹出的文件选择界面,或者点击 "保存" 按钮时的文件保存界面,都是通过文件选择器 API 实现的。HarmonyOS 提供了多种选择器,针对不同类型的文件,用起来非常灵活。

重点总结:文件选择器 = 文件选择 + 文件保存 + 类型过滤

// 先导入核心模块,这是所有操作的基础
import { picker } from '@kit.CoreFileKit';

二、文档选择器 DocumentViewPicker:处理各种格式文件

文档选择器是最常用的选择器,支持选择和保存各种文档格式,比如 txt、pdf、doc 等。咱们一步步看怎么用。

1. 创建文档选择器实例

创建选择器实例有三种方法,推荐使用带 context 的构造函数,更稳定:

// 推荐方式:传入应用上下文,最稳定
import { common } from '@kit.AbilityKit';

@Entry
@Component
struct DocumentPickerDemo {
  @State message: string = '点击选择文件';

  build() {
    Column() {
      Text(this.message)
        .fontSize(18)
        .padding(10)
        .onClick(async () => {
          // 获取应用上下文
          const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
          // 创建文档选择器实例
          const documentPicker = new picker.DocumentViewPicker(context);
          // 这里可以调用选择或保存方法
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

// 不推荐方式:无参构造,可能概率性失败
const documentPicker = new picker.DocumentViewPicker();

// 高级方式:传入窗口实例,用于自定义窗口场景
import { window } from '@kit.ArkUI';

const customWindow = window.findWindow('myWindow'); // 先获取窗口实例
const documentPicker = new picker.DocumentViewPicker(context, customWindow);

2. 选择文件功能

选择文件有三种调用方式,Promise 和 callback 两种异步模式:

// Promise方式选择文件
async function selectFileWithPromise(context: common.UIAbilityContext) {
  const documentPicker = new picker.DocumentViewPicker(context);
  try {
    // 创建选择选项
    const options = new picker.DocumentSelectOptions();
    options.maxSelectNumber = 5; // 最多选5个文件
    options.fileSuffixFilters = ['文档|.txt,.doc,.pdf', '图片|.jpg,.png']; // 过滤文件后缀
    
    // 调用选择方法
    const uris = await documentPicker.select(options);
    console.log('选择的文件URI:', uris);
    // 处理选择的文件URI
  } catch (error) {
    console.error('选择文件失败:', error);
  }
}

// callback方式选择文件
function selectFileWithCallback(context: common.UIAbilityContext) {
  const documentPicker = new picker.DocumentViewPicker(context);
  const options = new picker.DocumentSelectOptions();
  options.defaultFilePathUri = '/storage/emulated/0/Documents'; // 设置默认路径
  
  documentPicker.select(options, (err, uris) => {
    if (err) {
      console.error('选择文件失败:', err);
      return;
    }
    console.log('选择的文件URI:', uris);
  });
}

// 简化callback方式,不传入选项
function selectFileSimpleCallback(context: common.UIAbilityContext) {
  const documentPicker = new picker.DocumentViewPicker(context);
  documentPicker.select((err, uris) => {
    if (err) {
      console.error('选择文件失败:', err);
      return;
    }
    console.log('选择的文件URI:', uris);
  });
}

3. 保存文件功能

保存文件同样支持 Promise 和 callback 方式,可以指定文件名和保存位置:

// Promise方式保存文件
async function saveFileWithPromise(context: common.UIAbilityContext) {
  const documentPicker = new picker.DocumentViewPicker(context);
  try {
    // 创建保存选项
    const options = new picker.DocumentSaveOptions();
    options.newFileNames = ['报告.docx', '备忘录.txt']; // 设置文件名
    options.defaultFilePathUri = '/storage/emulated/0/Documents'; // 默认保存路径
    options.fileSuffixChoices = ['文本文件|.txt', '文档文件|.docx', 'PDF文件|.pdf']; // 后缀选择
    
    // 调用保存方法
    const uris = await documentPicker.save(options);
    console.log('保存的文件URI:', uris);
    // 可以在这里写入文件内容
  } catch (error) {
    console.error('保存文件失败:', error);
  }
}

// callback方式保存文件
function saveFileWithCallback(context: common.UIAbilityContext) {
  const documentPicker = new picker.DocumentViewPicker(context);
  const options = new picker.DocumentSaveOptions();
  options.newFileNames = ['图片.jpg'];
  
  documentPicker.save(options, (err, uris) => {
    if (err) {
      console.error('保存文件失败:', err);
      return;
    }
    console.log('保存的文件URI:', uris);
    // 使用URI写入文件内容
  });
}

4. 获取保存文件的后缀类型

从 API 14 开始,支持获取用户选择的文件后缀类型下标:

async function getSavedFileSuffixIndex(context: common.UIAbilityContext) {
  const documentPicker = new picker.DocumentViewPicker(context);
  try {
    const options = new picker.DocumentSaveOptions();
    options.newFileNames = ['文件'];
    options.fileSuffixChoices = ['txt', 'mp4', 'pdf']; // 提供后缀选项
    
    const uris = await documentPicker.save(options);
    if (uris && uris.length > 0) {
      // 获取后缀下标
      const index = documentPicker.getSelectedIndex();
      if (index !== -1) {
        const suffix = options.fileSuffixChoices[index];
        console.log('选择的后缀类型:', suffix);
      }
    }
  } catch (error) {
    console.error('操作失败:', error);
  }
}

三、音频选择器 AudioViewPicker:专门处理音频文件

音频选择器用于选择和保存音频文件,使用方式和文档选择器类似:

1. 创建音频选择器实例

// 创建音频选择器实例
const audioPicker = new picker.AudioViewPicker(context);

2. 选择音频文件

// 选择音频文件
async function selectAudioFile(context: common.UIAbilityContext) {
  const audioPicker = new picker.AudioViewPicker(context);
  try {
    const options = new picker.AudioSelectOptions();
    options.maxSelectNumber = 3; // 最多选3个音频
    
    const uris = await audioPicker.select(options);
    console.log('选择的音频URI:', uris);
    // 处理音频文件
  } catch (error) {
    console.error('选择音频失败:', error);
  }
}

3. 保存音频文件

// 保存音频文件
async function saveAudioFile(context: common.UIAbilityContext) {
  const audioPicker = new picker.AudioViewPicker(context);
  try {
    const options = new picker.AudioSaveOptions();
    options.newFileNames = ['录音.mp3', '背景音乐.wav'];
    
    const uris = await audioPicker.save(options);
    console.log('保存的音频URI:', uris);
    // 写入音频数据
  } catch (error) {
    console.error('保存音频失败:', error);
  }
}

四、废弃的图库选择器 PhotoViewPicker:及替代方案

旧版的 PhotoViewPicker 已废弃,推荐使用 photoAccessHelper 替代:

// 废弃的图库选择器使用示例(不推荐)
const photoPicker = new picker.PhotoViewPicker(context);
photoPicker.select((err, result) => {
  if (err) {
    console.error('选择图片失败:', err);
    return;
  }
  console.log('选择的图片URI:', result.photoUris);
});

// 推荐替代方案:使用photoAccessHelper
import { photoAccessHelper } from '@ohos.photos.photoAccessHelper';

async function selectPhotoWithAccessHelper() {
  try {
    const photoPicker = new photoAccessHelper.PhotoViewPicker(context);
    const options = new photoAccessHelper.PhotoSelectOptions();
    options.mimeType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
    options.maxSelectNumber = 2;
    
    const result = await photoPicker.select(options);
    console.log('选择的图片URI:', result.photoUris);
  } catch (error) {
    console.error('选择图片失败:', error);
  }
}

五、选择器选项配置:精细控制选择器行为

1. 文档选择选项 DocumentSelectOptions

// 文档选择选项配置示例
const options = new picker.DocumentSelectOptions();
options.maxSelectNumber = 10; // 最大选择数
options.defaultFilePathUri = '/storage/emulated/0/Downloads'; // 默认路径
options.fileSuffixFilters = ['文本文件|.txt,.md', '所有文件|.*']; // 后缀过滤
options.selectMode = picker.DocumentSelectMode.FOLDER; // 选择模式:文件、文件夹或混合
options.authMode = true; // 授权模式
options.multiAuthMode = true; // 批量授权模式
options.multiUriArray = ['uri1', 'uri2']; // 批量授权URI数组
options.mergeMode = picker.MergeTypeMode.AUDIO; // 聚合视图模式
options.isEncryptionSupported = true; // 支持加密

2. 文档保存选项 DocumentSaveOptions

// 文档保存选项配置示例
const saveOptions = new picker.DocumentSaveOptions();
saveOptions.newFileNames = ['新文件.txt']; // 文件名
saveOptions.defaultFilePathUri = '/storage/emulated/0/Documents'; // 保存路径
saveOptions.fileSuffixChoices = ['txt', 'pdf', 'docx']; // 后缀选项
saveOptions.pickerMode = picker.DocumentPickerMode.DOWNLOAD; // 下载模式

六、实际应用场景:文件选择与保存完整流程

1. 文档选择与处理完整流程

import { common } from '@kit.AbilityKit';

@Entry
@Component
struct FilePickerApp {
  @State selectedFiles: string[] = [];
  @State isLoading: boolean = false;

  private context: common.UIAbilityContext | null = null;

  aboutToAppear() {
    // 获取应用上下文
    this.context = this.getUIContext().getHostContext() as common.UIAbilityContext;
  }

  // 选择文件方法
  async selectDocuments() {
    this.isLoading = true;
    try {
      const documentPicker = new picker.DocumentViewPicker(this.context!);
      const options = new picker.DocumentSelectOptions();
      options.maxSelectNumber = 5;
      options.fileSuffixFilters = ['文档|.txt,.doc,.pdf', '图片|.jpg,.png'];
      
      const uris = await documentPicker.select(options);
      this.selectedFiles = uris;
      console.log('选择的文件:', uris);
      
      // 这里可以添加文件处理逻辑,比如读取文件内容
      this.processSelectedFiles(uris);
    } catch (error) {
      console.error('选择文件失败:', error);
    } finally {
      this.isLoading = false;
    }
  }

  // 处理选择的文件
  processSelectedFiles(uris: string[]) {
    uris.forEach(uri => {
      // 使用@ohos.file.fs接口读取文件内容
      // 这里只是示例,实际需要处理文件操作
      console.log('处理文件:', uri);
    });
  }

  // 保存文件方法
  async saveDocument() {
    if (this.selectedFiles.length === 0) {
      console.log('请先选择文件');
      return;
    }
    
    this.isLoading = true;
    try {
      const documentPicker = new picker.DocumentViewPicker(this.context!);
      const options = new picker.DocumentSaveOptions();
      options.newFileNames = ['处理后的文件.txt'];
      options.fileSuffixChoices = ['txt', 'pdf'];
      
      const saveUris = await documentPicker.save(options);
      if (saveUris && saveUris.length > 0) {
        console.log('保存到:', saveUris[0]);
        // 这里可以添加文件写入逻辑
        this.writeToFile(saveUris[0], '这是处理后的文件内容');
      }
    } catch (error) {
      console.error('保存文件失败:', error);
    } finally {
      this.isLoading = false;
    }
  }

  // 写入文件内容
  writeToFile(uri: string, content: string) {
    // 使用@ohos.file.fs接口写入文件
    // 这里只是示例,实际需要处理文件写入
    console.log('写入内容到:', uri, '内容:', content);
  }

  build() {
    Column() {
      Text('文件选择器应用')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin(10)
      
      Button('选择文档')
        .onClick(() => this.selectDocuments())
        .margin(10)
        .disabled(this.isLoading)
      
      Button('保存处理后的文档')
        .onClick(() => this.saveDocument())
        .margin(10)
        .disabled(this.isLoading || this.selectedFiles.length === 0)
      
      if (this.selectedFiles.length > 0) {
        List({ space: 5 }) {
          ForEach(this.selectedFiles, (uri, index) => {
            ListItem() {
              Text(`文件 ${index + 1}: ${uri}`)
                .fontSize(16)
                .ellipsis({ end: true })
            }
          })
        }
        .width('90%')
      }
      
      if (this.isLoading) {
        LoadingProgress()
          .color('#007DFF')
          .width(50)
          .height(50)
          .margin(20)
      }
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Start)
    .padding(10)
  }
}

七、错误处理与最佳实践

1. 统一错误处理

function handlePickerError(error: any, operation: string) {
  console.error(`[${operation}] 操作失败`, error);
  if (error.code) {
    switch (error.code) {
      case 13900001:
        console.log('操作不允许,可能是参数错误或权限问题');
        break;
      case 401:
        console.log('参数错误,检查必填参数和类型');
        break;
      case 801:
        console.log('设备不支持该功能');
        break;
      default:
        console.log(`错误码 ${error.code},信息: ${error.message}`);
    }
  } else {
    console.log('未知错误:', error.message);
  }
}

// 使用示例
try {
  const uris = await documentPicker.select(options);
} catch (error) {
  handlePickerError(error, '选择文件');
}

2. 最佳实践建议

  1. 优先使用带 context 的构造函数:避免概率性失败问题
  2. 合理设置文件过滤:通过 fileSuffixFilters 限制显示的文件类型,提升用户体验
  3. 处理批量选择:maxSelectNumber 设置合适的选择数量上限
  4. 提供默认路径:defaultFilePathUri 设置常用路径,减少用户操作
  5. 处理异步回调:同时支持 Promise 和 callback 两种模式,适应不同场景
  6. 检查 API 版本:注意接口的起始版本,避免在不支持的设备上调用

八、总结:文件选择器 API 的三大优势与应用场景

1. 三大核心优势

  • 功能全面:支持文档、音频等多种文件类型,满足不同应用场景
  • 灵活配置:通过各种 Options 类可以精细控制选择器的行为
  • 安全可靠:返回的 URI 经过编码处理,支持加密和授权模式

2. 典型应用场景

  • 办公应用:打开和保存文档、表格、演示文稿
  • 媒体应用:选择音频文件进行编辑,保存处理后的音频
  • 文件管理器:实现文件浏览、选择、保存的核心功能
  • 社交应用:选择图片、视频进行分享
  • 备份工具:选择需要备份的文件,指定保存位置

通过 HarmonyOS 文件选择器 API,我们可以轻松实现应用中的文件选择和保存功能,提升用户体验。记住这些常用接口和配置方法,结合实际场景灵活运用,就能开发出强大的文件处理功能啦!

Logo

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

更多推荐