当前位置: 首页 > news >正文

鸿蒙HarmonyOS 关于图片、视频的选择详解

背景

在聊天软件中,发送相册中视频和照片、用相机拍摄视频和图片发送是很常用的功能。在Android和iOS端,大部分应用都通过API方式定义UI来实现相册选择照片、视频,相机拍摄照片、视频,它们一般都支持以下功能:

  1. 相册选择:
    1. 支持单选或多选;
    2. 对图片支持是否原图选择;
    3. 对于视频支持选择视频的文件大小、视频时长等过滤;
    4. 支持点击图像放大预览
  2. 对于相机拍摄
    1. 支持点击拍照,长按录制视频;
    2. 视频录制支持最大最小录制时长限制;
    3. 拍摄或录制结束后支持预览。

对于鸿蒙应用要实现上述功能,系统也提供了对应API,要实现上述功能需要几个系统权限:

3. 读取系统相册权限
4. 麦克风权限
5. 摄像头权限

HarmonyOS 权限系统介绍

与Android系统相比,HarmonyOS提供了更严谨的权限控制,这里不得不提HarmonyOS的应用权限管控策略。HarmonyOS 提供了一种允许应用访问系统资源(如:通讯录等)和系统能力(如:访问摄像头、麦克风等)的通用权限访问方式,来保护系统数据(包括用户个人数据)或功能,避免它们被不当或恶意使用,应用权限保护的对象可以分为数据和功能:

  • 数据包括个人数据(如照片、通讯录、日历、位置等)、设备数据(如设备标识、相机、麦克风等)。
  • 功能包括设备功能(如访问摄像头/麦克风、打电话、联网等)、应用功能(如弹出悬浮窗、创建快捷方式等)。

同时HarmonyOS 根据授权方式的不同,权限类型可分为system_grant(系统授权)和user_grant(用户授权):

  • system_grant(系统授权)指的是系统授权类型,在该类型的权限许可下,应用被允许访问的数据不会涉及到用户或设备的敏感信息,应用被允许执行的操作对系统或者其他应用产生的影响可控。如果在应用中申请了system_grant权限,那么系统会在用户安装应用时,自动把相应权限授予给应用。
  • user_grant(用户授权)指的是用户授权类型,在该类型的权限许可下,应用被允许访问的数据将会涉及到用户或设备的敏感信息,应用被允许执行的操作可能对系统或者其他应用产生严重的影响。该类型权限不仅需要在安装包中申请权限,还需要在应用动态运行时,通过发送弹窗的方式请求用户授权。在用户手动允许授权后,应用才会真正获取相应权限,从而成功访问操作目标对象。

例如,在应用权限列表中,麦克风和摄像头对应的权限都是属于用户授权权限,列表中给出了详细的权限使用理由。应用需要在应用商店的详情页面,向用户展示所申请的user_grant权限列表。

除了授权方式外还要了解另一个概念APL(Ability Privilege Level,元能力权限等级)等级。应用的等级可以分为以下三个等级,等级依次提高。

APL级别说明
normal默认情况下,应用的APL等级都为normal等级。
system_basic该等级的应用服务提供系统基础服务。
system_core该等级的应用服务提供操作系统核心能力。
应用APL等级不允许配置为system_core。

根据权限对于不同等级应用有不同的开放范围,权限类型对应分为以下三个等级,等级依次提高。

APL级别说明开放范围
normal允许应用访问超出默认规则外的普通系统资源,如配置Wi-Fi信息、调用相机拍摄等。这些系统资源的开放(包括数据和功能)对用户隐私以及其他应用带来的风险低。APL等级为normal及以上的应用。
system_basic允许应用访问操作系统基础服务(系统提供或者预置的基础功能)相关的资源,如系统设置、身份认证等。这些系统资源的开放对用户隐私以及其他应用带来的风险较高。1、APL等级为system_basic及以上的应用。2、部分权限对normal级别的应用受限开放,这部分权限在本指导中描述为“受限开放权限”。
system_core涉及开放操作系统核心资源的访问操作。这部分系统资源是系统最核心的底层服务,如果遭受破坏,操作系统将无法正常运行。1、 APL等级为system_core的应用。2、 仅对系统应用开放。

