前言

大家好,我是若城。本系列专注于帮助开发者快速实现 HarmonyOS Next 应用中的常用功能,提供即拿即用的代码示例。

本篇文章将详细介绍两种将网络图片保存到相册的实现方法:

  • 方法一:需要申请相册权限,但在应用上架时 ACL 权限审核较为严格

  • 方法二:无需申请相册权限,通过用户手动授权实现

让我们开始深入了解这两种方案的具体实现!

核心功能实现

方法一:权限申请方案

重要提醒:此方法在应用上架过程中需要申请 ACL 权限,审核通过率较低。根据实际测试,连续提交 3 次申请均被拒绝。

1.1 权限配置

首先在 module.json5 文件中配置所需权限:

 

权限配置示例

权限配置示例

 

权限配置代码

权限配置代码

"requestPermissions": [
  {
    "name": "ohos.permission.INTERNET"
  },
  {
    "name": "ohos.permission.READ_IMAGEVIDEO",
    "usedScene": {
      "abilities": [
        "EntryAbility"
      ],
      "when": "inuse"
    },
    "reason": "$string:CAMERA"
  },
  {
    "name": "ohos.permission.WRITE_IMAGEVIDEO",
    "usedScene": {
      "abilities": [
        "EntryAbility"
      ],
      "when": "inuse"
    },
    "reason": "$string:CAMERA"
  }
]

1.2 模块导入

导入所需的功能模块:

import { abilityAccessCtrl, common } from '@kit.AbilityKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import fs from '@ohos.file.fs';
import { http } from '@kit.NetworkKit';
import { promptAction } from '@kit.ArkUI';

1.3 核心功能实现

下载网络图片并保存到相册的完整函数封装:

// 保存图片到相册
async saveFile(url: string) {
  this.downLoadImg = true;
  
  try {
    // 申请相册管理模块权限 'ohos.permission.WRITE_IMAGEVIDEO'
    this.atManager.requestPermissionsFromUser(this.appContext, ['ohos.permission.WRITE_IMAGEVIDEO'])
      .then(async () => {
        // 权限申请成功,开始保存到图库
        let context = getContext();
        
        // 获取相册管理模块的实例,用于访问和修改相册中的媒体文件
        let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
        
        // 显示下载提示(注意:onClick触发后10秒内通过createAsset接口创建图片文件,10秒后权限收回)
        this.promptAction.showToast({
          message: '图片下载中....',
          duration: 2000
        });

        // 创建图片资源
        let uri = await phAccessHelper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg');
        let file = fs.openSync(uri, fs.OpenMode.READ_WRITE || fs.OpenMode.CREATE);
        let totalSize = 0;
        
        // 创建HTTP请求
        let httpRequest = http.createHttp();
        
        // 监听数据接收
        httpRequest.on("dataReceive", (data: ArrayBuffer) => {
          let writeLen = fs.writeSync(file.fd, data);
          totalSize = totalSize + writeLen;
        });
        
        // 发起流式请求
        httpRequest.requestInStream(url, {
          method: http.RequestMethod.GET,
          connectTimeout: 3000,
        }, httpCode => {
          console.info('requestInStream HTTP CODE is', httpCode)
        });
        
        // 监听下载完成
        httpRequest.on("dataEnd", () => {
          fs.close(file);
          this.promptAction.showToast({
            message: "下载图片结束,并保存至相册",
            duration: 2000,
            alignment: Alignment.Center,
          });
          this.downLoadImg = false;
        });
      });
  } catch (err) {
    console.error(`requestPermissionsFromUser call Failed! error: ${err.code}`);
    this.downLoadImg = false;
  }
}

1.4 UI 组件使用

// 下载按钮组件
Column() {
  Image($r("app.media.downImg"))
    .width(24)
    .height(24)
  Text('下载')
    .fontSize(12)
    .fontColor('#FFFFFF')
    .margin({ top: 4 })
}
.onClick(() => {
  if (this.downLoadImg) {
    promptAction.showToast({
      message: '正在下载中,请稍后',
      duration: 1000,
    });
  } else {
    this.saveFile(this.dataList.pic_url)
  }
})
.margin({ right: 32 })

1.5 数据类型定义

