文章

安全控件-保存网络图片至相册

安全控件-保存网络图片至相册

安全控件-保存网络图片至相册

安全控件-保存网络图片至相册

概述

安全控件是系统提供的一组系统实现的ArkUI组件,应用集成这类组件就可以实现在用户点击后自动授权,而无需弹窗授权。它们可以作为一种“特殊的按钮”融入应用页面,实现用户点击即许可的设计思路。

主要优点有:

  1. 用户可掌握授权时机,授权范围最小化。
  2. 授权场景可匹配用户真实意图。
  3. 减少弹窗打扰。
  4. 开发者不必向应用市场申请权限,简化操作。

安全控件列表

目前系统提供三类安全控件:

该控件对应剪贴板读取特权。应用集成粘贴控件后,用户点击该控件,应用读取剪贴板数据时不会弹窗提示。

建议使用场景:粘贴控件可以用于任何应用需要读取剪贴板的场景,避免弹窗提示对用户造成干扰。

该控件对应媒体库写入特权。应用集成保存控件后,用户点击该控件,应用会获取10秒内访问媒体库特权接口的授权。

建议使用场景:保存控件可以用于任何应用需要保存文件到媒体库的场景(保存图片、保存视频等)。与Picker需要拉起系统应用再由用户选择具体路径保存的方式不同,保存控件将直接保存到指定媒体库路径,操作更快捷。

该控件对应精准定位特权。应用集成位置控件后,用户点击该控件,无论应用是否申请过或者被授予精准定位权限,都会在本次前台期间获得精准定位的授权,可以调用位置服务获取精准定位。

建议使用场景:应用不是强位置关联应用(如导航、运动健康等),仅在部分前台场景需要使用位置信息(如定位城市、打卡、分享位置等)。如果需要长时间使用或是在后台使用位置信息,建议申请位置权限。

使用保存控件

保存控件是一种特殊的安全控件,它允许用户通过点击按钮临时获取存储权限,而无需通过权限弹框进行授权确认。

集成保存控件后,当用户点击该控件时,应用会获得10秒内访问媒体库特权接口的授权。这适用于任何需要将文件保存到媒体库的应用场景,例如保存图片或视频等。

与需要触发系统应用并由用户选择具体保存路径的Picker不同,保存控件可以直接保存到指定的媒体库路径,使得操作更为便捷。

使用 SaveButton

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import { http } from '@kit.NetworkKit';
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { promptAction } from '@kit.ArkUI';
import { fileIo as fs } from '@kit.CoreFileKit';

/**
 * TODO 图片相关工具类
 * 1、保存网络图片
 */
export class ImageUtil {
  static pixelMap: PixelMap | undefined = undefined;

  // 保存网络图片
  // 可以使用安全控件中的保存控件,免去权限申请和权限请求等环节,获得临时授权,保存对应图片
  // 需要申请权限:ohos.permission.INTERNET
  static saveImageWithUrl(url: string) {
    let responseCode = http.ResponseCode;
    let OutData: http.HttpResponse;
    let imagePackerApi = image.createImagePacker();
    let packOpts: image.PackingOption = { format: 'image/jpeg', quality: 98 };
    // 确保网络正常
    http.createHttp().request(url, {
      method: http.RequestMethod.GET,
      connectTimeout: 60000,
      readTimeout: 60000
    },
      async (error: BusinessError, data: http.HttpResponse) => {
        if (error) {
          console.error(`http reqeust failed with. Code: ${error.code}, message: ${error.message}`);
        } else {
          OutData = data;
          let code: http.ResponseCode | number = OutData.responseCode;
          if (responseCode.OK === code) {
            let imageData: ArrayBuffer = OutData.result as ArrayBuffer;
            let imageSource: image.ImageSource = image.createImageSource(imageData);

            class tmp {
              height: number = 100
              width: number = 100
            };

            let options: Record<string, number | boolean | tmp> = {
              'alphaType': 0, // 透明度
              'editable': false, // 是否可编辑
              'pixelFormat': 3, // 像素格式
              'scaleMode': 1, // 缩略值
              'size': { height: 100, width: 100 }
            }; // 创建图片大小
            imageSource.createPixelMap(options).then((pixelMap: PixelMap) => {
              ImageUtil.pixelMap = pixelMap;
              ImageUtil.pixelMap.getImageInfo().then((info: image.ImageInfo) => {
                console.info('info.width = ' + info.size.width);
              }).catch((err: BusinessError) => {
                console.error('Failed ' + err);
              })
              imagePackerApi.packing(pixelMap, packOpts).then(async (buffer: ArrayBuffer) => {
                try {
                  const context = getContext();
                  let helper = photoAccessHelper.getPhotoAccessHelper(context);
                  let uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'png');
                  let file = await fs.open(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
                  // 写入文件
                  await fs.write(file.fd, buffer);
                  promptAction.showToast({ message: '已保存至相册!' });
                  // 关闭文件
                  await fs.close(file.fd);
                } catch (error) {
                  console.error('error is ' + JSON.stringify(error));
                }
              }).catch((error: BusinessError) => {
                console.error('Failed to pack the image. And the error is: ' + error);
              })
              pixelMap.release();
            })
          }
        }
      }
    )
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Row() {
  Image(params.qrData)
    .width('240lpx')
    .height('240lpx')
    .margin({ right: '20lpx' })
  Column() {
    Text('点击下方按钮保存二维码')
      .TextExtend()
    Text('打开微信,扫一扫进行关注')
      .TextExtend()
    SaveButton({
      icon: SaveIconStyle.LINES,
      text: SaveDescription.SAVE_IMAGE
    })
      .onClick(async (event: ClickEvent, result: SaveButtonOnClickResult) => {
        if (result === SaveButtonOnClickResult.SUCCESS) {
          ImageUtil.saveImageWithUrl(params.qrData)
        } else {
          Toast.show('设置权限失败');
        }
      })
  }
  .alignItems(HorizontalAlign.Start)
}
本文由作者按照 CC BY 4.0 进行授权