Skip to content

0x019-鸿蒙应用开发笔记6-权限申请和相册选择

权限申请

关于权限检测可参考官方文档: @ohos.abilityAccessCtrl (程序访问控制管理)
简单封装如下:

js
import { abilityAccessCtrl, Permissions, bundleManager, common } from '@kit.AbilityKit';  
import { BusinessError } from '@kit.BasicServicesKit';  
import { ToastUtil } from '@pura/harmony-utils';

/**  
 * 用于检测授权、申请授权  
 * API 文档: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-abilityaccessctrl-V5#checkaccesstokensync10 */export class PermissionManager {  
  /**  
   * 判断授权,没有授权申请授权  
   *   * @param permissions   * @param callback   */  static checkAndRequestPermission(permissions: Permissions, callback: (result: boolean) => void) {  
    if (PermissionManager.checkPermissions(permissions)) {  
      callback(true)  
      return  
    }  
    PermissionManager.requestPermission(permissions, callback)  
  }  
  
  static jumpToAppSetting() {  
    // 获取上下文  
    const context = getContext() as common.UIAbilityContext  
    // 获取包信息  
    const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION)  
    // 打开系统设置页  
    context.startAbility({  
      bundleName: 'com.huawei.hmos.settings',  
      abilityName: 'com.huawei.hmos.settings.MainAbility',  
      uri: 'application_info_entry',  
      parameters: {  
        // 按照包名打开对应设置页  
        pushParams: bundleInfo.name  
      }  
    })  
  }  
  
  /**  
   * 检查应用已经获取到权限  
   * @param permissions   * @returns 如果应用已经获取到权限,返回true; 如果应用没获取过授权,或者用户拒绝了授权,返回false;  
   */  static checkPermissions(permissions: Permissions): boolean {  
    // 程序访问控制管理  
    const atManager = abilityAccessCtrl.createAtManager();  
    // 获取 bundle 信息  
    const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION)  
    const tokenID: number = bundleInfo.appInfo.accessTokenId  
    // 校验应用是否被授予权限,同步返回结果  
    const authResult = atManager.checkAccessTokenSync(tokenID, permissions)  
    return authResult === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;  
  }  
  
  /**  
   * 针对某个权限进行申请授权。弹窗授权只会弹出一次。如果已经授权过,会直接返回成功 或 失败  
   *   * @param permissions   * @param callback   */  static requestPermission(permissions: Permissions, callback: (result: boolean) => void) {  
    // 程序访问控制管理  
    const atManager = abilityAccessCtrl.createAtManager();  
    // 拉起弹框请求用户授权  
    atManager.requestPermissionsFromUser(getContext(), [permissions]).then(data => {  
      const result = data.authResults.every(item => item === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)  
      callback(result)  
    }).catch((e: BusinessError) => {  
      ToastUtil.showShort("申请权限失败:" + e.code)  
    })  
  }  
}
import { abilityAccessCtrl, Permissions, bundleManager, common } from '@kit.AbilityKit';  
import { BusinessError } from '@kit.BasicServicesKit';  
import { ToastUtil } from '@pura/harmony-utils';

