harmony图片授权下载功能

Harmony开发-远程图片/本地资源下载

有一些封装的方法没搬上来,用到的时候删掉即可,检查扩展名也很简单自己写一下或者写死呗

// 图片下载工具类 - 专注于弹窗授权保存网络图片
import { promptAction } from '@kit.ArkUI'
import { http } from '@kit.NetworkKit'
import { fileIo, fileUri } from '@kit.CoreFileKit'
import { photoAccessHelper } from '@kit.MediaLibraryKit'
import { common } from '@kit.AbilityKit'
import fs from '@ohos.file.fs'
import { Logger } from './Logger'
import { checkExtension } from '../utils/tools'

/**
 * 图片下载工具类 - 使用弹窗授权方式保存网络图片/本地资源$r('app.media.xx')到相册
 */
export class ImageDownloadUtil {
  /**
   * 使用弹窗授权保存网络图片到相册(完全按照官方示例)ß
   * @param imageUrl 网络图片URL
   * @param fileName 保存的文件名(可选)
   */
  static async downloadImage(imageUrl: string | Resource | (string | Resource)[], fileName?: string | string[]): Promise<boolean> {
    try {
      // 判断传入的是单张还是多张[数组]
      const isMultiple = Array.isArray(imageUrl)
      const resources = isMultiple ? imageUrl : [imageUrl]
      const fileNames = Array.isArray(fileName) ? fileName : (fileName ? [fileName] : [])

      Logger.info(`开始使用弹窗授权方式保存${isMultiple ? '多张' : '单张'}图片:`, resources)

      const context = getContext() as common.UIAbilityContext
      const helper = photoAccessHelper.getPhotoAccessHelper(context)
      const resMgr = context.resourceManager

      // 1. 下载所有图片到本地 sandbox 沙窗地址
      const localPaths: string[] = []
      const photoConfigs: photoAccessHelper.PhotoCreationConfig[] = []
      Logger.info(`resources--${JSON.stringify(resources)}`)
      for (let i = 0; i < resources.length; i++) {
        const resource = resources[i]
        Logger.info(`resource--${JSON.stringify(resource)}`)
        
        let extension = 'png' // 本地资源默认png
        let finalFileName: string
        let localPath: string
        Logger.info(`typeof--${JSON.stringify(typeof resource)}`)

        // 判断是否为本地资源
        if (typeof resource === 'object' && resource.id) {
          // 本地资源处理
          console.log('处理本地资源')
          finalFileName = fileNames[i] || `pointsMall_resource_${Date.now()}_${i}.${extension}`
          localPath = `${context.filesDir}/${finalFileName}`
          
          // 获取本地资源内容
          const mediaContent = await resMgr.getMediaContent(resource)
          console.log('本地资源数据长度:', Object.keys(mediaContent).length)
          
          // 将类数组对象转换为 Uint8Array
          const dataLength = Object.keys(mediaContent).length
          const uint8Array = new Uint8Array(dataLength)
          for (let j = 0; j < dataLength; j++) {
            uint8Array[j] = mediaContent[j]
          }
          
          // 保存到本地文件
          const file = await fileIo.open(localPath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY)
          await fileIo.write(file.fd, uint8Array.buffer)
          await fileIo.close(file.fd)
          
          console.log(`第${i + 1}个本地资源保存成功`)
        } else {
          // 网络资源处理
          const url = resource as string
          extension = checkExtension(url)
          finalFileName = fileNames[i] || `pointsMall_${Date.now()}_${i}.${extension}`
          localPath = `${context.filesDir}/${finalFileName}`
          
          console.log(`准备下载第${i + 1}张图片到: ${localPath}`)
          
          // 下载图片
          const httpRequest = http.createHttp()
          try {
            const response = await httpRequest.request(url, {
              method: http.RequestMethod.GET,
              expectDataType: http.HttpDataType.ARRAY_BUFFER,
            })
            if (response.responseCode === 200 && response.result) {
              console.log(`第${i + 1}张图片下载成功,开始保存到本地`)
              
              // 保存到本地文件
              const file = await fileIo.open(localPath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY)
              await fileIo.write(file.fd, response.result as ArrayBuffer)
              await fileIo.close(file.fd)
            } else {
              throw new Error(`第${i + 1}张图片下载失败`)
            }
          } finally {
            httpRequest.destroy()
          }
        }

        // 验证文件
        const stat = await fileIo.stat(localPath)
        console.log(`第${i + 1}张图片文件验证成功,大小: ${stat.size} 字节`)

        localPaths.push(localPath)

        // 确定标题:优先用户传入的文件名,其次图片本身的名字,最后默认名称
        let title = `pointsMall_${i + 1}` // 默认标题

        if (fileNames[i]) {
          // 优先使用用户传入的文件名(去掉扩展名)
          title = fileNames[i].split('.')[0]
        } else if (typeof resource === 'string') {
          // 其次使用图片URL中的文件名
          const urlParts = resource.split('/')
          const lastPart = urlParts[urlParts.length - 1]
          if (lastPart && lastPart.includes('.')) {
            title = lastPart.split('.')[0]
          } else if (lastPart) {
            // 如果没有扩展名,直接使用最后一部分(去掉查询参数)
            title = lastPart.split('?')[0]
          }
        }

        photoConfigs.push({
          title: title,
          fileNameExtension: extension,
          photoType: photoAccessHelper.PhotoType.IMAGE
        })
      }
      if (localPaths.length === 0) {
        throw new Error('没有成功下载任何图片')
      }
      Logger.info(`成功下载${localPaths.length}张图片,准备调用弹窗授权`)

      // 沙窗文件需要转成file, 弹出的授权窗口才会有预览图
      const localUri: string[] = []
      for (let i = 0; i < localPaths.length; i++) {
        localUri.push(fileUri.getUriFromPath(localPaths[i]))
      }
      const desFileUris = await helper.showAssetsCreationDialog(localUri, photoConfigs)
      Logger.info(`弹窗授权成功,返回URI数量: ${JSON.stringify(desFileUris.length)}`)
      if (desFileUris.length > 0) {
        // 逐个保存图片
        for (let i = 0; i < desFileUris.length && i < localUri.length; i++) {
          try {
            const desFile = fs.openSync(desFileUris[i], fs.OpenMode.WRITE_ONLY)
            const srcFile = fs.openSync(localUri[i], fs.OpenMode.READ_ONLY)
            fs.copyFileSync(srcFile.fd, desFile.fd)
            fs.closeSync(srcFile)
            fs.closeSync(desFile)
            console.log(`第${i + 1}张图片保存成功`)
          } catch (copyError) {
            console.error(`第${i + 1}张图片保存失败:`, copyError)
          }
        }
        promptAction.openToast({
          message: `${desFileUris.length}张图片保存成功`,
          duration: 2000
        })
        // 清理所有临时文件
        for (const localPath of localPaths) {
          try {
            await fileIo.unlink(localPath)
          } catch (unlinkError) {
            Logger.warn(`清理临时文件失败:, ${JSON.stringify(unlinkError)}`)
          }
        }
        return true
      } else {
        promptAction.openToast({
          message: '用户禁止授权,保存动作取消',
          duration: 2000
        })
        // 清理所有临时文件
        for (const localPath of localPaths) {
          try {
            await fileIo.unlink(localPath)
          } catch (unlinkError) {
            Logger.warn(`清理临时文件失败:, ${JSON.stringify(unlinkError)}`)
          }
        }
        throw new Error('用户禁止授权')
      }
    } catch (err) {
      Logger.info(err)
      Logger.error(`下载失败-${err.message || err }`)
      return false
    }
  }
}

使用

const imgUrl = [
                $r('app.media.app_icon'),
                'https://statics.southbigdata.cn/pointsMall/public/empty.png',
                'https://gd-hbimg.huaban.com/33c0c9686f4e4b432578e6b12f6470c28c3a21c6951f-4BjE2Z_fw1200webp',
                'https://gd-hbimg.huaban.com/3a3f22fee8576297997d696c2347c675aeaadd3255c2e-RD4txB_fw1200webp',
                'https://gd-hbimg.huaban.com/b5c98509d0d7f96b430d9bb0191cfaa665a7757b1f074-FBTWkj_fw1200webp'
              ]
              // const imgUrl = 'https://gd-hbimg.huaban.com/ea6e2a4ffa213d3ee56c2d0488374a0f53bfe2386cfd0-PPMEPO_fw1200webp'
              const success = await ImageDownloadUtil.downloadImage(imgUrl, 'encapsulated_image.jpg')
              if (success) {
                console.log('✅ 封装方法保存成功!')
              } else {
                console.log('❌ 封装方法保存失败!')
              }

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值