HarmonyOS中如何通过安全控件、弹窗授权实现媒体资源保存
·
问题现象
当申请ohos.permission.WRITE_IMAGEVIDEO权限被拒时,如何保存图片和视频到相册。
背景知识
ohos.permission.WRITE_IMAGEVIDEO是受限开放的权限,应用可以通过安全控件或授权弹窗的方式,将指定的媒体资源保存到相册中。
- 安全控件(SaveButton):安全控件的保存控件,用户通过点击该保存按钮,可以临时获取存储权限,而不需要权限弹框授权确认。使用此控件需要UI样式合法,不合法会导致授权失败,可参考安全控件样式的约束与限制。
- 授权弹窗(showAssetsCreationDialog):通过调用接口拉起保存确认弹窗,基于弹窗授权的方式获取的目标媒体文件uri。用户同意保存后,返回已创建并授予保存权限的uri列表,该列表永久生效,应用可使用该uri写入图片/视频。如果用户拒绝保存,将返回空列表。
解决方案
- 方案一:安全控件SaveButton可以临时获取存储权限,而不需要权限弹框授权确认,最终把图片保存到相册。
import photoAccessHelper from '@ohos.file.photoAccessHelper'; import fs from '@ohos.file.fs'; import { common } from '@kit.AbilityKit'; import { UIContext } from '@kit.ArkUI'; @Entry @Component struct Index4 { @State message: string = 'Hello World' uiContext: UIContext = this.getUIContext() build() { Row() { Column() { Image($r('app.media.icon')) .height(300) .width(300) SaveButton().onClick(async (_event: ClickEvent, result: SaveButtonOnClickResult) => { if (result === SaveButtonOnClickResult.SUCCESS) { try { let context: Context = this.uiContext.getHostContext() as common.UIAbilityContext; let helper = photoAccessHelper.getPhotoAccessHelper(context); // onClick触发后一分钟内通过createAsset接口创建图片文件,一分钟后createAsset权限收回 let uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg'); // 使用uri打开文件,可以持续写入内容,写入过程不受时间限制 let file = await fs.open(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); try { context.resourceManager.getMediaContent($r('app.media.startIcon').id, 0) .then(async value => { let media = value.buffer; // 写到媒体库文件中 await fs.write(file.fd, media); await fs.close(file.fd); this.uiContext.showAlertDialog({ message: '已保存至相册!' }); }); } catch (err) { console.error(`error is ${err}`); } } catch (error) { console.error(`error is ${error}`); } } else { this.uiContext.showAlertDialog({ message: '设置权限失败' }); } }) } .width('100%') } .height('100%') } }方案二:调用showAssetsCreationDialog弹窗授权保存图片到相册。
使用弹窗授权保存图片到相册,首先获取需要保存到媒体库的位于应用沙箱的图片/视频uri,然后调用showAssetsCreationDialog接口弹窗授权,通过fs.copyFileSync将图片保存到相册。
import { fileIo as fs, fileUri } from '@kit.CoreFileKit' import { common } from '@kit.AbilityKit'; import { photoAccessHelper } from '@kit.MediaLibraryKit'; import { camera, cameraPicker as picker } from '@kit.CameraKit'; @Entry @Component struct Index { @State message: string = 'SaveButton'; uiContext: UIContext = this.getUIContext() async saveFile() { let mContext: Context = this.uiContext.getHostContext() as common.UIAbilityContext; let types: Array<picker.PickerMediaType> = [picker.PickerMediaType.PHOTO]; let pickerProfile: picker.PickerProfile = { cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK, videoDuration: 15 }; let pickerResult: picker.PickerResult = await picker.pick(mContext, types, pickerProfile); if (pickerResult.resultCode === 0) { // 成功 let finalUri = pickerResult.resultUri; // 保存图片到缓存目录 let dirpath = (this.uiContext.getHostContext() as common.UIAbilityContext).tempDir + '/cameTem.jpg'; let dirUri = fileUri.getUriFromPath(dirpath); let finalFile = fs.openSync(finalUri, fs.OpenMode.READ_ONLY) let dirFile = fs.openSync(dirpath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE) fs.copyFileSync(finalFile.fd, dirFile.fd) fs.closeSync(finalFile); fs.closeSync(dirFile); console.info('ImagePicker', 'Succeeded in copying. '); // 删除相册图片(需要申请ohos.permission.WRITE_IMAGEVIDEO权限) try { await photoAccessHelper.MediaAssetChangeRequest.deleteAssets(this.uiContext.getHostContext(), [pickerResult.resultUri]) finalUri = dirUri; } catch (err) { console.error('ImagePicker', `deleteAssetsDemo failed with error: ${err.code}, ${err.message}`); } console.info('ShowAssetsCreationDialogDemo.'); // 获取需要保存到媒体库的位于应用沙箱的图片/视频uri try { let srcFileUris: Array<string> = [dirUri]; let photoCreationConfigs: Array<photoAccessHelper.PhotoCreationConfig> = [{ fileNameExtension: 'jpg', photoType: photoAccessHelper.PhotoType.IMAGE, }]; let context = this.uiContext.getHostContext() as common.UIAbilityContext; let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context); let desFileUris: Array<string> = await phAccessHelper.showAssetsCreationDialog(srcFileUris, photoCreationConfigs); console.info('showAssetsCreationDialog success, data is ' + desFileUris); if (desFileUris.length > 0) { try { let srcFile = fs.openSync(srcFileUris[0], fs.OpenMode.READ_ONLY) let desFile = fs.openSync(desFileUris[0], fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE) fs.copyFileSync(srcFile.fd, desFile.fd) fs.closeSync(srcFile); fs.closeSync(desFile); } catch (e) { console.error(e) } } } catch (err) { console.error('showAssetsCreationDialog failed, errCode is ' + err.code + ', errMsg is ' + err.message); } } } build() { RelativeContainer() { Text(this.message) .id('SaveButton') .fontSize(50) .fontWeight(FontWeight.Bold) .alignRules({ center: { anchor: '__container__', align: VerticalAlign.Center }, middle: { anchor: '__container__', align: HorizontalAlign.Center } }) .onClick(() => { this.saveFile(); }) } .height('100%') .width('100%') } }说明
调用showAssetsCreationDialog接口时请确保module.json5文件中的abilities标签中配置了label和icon项,因为showAssetsCreationDialog依赖module.json5文件中的abilities标签中label和icon项,所以module.json5文件中的abilities标签必须存在label和icon项。
module.json5的配置文件如下:
// module.json5的部分代码 "abilities": [ { "name": "EntryAbility", "srcEntry": "./ets/entryability/EntryAbility.ets", "description": "$string:EntryAbility_desc", "icon": "$media:app_icon", // 以product的配置为主 "label": "$string:app_name_test", "startWindowIcon": "$media:startIcon", "startWindowBackground": "$color:start_window_background", "exported": true, "skills": [ { "entities": [ "entity.system.home" ], "actions": [ "action.system.home", "wxentity.action.open" ] } ] } ]
更多推荐

所有评论(0)