@State dataList: Poem = {
  "_id": "68962176fb8dca9bad565162",
  "date": "20250810",
  "author": "洛尔迦",
  "content": "我会走得很远,远过这些山丘,远过这些大海,直到靠近星星。",
  "from": "诗人",
  "like": 117,
  "pic_url": "https://pics.tide.moreless.io/dailypics/FnDB9yZb_8lAL9tvw2Ug3x1AO6Dh?imageView2/1/w/1366/h/768/format/webp",
  "share": 114,
  "thumb": "https://pics.tide.moreless.io/dailypics/FnDB9yZb_8lAL9tvw2Ug3x1AO6Dh?imageView2/1/w/1366/h/768/format/webp?imageView2/1/w/300/h/300/format/webp"
}

💡 核心要点:上述代码详细展示了如何将网络图片保存到相册的完整流程,函数传参为网络图片的 URL 地址。


方法二:免权限申请方案

第二种方法无需申请相关权限,是官方推荐的实现方式。虽然默认 UI 样式定制性有限,但我们可以通过以下技巧来解决:

UI 定制解决方案

  1. 使用 Stack 布局,正常编写自定义下载按钮 UI

  2. SaveButton 默认 UI 进行完全透明化处理

2.1 模块导入

引入所需的功能包:

import { abilityAccessCtrl, common } from '@kit.AbilityKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import fs from '@ohos.file.fs';
import { promptAction } from '@kit.ArkUI';

2.2 UI 组件实现

使用系统提供的 SaveButton 组件:

SaveButton({ icon: SaveIconStyle.FULL_FILLED })
  .onClick(async (event: ClickEvent, result: SaveButtonOnClickResult) => {
    if (result === SaveButtonOnClickResult.SUCCESS) {
      this.saveFile(this.dataList.date)
    } else {
      this.downLoadImg = false
      promptAction.showToast({
        message: '设置权限失败!',
        duration: 2000
      });
    }
  })
  .fontColor('#FFFFFF')
  .iconSize(12)
  .width(24)
  .height(24)
  .backgroundColor('#4CD964')
  .padding(5)

2.3 核心功能封装

// 保存图片到相册
async saveFile(url: string) {
  this.downLoadImg = true;
  promptAction.showToast({
    message: '图片下载中',
    duration: 1000
  });
  
  try {
    const context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
    
    // 免去权限申请和权限请求等环节,获得临时授权,保存对应图片
    let helper = photoAccessHelper.getPhotoAccessHelper(context);
    
    try {
      // onClick触发后5秒内通过createAsset接口创建图片文件,5秒后createAsset权限收回
      let uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg');
      
      // 使用uri打开文件,可以持续写入内容,写入过程不受时间限制
      let file = await fs.open(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
      let content: Uint8Array | undefined = context?.resourceManager.getRawFileContentSync(`${url}.jpg`)
      await fs.write(file.fd, content?.buffer);
      await fs.close(file.fd);
      
      promptAction.showToast({
        message: '图片已保存至相册',
        duration: 2000
      });
      this.downLoadImg = false
    } catch (error) {
      this.downLoadImg = false
      promptAction.showToast({
        message: '图片保存失败',
        duration: 2000
      });
    }
  } catch (err) {
    this.downLoadImg = false
    console.error(`create asset failed with error: ${err.code}, ${err.message}`);
  }
}

2.4 实现说明

重要说明:第二种方法示例中并未直接使用网络图片,而是使用应用内置图片进行演示。图片需要存放在 rawfile 文件夹中,传递的参数实际上是图片的名称。

 

rawfile文件夹结构

rawfile文件夹结构


方案对比分析

特性 方法一(权限申请) 方法二(免权限)
权限要求 需要申请 ACL 权限 无需申请权限
上架难度 审核严格,通过率低 审核容易通过
UI 定制性 完全自定义 受限,需要技巧处理
用户体验 一键下载 需要用户手动授权
网络图片支持 完整支持 需要额外处理
开发复杂度 中等 简单

最佳实践建议

选择建议

  • 推荐方法二:对于大多数应用场景,建议使用免权限方案,避免上架审核问题

  • 特殊场景使用方法一:如果应用对用户体验要求极高,且有把握通过 ACL 权限审核

总结

本文详细介绍了 HarmonyOS Next 中实现网络图片保存到相册的两种方案:

  1. 权限申请方案:功能完整但上架审核严格,适合对用户体验要求极高的场景

  2. 免权限方案:开发简单、审核友好,是大多数应用的最佳选择

通过合理选择实现方案,开发者可以在保证功能完整性的同时,确保应用能够顺利上架。建议在实际开发中根据具体需求和项目特点选择合适的方案。

希望这篇文章能够帮助到正在开发 HarmonyOS Next 应用的朋友们!如果有任何问题,欢迎在评论区交流讨论。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