| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317 |
- 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<string, string> = 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<void> {
- 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<string | null> 本地文件路径,失败返回 null
- */
- private static async downloadImage(url: string): Promise<string | null> {
- 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<void> {
- 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<void> {
- const promises: Promise<void>[] = [];
- 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<ImageSize | null> 图片尺寸
- */
- static async getImageSize(url: string): Promise<ImageSize | null> {
- try {
- // HarmonyOS 需要使用 image 模块获取图片尺寸
- // 这里返回 null,提示需要实现
- LogHelper.w(TAG, "getImageSize 需要使用 @kit.CoreFileKit 实现");
- return null;
- } catch (e) {
- LogHelper.e(TAG, `获取图片尺寸失败: ${url}`, e as Error);
- return null;
- }
- }
- }
|