# PhotoPicker的常见使用问题

#### 问题现象

HarmonyOS为开发者提供了PhotoViewPicker接口和PhotoPickerComponent组件，开发者可以使用这两种方式拉起媒体文件选择器，让用户自行选择媒体文件资源。本文总结了一些PhotoPicker的常见使用问题如下：

1.  PhotoViewPicker和PhotoPickerComponent的区别是什么，如何选择？
2.  PhotoPickerComponent存在哪些限制？
3.  使用Picker完成图片选择后返回的uri如何使用，直接使用该uri上传报错如何处理？

#### 背景知识

使用Picker选择媒体库资源：当用户需要分享图片、视频等文件时，开发者可以通过特定接口拉起系统图库，让用户自行选择待分享的资源，完成分享。此接口本身无需申请权限，目前适用于界面UIAbility，使用窗口组件触发。

使用PhotoPicker组件访问图片/视频：当应用需要读取用户图片时，开发者可以在应用界面中嵌入PhotoPickerComponent组件，在用户选择所需要的图片资源后，直接返回该图片资源，而不需要授予应用读取图片文件的权限，即可完成图片或视频文件的访问和读取。

#### 解决方案

1.  PhotoViewPicker和PhotoPickerComponent是HarmonyOS为开发者提供的选取图库媒体文件资源的两种方式，两者差异及适用场景如下：
    
    | 对比维度 | PhotoViewPicker | PhotoPickerComponent |
    | --- | --- | --- |
    | 本质类型 | 系统接口（@ohos.photoAccess.photoAccessHelper） | 嵌入式组件（@ohos.file.PhotoPickerComponent） |
    | 调用方式 | 异步拉起系统相册界面 | 直接嵌入应用布局 |
    | 界面交互 | 跳转至独立系统相册界面 | 内嵌在当前应用页面 |
    | 可配置项 | PhotoSelectOptions | PickerOptions |
    | 定制化能力 | 使用固定系统界面 | 可深度集成到自定义 UI（支持背景色/勾选框样式等配置） |
    | 适用场景 | 需系统级相册界面 要求文件类型过滤 需重复选择同一文件 | 避免页面跳转的沉浸式体验 需与自定义 UI 深度集成 简化交互流程（如勾选后直接编辑） |
    

2.  PhotoPickerComponent组件使用存在限制如下：
    *   不支持嵌套使用，用户使用PhotoPickerComponent选中媒体文件后，系统会将媒体文件的uri授权给应用，如果此时在Picker上方存在可点击事件，可能会对用户安全造成影响，因此PhotoPickerComponent上方覆盖设置了overlay属性的组件，将导致PhotoPickerComponent无法接受手势事件。
    *   PhotoPickerComponent不支持同层渲染。
    *   PhotoPickerComponent不支持在@ohos.inputMethod (输入法框架)中使用。

3.  使用Picker完成图片选择后，会直接返回该图片资源，系统出于安全考虑不允许直接对其进行上传，必须先存到沙箱，所以需要使用copyFileSync接口把文件资源读取到自己的沙箱目录中再进行操作。图库拷贝到沙箱参考代码：
    
    ```
    async copyFile2Sandbox(filePathString: string): Promise<boolean> {
      let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
      let resFile: fileIo.File | undefined;
      try {
        resFile = fileIo.openSync(filePathString, fileIo.OpenMode.READ_ONLY);
        // 创建临时文件目录
        let dateStr = (new Date().getTime()).toString();
        let newPath = context.cacheDir + `/${dateStr + resFile.name}`;
        // 拷贝系统图库图片到沙箱
        fileIo.copyFileSync(resFile.fd, newPath);
        return true;
      } catch (error) {
        console.error(`Failed to copy file: ${JSON.stringify(error)}`);
        return false;
      } finally {
        if (resFile) {
          fileIo.closeSync(resFile);
        }
      }
    }
    ```
    
    此示例以PhotoPickerComponent为例，PhotoViewPicker同理，完整代码如下：
    
    ```
    import { common } from '@kit.AbilityKit';
    import { fileIo } from '@kit.CoreFileKit';
    import {
      PhotoPickerComponent,
      PickerController,
      PickerOptions,
      ItemInfo,
      ItemType,
      ClickType,
      ReminderMode,
      photoAccessHelper,
    } from '@kit.MediaLibraryKit';
    
    
    @Entry
    @Component
    struct Index {
      // 组件初始化完成后，可控制组件部分行为。
      @State pickerController: PickerController = new PickerController();
      // 组件初始化时设置参数信息。
      pickerOptions: PickerOptions = new PickerOptions();
      // 目前选择的图片。
      currentUri: string = '';
    
    
      aboutToAppear() {
        // 设置picker宫格页数据类型
        this.pickerOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 只显示图片
        // 最大选择数量。
        this.pickerOptions.maxSelectNumber = 1;
        // 超出最大选择数量时。
        this.pickerOptions.maxSelectedReminderMode = ReminderMode.TOAST;
      }
    
    
      // 资源被选中回调，返回资源的信息，以及选中方式。
      private onItemClicked(itemInfo: ItemInfo, clickType: ClickType): boolean {
        if (!itemInfo) {
          return false;
        }
        let type: ItemType | undefined = itemInfo.itemType;
        let uri: string | undefined = itemInfo.uri;
        if (type === ItemType.CAMERA) {
          // 点击相机item。
          return true; // 返回true则拉起系统相机，若应用需要自行处理则返回false。
        } else {
          if (clickType === ClickType.SELECTED) {
            // 应用做自己的业务处理。
            if (uri) {
              this.copyFile2Sandbox(uri)
                .then((result: boolean) => {
                  try {
                    this.getUIContext().getPromptAction().showToast({ message: result ? '文件拷贝成功' : '文件拷贝失败' });
                  } catch (err) {
                    console.error(`Failed to invoke showToast`);
                  }
                });
            }
            return true; // 返回true则勾选，否则则不响应勾选。
          }
          return true;
        }
      }
      async copyFile2Sandbox(filePathString: string): Promise<boolean> {
        let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
        let resFile: fileIo.File | undefined;
        try {
          resFile = fileIo.openSync(filePathString, fileIo.OpenMode.READ_ONLY);
          // 创建临时文件目录
          let dateStr = (new Date().getTime()).toString();
          let newPath = context.cacheDir + `/${dateStr + resFile.name}`;
          // 拷贝系统图库图片到沙箱
          fileIo.copyFileSync(resFile.fd, newPath);
          return true;
        } catch (error) {
          console.error(`Failed to copy file: ${JSON.stringify(error)}`);
          return false;
        } finally {
          if (resFile) {
            fileIo.closeSync(resFile);
          }
        }
      }
    
    
    
    
      build() {
        Flex({
          direction: FlexDirection.Column,
          alignItems: ItemAlign.Center,
          justifyContent: FlexAlign.Center,
        }) {
          Column() {
            PhotoPickerComponent({
              pickerOptions: this.pickerOptions,
              pickerController: this.pickerController,
              onItemClicked: (itemInfo: ItemInfo, clickType: ClickType): boolean => this.onItemClicked(itemInfo, clickType),
            });
          }
          .width('100%')
          .aspectRatio(1);
        };
      }
    }
    ```
    

