HarmonyOS NEXT实战:保存网络图片

简介: 本教程介绍如何在HarmonyOS应用中实现网络图片的显示与保存功能,包含权限申请、图片下载及本地存储操作。

HarmonyOS Next实战##HarmonyOS应用开发##教育

目标:显示网络图片,通过点击保存按钮,下载网络图片,并保存到本地。

前提:需要申请权限ohos.permission.INTERNET。

实现思路:

  1. 通过Image显示图片。
  2. 通过SaveButton获取操作权限。
  3. 通过request.downloadFile下载图片。
  4. 通过fileIo拷贝图片到本地。

具体实现

import {
    http } from '@kit.NetworkKit';
import {
    image } from '@kit.ImageKit';
import {
    BusinessError, request } from '@kit.BasicServicesKit';
import {
    photoAccessHelper } from '@kit.MediaLibraryKit';
import {
    promptAction } from '@kit.ArkUI';
import {
    fileIo, fileIo as fs, fileUri } from '@kit.CoreFileKit';
import {
    common } from '@kit.AbilityKit';

@Entry
@Component
struct SaveImageDemoPage {
   
  imgUrl:string = 'https://pic1.zhimg.com/70/v2-88fd131a2081f6880036682526e40f4b_1440w.avis?source=172ae18b&biz_tag=Post'
  @State pixelMap: PixelMap | undefined = undefined;