访问操作系统基础服务(系统提供或者预置的基础功能)相关的资源,如系统设置、身份认证等。这些系统资源的开放对用户隐私以及其他应用带来的风险较高。    1、APL等级为system_basic及以上的应用。2、部分权限对normal级别的应用受限开放,这部分权限在本指导中描述为“受限开放权限”。
system_core    涉及开放操作系统核心资源的访问操作。这部分系统资源是系统最核心的底层服务,如果遭受破坏,操作系统将无法正常运行。    1、 APL等级为system_core的应用。2、 仅对系统应用开放。
了解了授权方式和权限等级后,我们再来聊聊这个设计背后的原因。
首先聊聊授权方式,其中系统授权类似于Android中的普通权限申请,在清单文件声明即可,对于用户授权,类似于Android中的动态权限,需要触发弹窗让用户感知后决定是否授权。这个涉及背后的逻辑很清晰,对于数据不会涉及到用户或设备的敏感信息(比如使用网络、使用蓝牙等),默认声明后授权不会对系统产生什么破坏,如果也强制用户感知授权后才可以使用,对于开发者来说增加工作量,对用户体验也不是很友好;
其次,对于权限等级,HarmonyOS分成了三类,normal 级别的,即普通应用应用可以使用的权限,比如相机,麦克风等,只要说明使用的场景即可向用户申请;system_core级别的是只有系统应用可以使用,普通应用无法申请,如果普通应用使用可能会对系统造成不可修复的错误,比如类似Android的root权限,如果普通应用申请到后,删除了系统核心文件等,系统会遭到破坏;第三种是system_basic,主要对系统应用开发,对普通应用受限开放,什么是受限开放呢?比如说读取相册权限,如果授权给普通应用,用户赋予它这个权限后,它偷偷的将相册数据传送到应用服务端用户完全无法感知,但是对于一些应用,比如网盘、相册备份类应用,没有这个权限还无法工作,所以系统设计时考虑到这一点,对于特殊的应用向平台申请后,平台根据应用类型给特殊应用放开该权限。可能有人会疑惑,为什么读取相册的是受限,而相机、麦克风是normal权限,因为麦克风、相机无法在用户无感知时使用,而读取文件可以。

回到正题,选择图片视频需要system_basic 受限权限,麦克风相机也需要用户授权,为了减少授权导致的操作流程终端,HarmonyOS 提供了系统Picker,可以使用系统组件再不需要权限的情况下完成功能。
应用拉起系统Picker组件(文件选择器、照片选择器、联系人选择器等),由用户在Picker上选择对应的文件、照片、联系人等资源,应用即可获取到Picker的返回结果,系统Picker由系统独立进程实现,从系统Picker获取资源是一个跨进程调度过程。

为什么使用系统Picker获取资源不需要权限?因为从系统Picker获取资源需要用户操作,是用户可以感知的,所以不需要再申请权限。注意,此时应用获取到的读取资源的权限是临时的,受限的,只能临时受限访问对应的资源。我们从Picker里选择了1.jpeg,在我们应用进程可以操作这个文件,我们从系统相册中获取到另一个2.jpeg文件,在进程里直接访问是不可以的,而且对1.jpeg的访问也是有时效性的。

接下来介绍如何基于系统API,从相册选择照片和视频,以及从相机拍摄视频和照片。

从相册选择

HarmonyOS 提供了 photoAccessHelper.PhotoViewPicker() 获取系统相册中的图片,下面是使用PhotoViewPicker的示例:

import { BusinessError } from '@kit.BasicServicesKit';
async function example01() {try {let PhotoSelectOptions = new photoAccessHelper.PhotoSelectOptions();PhotoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;PhotoSelectOptions.maxSelectNumber = 5;let photoPicker = new photoAccessHelper.PhotoViewPicker();photoPicker.select(PhotoSelectOptions).then((PhotoSelectResult: photoAccessHelper.PhotoSelectResult) => {console.info('PhotoViewPicker.select successfully, PhotoSelectResult uri: ' + JSON.stringify(PhotoSelectResult));}).catch((err: BusinessError) => {console.error(`PhotoViewPicker.select failed with err: ${err.code}, ${err.message}`);});} catch (error) {let err: BusinessError = error as BusinessError;console.error(`PhotoViewPicker failed with err: ${err.code}, ${err.message}`);}
}