#### 常见FAQ

Q：PhotoViewPicker在@ohos.file.picker中跟@ohos.file.photoAccessHelper都存在，两个包下的PhotoViewPicker有什么区别？

A：@ohos.file.picker下的PhotoViewPicker适用于图片或视频类型文件的选择与保存（该接口在后续版本不再演进），请使用@ohos.file.photoAccessHelper下的PhotoAccessHelper中的PhotoViewPicker来选择图片或视频文件。

Q：用户手动取消选中uri并点击确认和直接点击右上角关闭按钮退出选择页面，这两种场景如何区分？

A：目前PhotoViewPicker取消所有已选，“完成”这个按钮是不可点的状态，只能通过右上角的关闭按钮退出选择页面。

Q：使用PhotoViewPicker是否勾选原图，返回的图片都是同一个uri，并且图片文件的大小也一样，如何区分是否勾选原图？

A：可根据返回PhotoSelectResult中的isOriginalPhoto区分选择后的媒体文件是否为原图，系统不会主动对图片进行压缩，仅将用户选择结果传递给调用方，由调用方自行决定如何压缩处理图片。

Q：拉起媒体库的时候是否能选择沙箱里的图片文件？

A：无法选择；应用沙箱内的文件无法直接通过系统媒体库选择器（Picker）进行访问或选择，因为沙箱路径对其他应用（包括系统应用）不可见。

Q：升级手机系统后，使用PhotoPicker时出现图库使用授权的页面后直接返回。

A：应用使用PhotoPicker前，需确保图库应用本身使用协议已经同意，才能正常使用该能力。

Q：不申请ohos.permission.READ\_IMAGEVIDEO权限和PhotoPicker可以访问图库实现自定义相册吗？

A：不可以，为了保护用户的照片和视频资产隐私安全，HarmonyOS系统制定了安全隐私规则和相应技术方案，只有克隆类和云盘类应用可以申请ohos.permission.READ\_IMAGEVIDEO权限用来访问全量媒体库，其他类型应用必须通过PhotoViewPicker来访问用户指定的图片，或者使用AlbumPickerComponent组件访问公共目录中的相册列表配合PhotoPickerComponent使用。

Q：通过PhotoViewPicker让用户选择图片后，应用拿到photoSelectResult.photoUris，是否仅在本次APP运行时有效？

A：通过PhotoViewPicker获取到的图片photoUris在应用卸载前一直有效。

Q：使用PhotoViewPicker时，同一张图片是否可以多次被选中，比如一张图片点击两次，那么result里包含同一张图片的路径两次？

A：pickeroptions中isRepeatSelectSupported参数支持单张图片重复选择。true表示支持。默认不支持。

Q：使用PhotoViewPicker拉起相册选择，只显示gif格式的图片让用户选择，该如何实现？

A：MimeTypeFilter支持文件类型的过滤配置，通过配置gif可以实现只显示gif格式的图片让用户选择。

Q：通过PhotoViewPicker.select从相册选取图片，获取到选取图片的uri数组，使用dataSharePredicates查询时，如何查询多个项目？

A：使用PhotoAccessHelper.PhotoKeys.URI做查询条件时，仅支持使用DataSharePredicates.equalTo的方式，DataSharePredicates.in不支持，可以通过for循环的方式去获取多个图片信息。

Q：使用PhotoPicker选择图片并编辑，图片的uri不变。

A：相册图片编辑后，uri不变属于规格，图片做了编辑操作，会发送图片的update通知，具体可参考媒体资源变更通知相关指导。

Q：PhotoViewPicker是否支持设置最小选择数量？

A：PhotoViewPicker仅支持设置最大选择数量，开发者可以自行记录用户选择的媒体文件数量，在数量不足时对用户进行弹窗提示。