ImageKnife/library/src/main/ets/ImageKnife.ets

453 lines
14 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ImageKnifeRequest } from './model/ImageKnifeRequest';
import { CacheStrategy, ImageKnifeData, ImageKnifeRequestSource } from './model/ImageKnifeData';
import { MemoryLruCache } from './cache/MemoryLruCache';
import { IMemoryCache } from './cache/IMemoryCache'
import { FileCache } from './cache/FileCache';
import { ImageKnifeDispatcher } from './ImageKnifeDispatcher';
import { IEngineKey } from './key/IEngineKey';
import { HeaderOptions, ImageKnifeOption } from './model/ImageKnifeOption';
import { FileTypeUtil } from './utils/FileTypeUtil';
import { util } from '@kit.ArkTS';
import { image } from '@kit.ImageKit';
import { common } from '@kit.AbilityKit';
import { LogUtil } from './utils/LogUtil';
export class ImageKnife {
private static instance: ImageKnife;
// 内存缓存
private memoryCache: IMemoryCache = new MemoryLruCache(256, 128 * 1024 * 1024);
// 文件缓存
private fileCache?: FileCache
private dispatcher: ImageKnifeDispatcher = new ImageKnifeDispatcher()
// 配置全局是否在子线程加载图片请求
private _isRequestInSubThread: boolean = true;
//定义全局网络请求header map
headerMap: Map<string, Object> = new Map<string, Object>();
customGetImage: ((context: Context, src: string | PixelMap | Resource) => Promise<ArrayBuffer | undefined>) | undefined = undefined
public static getInstance(): ImageKnife {
if (!ImageKnife.instance) {
ImageKnife.instance = new ImageKnife();
}
return ImageKnife.instance;
}
private constructor() {
}
public set isRequestInSubThread(value: boolean) {
this._isRequestInSubThread = value;
}
public get isRequestInSubThread(): boolean {
return this._isRequestInSubThread;
}
/**
* 初始化文件缓存个数,大小,以及路径
* @param context 上下文
* @param size 缓存数量
* @param memory 内存大小
* @param path 文件目录
*/
async initFileCache(context: Context, size: number = 256, memory: number = 256 * 1024 * 1024,path?: string) {
this.fileCache = new FileCache(context, size, memory)
if ( path != undefined ) {
await this.fileCache.initFileCache(path)
} else {
await this.fileCache.initFileCache()
}
}
/**
* 判断文件缓存是否已完成初始化
* @returns 是否初始化
*/
public isFileCacheInit(): boolean {
return this.fileCache === undefined ? false : this.fileCache.isFileCacheInit()
}
/**
* 全局添加单个请求头header
* @param key 请求头属性
* @param value 请求头值
*/
addHeader(key: string, value: Object) {
this.headerMap.set(key, value)
}
/**
* 全局设置请求头header
* @param options 请求头数组
*/
serHeaderOptions(options: Array<HeaderOptions>) {
options.forEach((value) => {
this.headerMap.set(value.key, value.value)
})
}
/**
* 删除单个请求头header
* @param key 请求头属性
*/
deleteHeader(key: string) {
this.headerMap.delete(key)
}
/**
* 设置自定义的内存缓存
* @param newMemoryCache 自定义内存缓存
*/
initMemoryCache(newMemoryCache: IMemoryCache): void {
this.memoryCache = newMemoryCache
}
/**
* 清除所有内存缓存
*/
removeAllMemoryCache(): void {
this.memoryCache.removeAll()
}
/**
* 清除指定内存缓存
* @param url 待清除的url路径或ImageKnifeOption
*/
removeMemoryCache(url: string | ImageKnifeOption) {
let imageKnifeOption = new ImageKnifeOption();
if (typeof url == 'string') {
imageKnifeOption.loadSrc = url;
} else {
imageKnifeOption = url;
}
let key = this.getEngineKeyImpl().generateMemoryKey(imageKnifeOption.loadSrc, ImageKnifeRequestSource.SRC, imageKnifeOption);
this.memoryCache.remove(key);
}
/**
* 预加载图片到文件缓存
* @param loadSrc 图片地址url
* @returns 返回文件缓存路径
*/
preLoadCache(loadSrc: string | ImageKnifeOption): Promise<string> {
return new Promise((resolve, reject) => {
let imageKnifeOption = new ImageKnifeOption()
if (typeof loadSrc == "string") {
imageKnifeOption.loadSrc = loadSrc
} else {
imageKnifeOption = loadSrc;
}
LogUtil.log("ImageKnife_DataTime_preLoadCache-imageKnifeOption:"+loadSrc)
let fileKey = this.getEngineKeyImpl().generateFileKey(imageKnifeOption.loadSrc, imageKnifeOption.signature)
let cachePath = ImageKnife.getInstance().getFileCache().getFileToPath(fileKey)
if (cachePath == null || cachePath == "" || cachePath == undefined) {
imageKnifeOption.onLoadListener = {
onLoadSuccess(){
resolve(ImageKnife.getInstance().getFileCache().getFileToPath(fileKey))
},
onLoadFailed(err) {
reject(err)
}
}
let request = new ImageKnifeRequest(
imageKnifeOption,
imageKnifeOption.context !== undefined ? imageKnifeOption.context : getContext(this) as common.UIAbilityContext,
0,
0,
0,
{
showPixelMap(version: number, pixelMap: PixelMap | string) {
}
}
)
this.execute(request)
} else {
resolve(cachePath)
}
})
}
/**
* 从内存或文件缓存中获取图片数据
* @param url 图片地址url
* @param cacheType 缓存策略
* @returns 图片数据
* @param signature key自定义信息
*/
getCacheImage(loadSrc: string,
cacheType: CacheStrategy = CacheStrategy.Default, signature?: string): Promise<ImageKnifeData | undefined> {
let option: ImageKnifeOption = new ImageKnifeOption({
loadSrc: loadSrc,
signature:signature
})
let engineKeyImpl: IEngineKey = this.getEngineKeyImpl();
return new Promise((resolve, reject) => {
if (cacheType == CacheStrategy.Memory) {
resolve(this.readMemoryCache(loadSrc, option, engineKeyImpl))
} else if (cacheType == CacheStrategy.File) {
this.readFileCache(loadSrc, engineKeyImpl, resolve)
} else {
let data = this.readMemoryCache(loadSrc, option, engineKeyImpl)
data == undefined ? this.readFileCache(loadSrc, engineKeyImpl, resolve) : resolve(data)
}
})
}
/**
* 预加载缓存用于外部已获取pixelmap需要加入imageknife缓存的场景
* @param url 图片地址url
* @param pixelMap 图片
* @param cacheType 缓存策略
* @param signature key自定义信息
*/
putCacheImage(url: string, pixelMap: PixelMap, cacheType: CacheStrategy = CacheStrategy.Default, signature?: string) {
let memoryKey = this.getEngineKeyImpl()
.generateMemoryKey(url, ImageKnifeRequestSource.SRC, new ImageKnifeOption({ loadSrc: url, signature: signature }));
let fileKey = this.getEngineKeyImpl().generateFileKey(url, signature);
let imageKnifeData: ImageKnifeData = { source: pixelMap, imageWidth: 0, imageHeight: 0 };
switch (cacheType) {
case CacheStrategy.Default:
this.saveMemoryCache(memoryKey, imageKnifeData);
this.saveFileCache(fileKey, this.pixelMapToArrayBuffer(pixelMap));
break;
case CacheStrategy.File:
this.saveFileCache(fileKey, this.pixelMapToArrayBuffer(pixelMap));
break
case CacheStrategy.Memory:
this.saveMemoryCache(memoryKey, imageKnifeData);
break
}
}
/**
* 清除所有文件缓存
* @returns
*/
async removeAllFileCache(): Promise<void> {
if (this.fileCache !== undefined) {
await this.fileCache.removeAll()
}
}
/*
* 清除指定文件缓存
* */
removeFileCache(url: string | ImageKnifeOption) {
let imageKnifeOption: ImageKnifeOption;
if (url instanceof ImageKnifeOption) {
imageKnifeOption = url;
} else {
imageKnifeOption = new ImageKnifeOption({
loadSrc: url
});
}
let key = this.getEngineKeyImpl().generateFileKey(imageKnifeOption.loadSrc, imageKnifeOption.signature);
if (this.fileCache !== undefined) {
this.fileCache.remove(key);
}
}
/**
* 设置taskpool默认并发数量
* @param concurrency 默认并发数量默认为8
*/
setMaxRequests(concurrency: number): void {
this.dispatcher.setMaxRequests(concurrency)
}
getFileCacheByFile(context: Context, key: string): ArrayBuffer | undefined {
if (this.fileCache !== undefined) {
return FileCache.getFileCacheByFile(context, key)
}
return undefined
}
loadFromMemoryCache(key: string): ImageKnifeData | undefined {
if (key !== "") {
return this.memoryCache.get(key)
}
return undefined
}
saveMemoryCache(key: string, data: ImageKnifeData): void {
if (key !== "") {
this.memoryCache.put(key, data)
}
}
loadFromFileCache(key: string): ArrayBuffer | undefined {
return this.fileCache?.get(key)
}
saveFileCache(key: string, data: ArrayBuffer): void {
this.fileCache?.put(key, data)
}
getFileCache(): FileCache {
return this.fileCache as FileCache
}
/**
* get cache upper limit
* @param cacheType
* @returns
*/
getCacheUpperLimit(cacheType?: CacheStrategy): number | undefined {
if (cacheType == undefined || cacheType == CacheStrategy.Default) {
cacheType = CacheStrategy.Memory;
}
if (cacheType == CacheStrategy.Memory) {
return (this.memoryCache as MemoryLruCache).maxMemory;
} else {
if (this.isFileCacheInit()) {
return this.fileCache?.maxMemory;
} else {
throw new Error("the disk cache not init");
}
}
}
/**
* gets the number of images currently cached
* @param cacheType
* @returns
*/
getCurrentPicturesNum(cacheType: CacheStrategy): number | undefined {
if (cacheType == undefined || cacheType == CacheStrategy.Default) {
cacheType = CacheStrategy.Memory;
}
if (cacheType == CacheStrategy.Memory) {
return (this.memoryCache as MemoryLruCache).size();
} else {
if (this.isFileCacheInit()) {
return this.fileCache?.size();
} else {
throw new Error("the disk cache not init");
}
}
}
/**
* gets the current cache size
* @param cacheType
* @returns
*/
getCurrentCacheSize(cacheType: CacheStrategy): number | undefined {
if (cacheType == undefined || cacheType == CacheStrategy.Default) {
cacheType = CacheStrategy.Memory;
}
if (cacheType == CacheStrategy.Memory) {
return (this.memoryCache as MemoryLruCache).currentMemory;
} else {
if (this.isFileCacheInit()) {
return this.fileCache?.currentMemory;
} else {
throw new Error("the disk cache not init");
}
}
}
private pixelMapToArrayBuffer(pixelMap: PixelMap): ArrayBuffer {
let imageInfo = pixelMap.getImageInfoSync();
let readBuffer: ArrayBuffer = new ArrayBuffer(imageInfo.size.height * imageInfo.size.width * 4);
pixelMap.readPixelsToBufferSync(readBuffer);
return readBuffer
}
private readMemoryCache(loadSrc: string, option: ImageKnifeOption, engineKey: IEngineKey): ImageKnifeData | undefined {
let memoryKey = engineKey.generateMemoryKey(loadSrc, ImageKnifeRequestSource.SRC, option)
return ImageKnife.getInstance()
.loadFromMemoryCache(memoryKey)
}
private readFileCache(loadSrc: string, engineKey: IEngineKey, onComplete: (data: ImageKnifeData | undefined) => void) {
let keys = engineKey.generateFileKey(loadSrc)
let buffer = ImageKnife.getInstance().loadFromFileCache(keys)
if (buffer != undefined) {
let fileTypeUtil = new FileTypeUtil();
let typeValue = fileTypeUtil.getFileType(buffer);
if (typeValue === 'gif' || typeValue === 'webp') {
let base64Help = new util.Base64Helper()
let base64str = "data:image/" + typeValue + ";base64," + base64Help.encodeToStringSync(new Uint8Array(buffer))
onComplete({
source: base64str,
imageWidth: 0,
imageHeight: 0
})
}
let imageSource: image.ImageSource = image.createImageSource(buffer);
let decodingOptions: image.DecodingOptions = {
editable: true,
}
imageSource.createPixelMap(decodingOptions)
.then((pixelmap: PixelMap) => {
onComplete({
source: pixelmap,
imageWidth: 0,
imageHeight: 0
})
imageSource.release()
})
} else {
onComplete(undefined)
}
}
saveWithoutWriteFile(key: string, bufferSize: number): void {
this.fileCache?.putWithoutWriteFile(key, bufferSize)
}
saveFileCacheOnlyFile(context: Context, key: string, value: ArrayBuffer): boolean {
if (this.fileCache !== undefined) {
return FileCache.saveFileCacheOnlyFile(context, key, value)
}
return false
}
async execute(request: ImageKnifeRequest,isAnimator?: boolean): Promise<void> {
LogUtil.log("ImageKnife_DataTime_execute.start:"+request.imageKnifeOption.loadSrc)
if (this.headerMap.size > 0) {
request.addHeaderMap(this.headerMap)
}
this.dispatcher.enqueue(request,isAnimator)
LogUtil.log("ImageKnife_DataTime_execute.end:"+request.imageKnifeOption.loadSrc)
}
setEngineKeyImpl(impl: IEngineKey): void {
this.dispatcher.setEngineKeyImpl(impl);
}
getEngineKeyImpl(): IEngineKey {
return this.dispatcher.getEngineKeyImpl();
}
/**
* 全局设置自定义下载
* @param customGetImage 自定义请求函数
*/
setCustomGetImage(customGetImage?: (context: Context, src: string | PixelMap | Resource) => Promise<ArrayBuffer | undefined>) {
this.customGetImage = customGetImage
}
getCustomGetImage(): undefined | ((context: Context, src: string | PixelMap | Resource) => Promise<ArrayBuffer | undefined>){
return this.customGetImage
}
}