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('❌ 封装方法保存失败!')
}

1036

被折叠的 条评论
为什么被折叠?



