import { LogHelper } from '../log/LogHelper'; import { Context } from '@kit.AbilityKit'; import { fileIo } from '@kit.CoreFileKit'; import { http } from '@kit.NetworkKit'; const TAG = "ImageLoader"; /** * ImageView 接口(用于类型定义) */ export interface ImageView { src?: string; } /** * 图片尺寸接口 */ export interface ImageSize { width: number; height: number; } /** * 图片加载器 * * 统一封装图片加载功能,提供便捷的图片加载方式 * * 注意:HarmonyOS 的图片加载通常使用 Image 组件的 src 属性直接加载 * 这个类主要用于提供统一的 API 接口和缓存管理 * * 使用方式: * ```typescript * // 加载网络图片 * ImageLoader.load(imageView, "https://example.com/image.jpg") * * // 加载本地图片 * ImageLoader.load(imageView, "/data/storage/image.jpg") * * // 加载并缓存 * ImageLoader.loadWithCache(imageView, "https://example.com/image.jpg") * ``` */ export class ImageLoader { private static imageCache: Map = new Map(); // URL -> 本地路径 private static context: Context | null = null; /** * 初始化图片加载器(设置 Context 用于文件存储) */ static init(context: Context): void { ImageLoader.context = context; LogHelper.d(TAG, "ImageLoader 初始化完成"); } /** * 加载图片 * * @param imageView Image 组件引用(在 HarmonyOS 中直接使用 src 属性) * @param url 图片 URL(网络或本地路径) * @param placeholder 占位图(可选) * @param errorImage 错误占位图(可选) */ static load( imageView: ImageView, url: string, placeholder?: string, errorImage?: string ): void { try { // HarmonyOS 中图片加载直接使用 Image 组件的 src 属性 // 这里提供统一的 API 接口 if (imageView && typeof imageView.src !== 'undefined') { imageView.src = url; } else { LogHelper.w(TAG, "ImageLoader.load: imageView 不支持 src 属性"); } } catch (e) { LogHelper.e(TAG, `加载图片失败: ${url}`, e as Error); if (errorImage && imageView) { imageView.src = errorImage; } } } /** * 加载图片(带缓存) * * 如果是网络图片,会下载到本地并缓存;如果是本地图片,直接加载 * * @param imageView Image 组件引用 * @param url 图片 URL(网络或本地路径) * @param placeholder 占位图(可选) * @param errorImage 错误占位图(可选) */ static async loadWithCache( imageView: ImageView, url: string, placeholder?: string, errorImage?: string ): Promise { try { // 如果是本地路径,直接加载 if (!url.startsWith('http://') && !url.startsWith('https://')) { ImageLoader.load(imageView, url, placeholder, errorImage); return; } // 检查缓存 if (ImageLoader.imageCache.has(url)) { const cachedPath = ImageLoader.imageCache.get(url); if (cachedPath) { // 检查缓存文件是否存在 try { const stat = await fileIo.stat(cachedPath); if (stat.isFile()) { ImageLoader.load(imageView, cachedPath, placeholder, errorImage); return; } else { // 缓存文件不存在,移除缓存记录 ImageLoader.imageCache.delete(url); } } catch (e) { // 缓存文件不存在,移除缓存记录 ImageLoader.imageCache.delete(url); } } } // 显示占位图 if (placeholder && imageView) { imageView.src = placeholder; } // 下载图片 const localPath = await ImageLoader.downloadImage(url); if (localPath) { // 缓存成功,加载本地图片 ImageLoader.imageCache.set(url, localPath); ImageLoader.load(imageView, localPath, placeholder, errorImage); } else { // 下载失败,加载原 URL 或错误图片 if (errorImage && imageView) { imageView.src = errorImage; } else { ImageLoader.load(imageView, url, placeholder, errorImage); } } } catch (e) { LogHelper.e(TAG, `加载图片失败: ${url}`, e as Error); if (errorImage && imageView) { imageView.src = errorImage; } } } /** * 下载图片到本地 * * @param url 图片 URL * @return Promise 本地文件路径,失败返回 null */ private static async downloadImage(url: string): Promise { if (!ImageLoader.context) { LogHelper.w(TAG, "ImageLoader 未初始化,无法下载图片"); return null; } try { // 生成缓存文件名(使用 URL 的 MD5 或简单哈希) const urlHash = ImageLoader.hashUrl(url); const cacheDir = `${ImageLoader.context.cacheDir}/image_cache`; const fileName = `${urlHash}.jpg`; const filePath = `${cacheDir}/${fileName}`; // 确保缓存目录存在 try { await fileIo.mkdir(cacheDir, true); } catch (e) { // 目录可能已存在 } // 下载图片(使用 http 模块直接下载) // 注意:NetworkManager 的 get 方法返回的是 JSON 解析后的数据,不适合下载二进制文件 // 这里需要使用 http 模块直接下载 const httpRequest = http.createHttp(); try { const response: http.HttpResponse = await httpRequest.request(url, { method: http.RequestMethod.GET, header: { 'Accept': 'image/*' }, readTimeout: 60000, connectTimeout: 60000 }); if (response.responseCode >= 200 && response.responseCode < 300) { // response.result 可能是 ArrayBuffer 或 string if (response.result instanceof ArrayBuffer) { // 保存到本地 const file = await fileIo.open(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY | fileIo.OpenMode.TRUNC); await fileIo.write(file.fd, response.result); await fileIo.close(file.fd); LogHelper.d(TAG, `图片下载成功: ${url} -> ${filePath}`); return filePath; } else if (typeof response.result === 'string') { // 如果是字符串,转换为 Uint8Array 再保存 const uint8Array = new Uint8Array(response.result.length); for (let i = 0; i < response.result.length; i++) { uint8Array[i] = response.result.charCodeAt(i); } const file = await fileIo.open(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY | fileIo.OpenMode.TRUNC); await fileIo.write(file.fd, uint8Array.buffer); await fileIo.close(file.fd); LogHelper.d(TAG, `图片下载成功: ${url} -> ${filePath}`); return filePath; } else { LogHelper.w(TAG, `下载图片失败: 响应格式不正确`); return null; } } else { LogHelper.w(TAG, `下载图片失败: HTTP ${response.responseCode}`); return null; } } finally { httpRequest.destroy(); } } catch (e) { LogHelper.e(TAG, `下载图片失败: ${url}`, e as Error); return null; } } /** * 简单的 URL 哈希函数(用于生成缓存文件名) */ private static hashUrl(url: string): string { let hash = 0; for (let i = 0; i < url.length; i++) { const char = url.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32bit integer } return Math.abs(hash).toString(16); } /** * 清除图片缓存 */ static async clearCache(): Promise { if (!ImageLoader.context) { LogHelper.w(TAG, "ImageLoader 未初始化,无法清除缓存"); return; } try { const cacheDir = `${ImageLoader.context.cacheDir}/image_cache`; // 删除缓存目录中的所有文件 const files = await fileIo.listFile(cacheDir); for (let i = 0; i < files.length; i++) { const filePath = `${cacheDir}/${files[i]}`; try { await fileIo.unlink(filePath); } catch (e) { LogHelper.w(TAG, `删除缓存文件失败: ${filePath}`); } } ImageLoader.imageCache.clear(); LogHelper.d(TAG, "图片缓存已清除"); } catch (e) { LogHelper.e(TAG, "清除图片缓存失败", e as Error); } } /** * 预加载图片 * * @param urls 图片 URL 数组 */ static async preload(urls: string[]): Promise { const promises: Promise[] = []; for (let i = 0; i < urls.length; i++) { const url = urls[i]; // 如果是网络图片,下载到缓存 if (url.startsWith('http://') || url.startsWith('https://')) { promises.push(ImageLoader.downloadImage(url).then(localPath => { if (localPath) { ImageLoader.imageCache.set(url, localPath); LogHelper.d(TAG, `预加载图片成功: ${url}`); } })); } } await Promise.all(promises); LogHelper.d(TAG, `预加载完成,共 ${urls.length} 张图片`); } /** * 获取图片尺寸 * * @param url 图片 URL * @return Promise 图片尺寸 */ static async getImageSize(url: string): Promise { try { // HarmonyOS 需要使用 image 模块获取图片尺寸 // 这里返回 null,提示需要实现 LogHelper.w(TAG, "getImageSize 需要使用 @kit.CoreFileKit 实现"); return null; } catch (e) { LogHelper.e(TAG, `获取图片尺寸失败: ${url}`, e as Error); return null; } } }