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
  }
}
目录
相关文章
|
1月前
|
监控 JavaScript 编译器
从“天书”到源码:HarmonyOS NEXT 崩溃堆栈解析实战指南
本文详解如何利用 hiAppEvent 监控并获取 sourcemap、debug so 等核心产物,剖析了 hstack 工具如何将混淆的 Native 与 ArkTS 堆栈还原为源码,助力开发者掌握异常分析方法,提升应用稳定性。
373 38
|
2月前
|
开发者 容器
鸿蒙应用开发从入门到实战(十四):ArkUI组件Column&Row&线性布局
ArkUI提供了丰富的系统组件,用于制作鸿蒙原生应用APP的UI,本文主要讲解Column和Row组件的使用以及线性布局的方法。
271 12
|
1月前
|
机器学习/深度学习 PyTorch TensorFlow
卷积神经网络深度解析:从基础原理到实战应用的完整指南
蒋星熠Jaxonic,深度学习探索者。深耕TensorFlow与PyTorch,分享框架对比、性能优化与实战经验,助力技术进阶。
|
1月前
|
监控 Linux 测试技术
C++零拷贝网络编程实战:从理论到生产环境的性能优化之路
🌟 蒋星熠Jaxonic,技术宇宙中的星际旅人。深耕C++与零拷贝网络编程,从sendfile到DPDK,实战优化服务器性能,毫秒级响应、CPU降60%。分享架构思维,共探代码星辰大海!
|
2月前
|
API 数据处理
鸿蒙应用开发从入门到实战(十三):ArkUI组件Slider&Progress
ArkUI提供了丰富的系统组件,用于制作鸿蒙原生应用APP的UI,本文主要讲解滑块Slider和进度条Progress组件的使用。
165 1
|
1月前
|
移动开发 前端开发 Android开发
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
237 12
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
1月前
|
移动开发 JavaScript 应用服务中间件
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
203 5
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
1月前
|
移动开发 Rust JavaScript
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
516 4
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
2月前
|
数据安全/隐私保护 开发者
鸿蒙应用开发从入门到实战(十一):ArkUI组件Text&TextInput
ArkUI提供了丰富的系统组件,用于制作鸿蒙原生应用APP的UI,本文主要讲解文本组件Text和TextInput的使用。
270 3
|
1月前
|
移动开发 Android开发
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
121 0

热门文章

最新文章

下一篇
oss云网关配置