  loadImageWithUrl(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 request 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) => {
   
              this.pixelMap = pixelMap;
              this.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(this);
                  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();
            })
          }
        }
      }
    )
  }

  build() {
   
    Row() {
   
      Column({
    space: 10 }) {
   
        Image(this.imgUrl)
          .width('80%')

        SaveButton().onClick(async (event: ClickEvent, result: SaveButtonOnClickResult) => {
   
          if (result === SaveButtonOnClickResult.SUCCESS) {
   
            this.loadImageWithUrl(this.imgUrl);
          } else {
   
            promptAction.showToast({
    message: '设置权限失败!' });
          }
        })
      }
      .width('100%')
    }
    .height('100%')
    .backgroundColor(0xF1F3F5)
  }

  /**
   * 保存图片
   * @param url
   */
  async saveImage(imageUrl: string) {
   
    try {
   
      let fileNameExtension = this.getFileNameExtension(imageUrl)
      fileNameExtension = (fileNameExtension == '' || fileNameExtension == 'jfif') ? 'jpg' : fileNameExtension
      let context = getContext(this) as common.UIAbilityContext;
      let dirPath = context.filesDir + '/article_images_preview'
      let fileName = new Date().getTime().toString()
      if (!fileIo.accessSync(dirPath)) {
   
        fileIo.mkdirSync(dirPath)
      }
      //下载网络图片,并保存到沙箱
      request.downloadFile(context, {
    url: imageUrl, filePath: `${
     dirPath}/${
     fileName}.${
     fileNameExtension}` })
        .then((downloadTask) => {
   
          downloadTask.on('complete', async () => {
   
            try {
   
              // 位于应用沙箱的图片uri
              let srcFileUri = `${
     dirPath}/${
     fileName}.${
     fileNameExtension}`
              let srcFileUris: Array<string> = [fileUri.getUriFromPath(srcFileUri)];
              let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
              // 指定待保存照片的创建选项,包括文件后缀和照片类型,标题和照片子类型可选
              let photoCreationConfigs: Array<photoAccessHelper.PhotoCreationConfig> = [
                {
   
                  title: fileName, // 可选
                  fileNameExtension: fileNameExtension,
                  photoType: photoAccessHelper.PhotoType.IMAGE,
                  subtype: photoAccessHelper.PhotoSubtype.DEFAULT, // 可选
                }
              ];
              // 基于弹窗授权的方式获取媒体库的目标uri
              let desFileUris: Array<string> =
                await phAccessHelper.showAssetsCreationDialog(srcFileUris, photoCreationConfigs);
              if (desFileUris.length > 0) {
   
                let desFile: fileIo.File = await fileIo.open(desFileUris[0], fileIo.OpenMode.WRITE_ONLY);
                let srcFile: fileIo.File = await fileIo.open(srcFileUri, fileIo.OpenMode.READ_ONLY);
                await fileIo.copyFile(srcFile.fd, desFile.fd);
                fileIo.closeSync(srcFile);
                fileIo.closeSync(desFile);
                promptAction.showToast({
    message: '已保存!' });
              } else {
   
                promptAction.showToast({
    message: '保存失败!' });
              }
              fs.rmdir(dirPath);
            } catch (error) {
   
              promptAction.showToast({
    message: '保存失败!' });
            }
          })
          downloadTask.on('fail', (err: number) => {
   
            //下载失败
            promptAction.showToast({
    message: '保存失败!' });
          })
        })
        .catch((err: BusinessError) => {
   
          promptAction.showToast({
    message: '保存失败!' });
        })
    } catch (err) {
   
      promptAction.showToast({
    message: '保存失败!' });
    }
  }

  /**
   * 获取文件拓展名
   * @param url
   * @returns 文件扩展名,没有则返回空字符串
   */
  getFileNameExtension(url: string): string {
   
    // 查找最后一个点(.)的位置
    const lastIndex = url.lastIndexOf('.');
    let fileNameExtension = ''
    if (lastIndex === -1 || lastIndex === url.length - 1) {
   
      fileNameExtension = ''
    } else {
   
      // 提取从最后一个点到字符串末尾的子字符串
      let subStr = url.substring(lastIndex);
      // 使用正则表达式匹配文件后缀名
      const extensionMatch = subStr.match(/\.([a-zA-Z0-9]+)(?:[\?#]|$)/);
      // 如果匹配到后缀名,则返回(去掉点号)
      fileNameExtension = extensionMatch ? extensionMatch[1].toLowerCase() : '';
    }
    return fileNameExtension
  }
}
目录
相关文章
|
4月前
|
容器
HarmonyOS NEXT仓颉开发语言实战案例:外卖App
仓颉语言实战分享,教你如何用仓颉开发外卖App界面。内容包括页面布局、导航栏自定义、搜索框实现、列表模块构建等,附完整代码示例。轻松掌握Scroll、List等组件使用技巧,提升HarmonyOS应用开发能力。
|
3月前
|
移动开发 前端开发 JavaScript
鸿蒙NEXT时代你所不知道的全平台跨端框架:CMP、Kuikly、Lynx、uni-app x等
本篇基于当前各大活跃的跨端框架的现状,对比当前它们的情况和未来的可能,帮助你在选择框架时更好理解它们的特点和差异。
312 0
|
4月前
|
安全 API 开发工具
【HarmonyOS NEXT】一键扫码功能
这些Kit为我们应用开发提升了极大地效率。很多简单的功能,如果不需要太深的定制化需求,直接调用kit提供的API就可以实现,在android或者ios上需要很多代码才能实现的功能效果。
119 0
HarmonyOS NEXT仓颉开发语言实战案例:电影App
周末好!本文分享使用仓颉语言重构ArkTS实现的电影App案例,对比两者在UI布局、组件写法及语法差异。内容包括页面结构、列表分组、分类切换与电影展示等。通过代码演示仓颉在HarmonyOS开发中的应用。##仓颉##ArkTS##HarmonyOS开发
|
4月前
|
容器
HarmonyOS NEXT仓颉开发语言实战案例:健身App
本期分享一个健身App首页的布局实现,顶部采用Stack容器实现重叠背景与偏移效果,列表部分使用List结合Scroll实现可滚动内容。代码结构清晰,适合学习HarmonyOS布局技巧。
HarmonyOS NEXT仓颉开发语言实战案例:小而美的旅行App
本文分享了一个旅行App首页的设计与实现,使用List容器搭配Row、Column布局完成个人信息、功能列表及推荐模块的排版,详细展示了HarmonyOS下的界面构建技巧。
|
18天前
|
存储 缓存 5G
鸿蒙 HarmonyOS NEXT端云一体化开发-云存储篇
本文介绍用户登录后获取昵称、头像的方法,包括通过云端API和AppStorage两种方式,并实现上传头像至云存储及更新用户信息。同时解决图片缓存问题,添加上传进度提示,支持自动登录判断,提升用户体验。
90 0
|
18天前
|
存储 负载均衡 数据库
鸿蒙 HarmonyOS NEXT端云一体化开发-云函数篇
本文介绍基于华为AGC的端云一体化开发流程,涵盖项目创建、云函数开通、应用配置及DevEco集成。重点讲解云函数的编写、部署、调用与传参,并涉及环境变量设置、负载均衡、重试机制与熔断策略等高阶特性,助力开发者高效构建稳定云端服务。
178 0
鸿蒙 HarmonyOS NEXT端云一体化开发-云函数篇
|
18天前
|
存储 JSON 数据建模
鸿蒙 HarmonyOS NEXT端云一体化开发-云数据库篇
云数据库采用存储区、对象类型、对象三级结构,支持灵活的数据建模与权限管理,可通过AGC平台或本地项目初始化,实现数据的增删改查及端侧高效调用。
50 0
|
18天前
|
存储 开发者 容器
鸿蒙 HarmonyOS NEXT星河版APP应用开发-ArkTS面向对象及组件化UI开发使用实例
本文介绍了ArkTS语言中的Class类、泛型、接口、模块化、自定义组件及状态管理等核心概念,并结合代码示例讲解了对象属性、构造方法、继承、静态成员、访问修饰符等内容,同时涵盖了路由管理、生命周期和Stage模型等应用开发关键知识点。
150 0
鸿蒙 HarmonyOS NEXT星河版APP应用开发-ArkTS面向对象及组件化UI开发使用实例

热门文章

最新文章