/**  
 * 用于检测授权、申请授权  
 * API 文档: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-abilityaccessctrl-V5#checkaccesstokensync10 */export class PermissionManager {  
  /**  
   * 判断授权,没有授权申请授权  
   *   * @param permissions   * @param callback   */  static checkAndRequestPermission(permissions: Permissions, callback: (result: boolean) => void) {  
    if (PermissionManager.checkPermissions(permissions)) {  
      callback(true)  
      return  
    }  
    PermissionManager.requestPermission(permissions, callback)  
  }  
  
  static jumpToAppSetting() {  
    // 获取上下文  
    const context = getContext() as common.UIAbilityContext  
    // 获取包信息  
    const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION)  
    // 打开系统设置页  
    context.startAbility({  
      bundleName: 'com.huawei.hmos.settings',  
      abilityName: 'com.huawei.hmos.settings.MainAbility',  
      uri: 'application_info_entry',  
      parameters: {  
        // 按照包名打开对应设置页  
        pushParams: bundleInfo.name  
      }  
    })  
  }  
  
  /**  
   * 检查应用已经获取到权限  
   * @param permissions   * @returns 如果应用已经获取到权限,返回true; 如果应用没获取过授权,或者用户拒绝了授权,返回false;  
   */  static checkPermissions(permissions: Permissions): boolean {  
    // 程序访问控制管理  
    const atManager = abilityAccessCtrl.createAtManager();  
    // 获取 bundle 信息  
    const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION)  
    const tokenID: number = bundleInfo.appInfo.accessTokenId  
    // 校验应用是否被授予权限,同步返回结果  
    const authResult = atManager.checkAccessTokenSync(tokenID, permissions)  
    return authResult === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;  
  }  
  
  /**  
   * 针对某个权限进行申请授权。弹窗授权只会弹出一次。如果已经授权过,会直接返回成功 或 失败  
   *   * @param permissions   * @param callback   */  static requestPermission(permissions: Permissions, callback: (result: boolean) => void) {  
    // 程序访问控制管理  
    const atManager = abilityAccessCtrl.createAtManager();  
    // 拉起弹框请求用户授权  
    atManager.requestPermissionsFromUser(getContext(), [permissions]).then(data => {  
      const result = data.authResults.every(item => item === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)  
      callback(result)  
    }).catch((e: BusinessError) => {  
      ToastUtil.showShort("申请权限失败:" + e.code)  
    })  
  }  
}

使用:

js
PermissionManager.checkAndRequestPermission('ohos.permission.READ_IMAGEVIDEO', result => {
   if (!result) {
     AlertDialog.show({
       title: '温馨提示',
       message: '无权限,是否前往设置中开启?\n',
       buttons: [
         {
           value: '取消',
           action: () => {
           }
         },
         {
           value: '前往设置',
           enabled: true,
           defaultFocus: true,
           style: DialogButtonStyle.HIGHLIGHT,
           action: () => {
           PermissionManager.jumpToAppSetting()
           }
         }
       ],
       cancel: () => {
       }
     })
   }
 })
PermissionManager.checkAndRequestPermission('ohos.permission.READ_IMAGEVIDEO', result => {
   if (!result) {
     AlertDialog.show({
       title: '温馨提示',
       message: '无权限,是否前往设置中开启?\n',
       buttons: [
         {
           value: '取消',
           action: () => {
           }
         },
         {
           value: '前往设置',
           enabled: true,
           defaultFocus: true,
           style: DialogButtonStyle.HIGHLIGHT,
           action: () => {
           PermissionManager.jumpToAppSetting()
           }
         }
       ],
       cancel: () => {
       }
     })
   }
 })

图片选择

需要注意:

  • 先判断授权情况,用户拒绝的时候,适当给与提醒,让用户去设置中开启
  • 图片选择后,需要复制到沙盒中进行读取

图片选择的简易封装示例:

js
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { PermissionManager } from './PermissionManager';
import { BusinessError } from '@kit.BasicServicesKit';
import { ToastUtil } from '@pura/harmony-utils';
import fs from '@ohos.file.fs'; // 导入文件管理模块


export enum PhotoPickerImageViaEnum {
  //CAMERA = 'CAMERA',
  PHOTO_LIBRARY = 'PHOTO_LIBRARY',
  CAMERA_OR_PHOTO_LIBRARY = 'CAMERA_OR_PHOTO_LIBRARY'
}

export interface FileBuffer {
  buffer: ArrayBuffer;
  fileName: string;
}

export class PhotoPicker {
  phAccessHelper: photoAccessHelper.PhotoAccessHelper;

  constructor() {
    let context = getContext(this);
    this.phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
  }
  // TODO: 图片压缩
  selectImage(type: PhotoPickerImageViaEnum, count: number, callback: (photoUris: string[]) => void) {
    PermissionManager.checkAndRequestPermission('ohos.permission.READ_IMAGEVIDEO', result => {
      if (!result) {
        AlertDialog.show({
          title: '温馨提示',
          message: '您没有相册访问权限,请到"设置-应用和元服务"中开启\n',
          buttons: [
            {
              value: '取消',
              action: () => {
              }
            },
            {
              value: '去设置',
              enabled: true,
              defaultFocus: true,
              style: DialogButtonStyle.HIGHLIGHT,
              action: () => {
                // TODO: 可以改成 toAppSetting 这个方案
                PermissionManager.jumpToAppSetting()
              }
            }
          ],
          cancel: () => {
          }
        })
      } else {
        this.loadImage(type, count, callback)
      }
    })
  }