这里面涉及到两个对象PhotoSelectOptions和PhotoSelectResult,分别表示跳转到图片选择器的参数,和选择完返回的结果。
PhotoSelectOptions有一下三个参数:

名称类型必填说明
isEditSupported11+boolean是否支持编辑照片,true表示支持,false表示不支持,默认为true。
isOriginalSupported12+boolean是否显示选择原图按钮,true表示显示,false表示不显示,默认为true。

元服务API: 从API version 12开始,该接口支持在元服务中使用。
subWindowName12+string子窗窗口名称。

元服务API: 从API version 12开始,该接口支持在元服务中使用。


PhotoSelectOptions 继承自BaseSelectOptions,提供了以下配置:

名称类型必填说明
MIMEType10+PhotoViewMIMETypes可选择的媒体文件类型,若无此参数,则默认为图片和视频类型。

元服务API: 从API version 11开始,该接口支持在元服务中使用。
maxSelectNumber10+number选择媒体文件数量的最大值(最大可设置的值为500,若不设置则默认为50)。

元服务API: 从API version 11开始,该接口支持在元服务中使用。
isPhotoTakingSupported11+boolean是否支持拍照,true表示支持,false表示不支持,默认为true。

元服务API: 从API version 11开始,该接口支持在元服务中使用。
isSearchSupported11+boolean是否支持搜索,true表示支持,false表示不支持,默认为true。

元服务API: 从API version 11开始,该接口支持在元服务中使用。
recommendationOptions11+RecommendationOptions图片推荐相关配置参数。

元服务API: 从API version 11开始,该接口支持在元服务中使用。
preselectedUris11+Array<string>预选择图片的uri数据。

元服务API: 从API version 11开始,该接口支持在元服务中使用。
isPreviewForSingleSelectionSupported12+boolean单选模式下是否需要进大图预览,true表示需要,false表示不需要,默认为true。

元服务API: 从API version 12开始,该接口支持在元服务中使用。


综合上述配置说明,通过PhotoViewPicker可以实现最开始我们提到的:

  1. 支持选择图片和视频
  2. 支持设置最多选择媒体数量
  3. 支持是否选择原图
    不支持选择视频的最大最小长度配置。

看到这如果还有不知道从哪里开始入手了解鸿蒙开发技术、想要更深的掌握鸿蒙开发技术知识点的朋友们,或者是转行求职人员还在为面试问题而犯难的,可以动动手指进来参考一下针对‌鸿蒙开发学习‌而设计的系统性学习方案,涵盖基础入门到进阶实战项目相关学习文档:【鸿蒙开发学习指南】https://docs.qq.com/doc/DSk9ZeU9RTUhETm53

PhotoSelectResult是返回图库选择后的结果集,结构如下:

名称类型可读可写说明
photoUrisArray<string>返回图库选择后的媒体文件的uri数组,此uri数组只能通过临时授权的方式调用photoAccessHelper.getAssets接口去使用,具体使用方式参见用户文件uri介绍中的媒体文件uri的使用方式。
isOriginalPhotoboolean返回图库选择后的媒体文件是否为原图。

返回用户选中的媒体列表和是否选中原图。这里产生一个问题,如果既有选择视频又有选择图片,如何根据一个uri判断是视频还是图片?
这里有用到photoAccessHelper 来解析媒体信息,下面是一个方法封装:

public async uriGetAssets(uri:string): Promise<photoAccessHelper.PhotoAsset|undefined> {  try {  let context = getContext(this) as common.UIAbilityContext;  let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);  let predicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();  // 配置查询条件,使用PhotoViewPicker选择图片返回的uri进行查询  predicates.equalTo('uri', uri);  let fetchOption: photoAccessHelper.FetchOptions = {  fetchColumns: [photoAccessHelper.PhotoKeys.WIDTH, photoAccessHelper.PhotoKeys.HEIGHT, photoAccessHelper.PhotoKeys.TITLE, photoAccessHelper.PhotoKeys.DURATION],  predicates: predicates  };  let fetchResult: photoAccessHelper.FetchResult<photoAccessHelper.PhotoAsset> = await phAccessHelper.getAssets(fetchOption);  // 得到uri对应的PhotoAsset对象,读取文件的部分信息  const asset: photoAccessHelper.PhotoAsset = await fetchResult.getFirstObject();  Logg.i(TAG, 'asset displayName: ' + asset.displayName);  Logg.i(TAG, 'asset uri: '+asset.uri);  Logg.i(TAG, 'asset photoType: ' + asset.photoType);  Logg.i(TAG, 'asset width: ' +  asset.get(photoAccessHelper.PhotoKeys.WIDTH));  Logg.i(TAG, 'asset height: ' +  asset.get(photoAccessHelper.PhotoKeys.HEIGHT));  Logg.i(TAG, 'asset title: '  +  asset.get(photoAccessHelper.PhotoKeys.TITLE));  return asset;  } catch (error){  Logg.e(TAG, 'uriGetAssets failed with err: ' + JSON.stringify(error));  }  return undefined;  
}

FetchOptions用来配置要查询的媒体信息内容,比如长、宽等信息,其中photoAccessHelper.PhotoAsset的photoType表示媒体类型,是音频还是视频。

接下里我们再介绍另一个能力,如果是视频的话有时候想要获取视频封面,即Thumbnail,photoAccessHelper.PhotoAsset提供了查询封面的接口:getThumbnail(),返回的是一个pixelMap,如何将pixelMap转换成图像呢?在pixelMap文档里看到从pixelMap读取buffer的方法:

import { BusinessError } from '@kit.BasicServicesKit';
async function Demo() {const readBuffer: ArrayBuffer = new ArrayBuffer(96); // 96为需要创建的像素buffer大小,取值为:height * width *4if (pixelMap != undefined) {pixelMap.readPixelsToBuffer(readBuffer, (error: BusinessError, res: void) => {if(error) {console.error(`Failed to read image pixel data. code is ${error.code}, message is ${error.message}`);// 不符合条件则进入return;} else {console.info('Succeeded in reading image pixel data.');  //符合条件则进入}})}
}

上面ArrayBuffer需要指定大小,将前面获取到的宽高相乘再乘以4即可。将获取到的readBuffer写入文件发现图片不是正常的图片,这里又需要用到image.createImagePacker():

let imagePath:string|undefined = undefined;  
let pixelMap = await matedata.getThumbnail();  
Logg.i(this.TAG, 'getThumbnail successful ' + JSON.stringify(pixelMap));  
let cacheDir = getContext().cacheDir;  
imagePath = `${cacheDir}/thumbnail${Date.now()}.jpg`;  
let dstFile = fs.openSync(imagePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);  
let packOpts: image.PackingOption = { format: "image/jpeg", quality: 98 };  
await image.createImagePacker().packToFile(pixelMap, dstFile.fd, packOpts)  
fs.close(dstFile);  
Logg.i(this.TAG, "copy file success");

需用用createImagePacker将pixelMap写入到目标文件,并且指定图片格式和质量。

从相机拍摄

上面实现了从相册获取照片,接下来实现使用摄像机拍照和拍视频功能。
有两种方式可以实现打开系统相机进行拍摄。

Want
下面是示例代码:

invokeCamera(callback: (uri: string) => void) {  const context = getContext(this) as common.UIAbilityContext;  const want: Want = {  action: 'ohos.want.action.imageCapture',  parameters: {  "callBundleName": context.abilityInfo.bundleName,  }  };  const result: (error: BusinessError, data: common.AbilityResult) => void =  (error: BusinessError, data: common.AbilityResult) => {  if (error && error.code !== 0) {  console.log(`${TAG_CAMERA_ERROR} ${JSON.stringify(error.message)}`)  return;  }  // 获取相机拍照后返回的图片地址  const resultUri: string = data.want?.parameters?.resourceUri as string;  if (callback && resultUri) {  callback(resultUri);  }  }  context.startAbilityForResult(want, result);  
}

通过want意图对象打开相机界面,参考 如何调用系统拍照并获取图片,这种方式无法设置录制视频最大长度等。

cameraPicker
相机选择器使用示例:

import { cameraPicker as picker } from '@kit.CameraKit';
import { camera } from '@kit.CameraKit';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
let mContext = getContext(this) as common.Context;async function demo() {try {let pickerProfile: picker.PickerProfile = {cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK};let pickerResult: picker.PickerResult = await picker.pick(mContext,[picker.PickerMediaType.PHOTO, picker.PickerMediaType.VIDEO], pickerProfile);console.log("the pick pickerResult is:" + JSON.stringify(pickerResult));} catch (error) {let err = error as BusinessError;console.error(`the pick call failed. error code: ${err.code}`);}
}

示例中mediaTypes数组指定了媒体格式,可以包含视频和图片;PickerProfile指定了摄像头是前置还是后置,主要有一下属性:

名称类型必填说明
cameraPositioncamera.CameraPosition相机的位置。
saveUristring保存配置信息的uri。
videoDurationnumber录制的最大时长。


PickerResult是相机选择器的处理结果,有一下属性:

名称类型必填说明
resultCodenumber处理的结果,成功返回0,失败返回-1。
resultUristring返回的uri地址。若saveUri为空,resultUri为公共媒体路径。若saveUri不为空且具备写权限,resultUri与saveUri相同。若saveUri不为空且不具备写权限,则无法获取到resultUri。
mediaTypePickerMediaType返回的媒体类型。


根据返回结果中mediaType来表示是图片还是视频,按照不同媒体类型处理即可。

总结

本文介绍了HarmonyOS Next中图片视频选择、图片视频拍摄的能力,重点分析了无需权限即可实现的photoAccessHelper和cameraPicker组件。

http://www.lqws.cn/news/547363.html

相关文章:

  • Veo 3 视频生成大模型完整操作教程(2025)
  • 《Effective Python》第十章 健壮性——始终将资源传递给生成器,并在外部由调用者清理它们
  • 【RAG面试题】如何获取准确的语义表示
  • ​​Git提交代码Commit消息企业级规范
  • algorithm ——————》双指针(移动0 复写0 快乐数 装水问题 以及数组中找几个数和为指定的元组)
  • 链表两数相加深度解析【进位】【边界条件】【迭代】【递归】
  • Spring Boot 应用开发实战指南:从入门到实战(内含实用技巧+项目案例)
  • 人工智能-基础篇-2-什么是机器学习?(ML,监督学习,半监督学习,零监督学习,强化学习,深度学习,机器学习步骤等)
  • Windows的xshell连接VW里的centos系统里的mysql失败解决方法
  • PostgreSQL 主从集群搭建
  • 杭州市长姚高员带队调研景联文科技,听取高质量数据集建设情况
  • [特殊字符] Python 批量合并 Word 表格中重复单元格教程(收货记录案例实战)
  • 从零开始的二三维CAD|CAE轻量级软件开发:学习以及研发,Gmsh的脚本编辑器设计!
  • python 脚本 遍历目录,并把目录下的非utf-8文件改成utf8
  • 16.2 Docker多阶段构建实战:LanguageMentor镜像瘦身40%,支持500+并发1.2秒响应!
  • 02【C++ 入门基础】标准输入输出初识/缺省参数
  • Qt 与 Halcon 联合开发六:基于海康SDK设计完整的相机类【附源码】
  • 【Elasticsearch】Linux环境下安装Elasticsearch
  • git rebase -i 详解
  • 微服务中解决高并发问题的不同方法!
  • 未来蓝图:引领能源数字化新浪潮
  • html制作一个简单的表单
  • 每天一个前端小知识 Day 14 - 前端状态管理深入实践
  • [1-01-01].第27节:常用类 - 包装类
  • 26考研|数学分析:隐函数定理及其应用
  • 官方App Store,直链下载macOS ,无需Apple ID,macOS10.10以上.
  • php flush实时输出线上环境好使,本地环境等待一段时间后一次性输出结果的原因
  • 跨芯片 AI 算子库 FlagGems 正式加入PyTorch 基金会生态项目体系
  • MyBatis中的SQL理解
  • uniappx 安卓app项目本地打包运行,腾讯地图报错:‘鉴权失败,请检查你的key‘