  /**
   * 文件路径转为buffer (最好try catch起来) 。参考: https://ohpm.openharmony.cn/#/cn/detail/@ohos%2Faxios
   *
   * @param filePath
   * @returns
   */
  static fileToBuffer(filePath: string): FileBuffer {
    // 上传图片必须将文件拷贝到沙盒
    const file = fs.openSync(filePath, fs.OpenMode.READ_ONLY)
    const fileName = file.name;
    const newPath = getContext().cacheDir + '/' + fileName;
    fs.copyFileSync(file.fd, newPath)

    let file2 = fs.openSync(newPath, 0o2);
    let stat = fs.lstatSync(newPath);
    let buf2 = new ArrayBuffer(stat.size);
    fs.readSync(file2.fd, buf2); // 以同步方法从流文件读取数据。
    fs.fsyncSync(file2.fd);
    fs.closeSync(file2.fd);
    return { fileName, buffer: buf2 }
  }

  private loadImage(type: PhotoPickerImageViaEnum, count: number, callback: (photoUris: string[]) => void) {
    try {
      let photoPicker = new photoAccessHelper.PhotoViewPicker();
      // 是否仅支持图片
      const isPhotoTakingSupported = type != PhotoPickerImageViaEnum.PHOTO_LIBRARY
      photoPicker.select({
        isEditSupported: false,
        isOriginalSupported: true,
        maxSelectNumber: count,
        isPhotoTakingSupported: isPhotoTakingSupported,
        MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE
      }, (err: BusinessError, result: photoAccessHelper.PhotoSelectResult) => {
        if (err) {
          console.error(`PhotoViewPicker.select failed with err: ${err.code}, ${err.message}`);
          ToastUtil.showShort("选择图片失败:" + err.code)
          return;
        }
        callback(result.photoUris)
      })
    } catch (error) {
      let err: BusinessError = error as BusinessError;
      console.error(`PhotoViewPicker failed with err: ${err.code}, ${err.message}`);
    }
  }
}
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { PermissionManager } from './PermissionManager';
import { BusinessError } from '@kit.BasicServicesKit';
import { ToastUtil } from '@pura/harmony-utils';
import fs from '@ohos.file.fs'; // 导入文件管理模块


export enum PhotoPickerImageViaEnum {
  //CAMERA = 'CAMERA',
  PHOTO_LIBRARY = 'PHOTO_LIBRARY',
  CAMERA_OR_PHOTO_LIBRARY = 'CAMERA_OR_PHOTO_LIBRARY'
}

export interface FileBuffer {
  buffer: ArrayBuffer;
  fileName: string;
}

export class PhotoPicker {
  phAccessHelper: photoAccessHelper.PhotoAccessHelper;

  constructor() {
    let context = getContext(this);
    this.phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
  }
  // TODO: 图片压缩
  selectImage(type: PhotoPickerImageViaEnum, count: number, callback: (photoUris: string[]) => void) {
    PermissionManager.checkAndRequestPermission('ohos.permission.READ_IMAGEVIDEO', result => {
      if (!result) {
        AlertDialog.show({
          title: '温馨提示',
          message: '您没有相册访问权限,请到"设置-应用和元服务"中开启\n',
          buttons: [
            {
              value: '取消',
              action: () => {
              }
            },
            {
              value: '去设置',
              enabled: true,
              defaultFocus: true,
              style: DialogButtonStyle.HIGHLIGHT,
              action: () => {
                // TODO: 可以改成 toAppSetting 这个方案
                PermissionManager.jumpToAppSetting()
              }
            }
          ],
          cancel: () => {
          }
        })
      } else {
        this.loadImage(type, count, callback)
      }
    })
  }

  /**
   * 文件路径转为buffer (最好try catch起来) 。参考: https://ohpm.openharmony.cn/#/cn/detail/@ohos%2Faxios
   *
   * @param filePath
   * @returns
   */
  static fileToBuffer(filePath: string): FileBuffer {
    // 上传图片必须将文件拷贝到沙盒
    const file = fs.openSync(filePath, fs.OpenMode.READ_ONLY)
    const fileName = file.name;
    const newPath = getContext().cacheDir + '/' + fileName;
    fs.copyFileSync(file.fd, newPath)

    let file2 = fs.openSync(newPath, 0o2);
    let stat = fs.lstatSync(newPath);
    let buf2 = new ArrayBuffer(stat.size);
    fs.readSync(file2.fd, buf2); // 以同步方法从流文件读取数据。
    fs.fsyncSync(file2.fd);
    fs.closeSync(file2.fd);
    return { fileName, buffer: buf2 }
  }

  private loadImage(type: PhotoPickerImageViaEnum, count: number, callback: (photoUris: string[]) => void) {
    try {
      let photoPicker = new photoAccessHelper.PhotoViewPicker();
      // 是否仅支持图片
      const isPhotoTakingSupported = type != PhotoPickerImageViaEnum.PHOTO_LIBRARY
      photoPicker.select({
        isEditSupported: false,
        isOriginalSupported: true,
        maxSelectNumber: count,
        isPhotoTakingSupported: isPhotoTakingSupported,
        MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE
      }, (err: BusinessError, result: photoAccessHelper.PhotoSelectResult) => {
        if (err) {
          console.error(`PhotoViewPicker.select failed with err: ${err.code}, ${err.message}`);
          ToastUtil.showShort("选择图片失败:" + err.code)
          return;
        }
        callback(result.photoUris)
      })
    } catch (error) {
      let err: BusinessError = error as BusinessError;
      console.error(`PhotoViewPicker failed with err: ${err.code}, ${err.message}`);
    }
  }
}

文件上传(axios版)

js
static selectPictures(filePath: string, tag: string, webComponent: WebComponent | null) {
    const pk = new PhotoPicker();
    pk.selectImage(PhotoPickerImageViaEnum.CAMERA_OR_PHOTO_LIBRARY, 1, images => {
      if (!images || images.length === 0) {
        return
      }
      let formData = new FormData();
      let imgBase64 = ''
      const path = images[0];
      try {
        const bufferModel = PhotoPicker.fileToBuffer(path)
        formData.append('file', bufferModel.buffer, { filename: bufferModel.fileName });
        // https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V2/js-apis-buffer-0000001478181693-V2#ZH-CN_TOPIC_0000001523488722__bufferfrom-1
        const bf = buffer.from(bufferModel.buffer);
        imgBase64 = bf.toString('base64')
      } catch (error) {
        ToastUtil.showShort('图片处理失败,' + error)
      }
	  // TODO: 调用接口进行上传        
    })
  }
static selectPictures(filePath: string, tag: string, webComponent: WebComponent | null) {
    const pk = new PhotoPicker();
    pk.selectImage(PhotoPickerImageViaEnum.CAMERA_OR_PHOTO_LIBRARY, 1, images => {
      if (!images || images.length === 0) {
        return
      }
      let formData = new FormData();
      let imgBase64 = ''
      const path = images[0];
      try {
        const bufferModel = PhotoPicker.fileToBuffer(path)
        formData.append('file', bufferModel.buffer, { filename: bufferModel.fileName });
        // https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V2/js-apis-buffer-0000001478181693-V2#ZH-CN_TOPIC_0000001523488722__bufferfrom-1
        const bf = buffer.from(bufferModel.buffer);
        imgBase64 = bf.toString('base64')
      } catch (error) {
        ToastUtil.showShort('图片处理失败,' + error)
      }
	  // TODO: 调用接口进行上传        
    })
  }

调用接口,参考axois的文档,需要传入context:

js
/**
 * 用于图片上传
 * @param url
 * @param formData
 * @param baseURL
 */
export function upload(url: string, formData: FormData, baseURL: string = HttpConstant.BASE_URL) {
  return http({
    url: url,
    data: formData,
    method: 'post',
    context: getContext(),
    baseURL: baseURL,
    timeout: 1000 * 15,
    headers: {
      'Content-Type': 'multipart/form-data'
    },
  })
}
/**
 * 用于图片上传
 * @param url
 * @param formData
 * @param baseURL
 */
export function upload(url: string, formData: FormData, baseURL: string = HttpConstant.BASE_URL) {
  return http({
    url: url,
    data: formData,
    method: 'post',
    context: getContext(),
    baseURL: baseURL,
    timeout: 1000 * 15,
    headers: {
      'Content-Type': 'multipart/form-data'
    },
  })
}

参考文档

参考文档:

https://juejin.cn/post/7373681115746648103
https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-photoaccesshelper-V5#photoaccesshelper