更新说明
1、新增内存缓存策略 2、新增内存缓存图片张数缓存设置接口 Signed-off-by: 明月清风 <qiufeihu1@h-partners.com>
This commit is contained in:
parent
89fcb3a5d3
commit
3bfa3e71a7
|
@ -1,6 +1,7 @@
|
|||
## 2.1.1-rc.5
|
||||
- .jpg .png .gif解码功能使用taskpool实现
|
||||
- 修复了内存缓存张数设置为1时gif图片消失的问题
|
||||
- 新增内存缓存策略,新增缓存张数,缓存大小设置接口
|
||||
|
||||
|
||||
|
||||
|
|
33
README.md
33
README.md
|
@ -386,6 +386,39 @@ request.skipMemoryCache(true)
|
|||
|
||||
<img src="screenshot/gif4.gif" width="50%"/>
|
||||
|
||||
### setLruCacheSize
|
||||
|
||||
setLruCacheSize(size: number,memory:number): void
|
||||
|
||||
设置图片文件缓存的大小上限,size单位为张数,memory单位为字节,提升再次加载同源图片的加载速度,特别是对网络图源会有较明显提升。
|
||||
如果不设置则默认为100张,100MB。缓存采用内置的LRU策略。
|
||||
size为0则代表不限制缓存张数,memory为0则代表不限制缓存大小。
|
||||
建议根据应用实际需求,设置合理缓存上限,数字过大可能导致内存占用过高,可能导致OOM异常。
|
||||
|
||||
**参数:**
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
| -------- | -------- | -------- |-------------------------|
|
||||
| size | number | 是 | 图片文件的缓存张数,单位为张。只支持正整数,0 |
|
||||
| memory | number | 是 | 图片文件的缓存大小,单位为字节。只支持正数,0 |
|
||||
|
||||
**示例:**
|
||||
```ts
|
||||
//EntryAbility.ets
|
||||
import { InitImageKnife } from '...imageknife'
|
||||
export default class EntryAbility extends UIAbility {
|
||||
onWindowStageCreate(windowStage: window.WindowStage) {
|
||||
InitImageKnife.init(this.context);
|
||||
let imageKnife: ImageKnife | undefined = ImageKnifeGlobal.getInstance().getImageKnife()
|
||||
if (imageKnife != undefined) {
|
||||
//设置全局内存缓存大小张数
|
||||
imageKnife.setLruCacheSize(100, 100 * 1204 * 1024)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 约束与限制
|
||||
|
||||
在下述版本验证通过:
|
||||
|
|
|
@ -6,13 +6,8 @@
|
|||
"repository": {},
|
||||
"version": "2.1.1-rc.4",
|
||||
"dependencies": {
|
||||
|
||||
// 如果测试entry的demo需要开启以下2个依赖, 然后点击entry勾选 Edit Configurations->点击Deploy Multi Hap->勾选Deploy Multi Hap Packages
|
||||
// 然后点击module栏目 把library也勾选上,这样就可以在HSP场景下测试Entry里面的HSP场景
|
||||
"@ohos/libraryimageknife": "file:../library",
|
||||
"@ohos/disklrucache": "^2.0.2-rc.0",
|
||||
|
||||
// 下面这个依赖是为了跑XTS用例的,需要跑XTS时 需要注释上面2个依赖单独使用imageknife依赖
|
||||
"@ohos/imageknife": "file:../imageknife"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,8 @@ export default class EntryAbility extends UIAbility {
|
|||
.setDefaultLifeCycle(ImageKnifeDrawFactory.createProgressLifeCycle("#10a5ff", 0.5))
|
||||
// 全局配置缓存key
|
||||
imageKnife.setEngineKeyImpl(new CustomEngineKeyImpl())
|
||||
//设置全局内存缓存大小张数
|
||||
imageKnife.setLruCacheSize(100, 100 * 1204 * 1024)
|
||||
}
|
||||
//开启ImageKnife所有级别日志开关
|
||||
LogUtil.mLogLevel = LogUtil.ALL
|
||||
|
|
|
@ -79,7 +79,7 @@ struct IndexFunctionDemo {
|
|||
Button("ImageKnife测试目录页面")
|
||||
.onClick(() => {
|
||||
console.log("pages/imageknifeTestCaseIndex 页面跳转")
|
||||
router.replaceUrl({ url: "pages/imageknifeTestCaseIndex" });
|
||||
router.pushUrl({ url: "pages/imageknifeTestCaseIndex" });
|
||||
}).margin({ top: 15 })
|
||||
}.width('100%').height(60).backgroundColor(Color.Pink)
|
||||
}
|
||||
|
|
|
@ -14,32 +14,110 @@
|
|||
*/
|
||||
import { ImageKnife } from '../imageknife/ImageKnife';
|
||||
import { ImageKnifeData } from '../imageknife/ImageKnifeData';
|
||||
import { LogUtil } from '../imageknife/utils/LogUtil';
|
||||
import { LruCache } from './LruCache';
|
||||
|
||||
export class MemoryLruCache extends LruCache<string, ImageKnifeData> {
|
||||
constructor(maxsize:number) {
|
||||
super(maxsize)
|
||||
maxMemory: number = 0
|
||||
memorySize: number = 0
|
||||
|
||||
constructor(maxsize: number, maxMemory: number) {
|
||||
super(maxsize);
|
||||
this.removeAll();
|
||||
this.maxMemory = maxMemory
|
||||
this.memorySize = 0;
|
||||
}
|
||||
|
||||
// 添加缓存键值对
|
||||
put(key: string, value: ImageKnifeData) {
|
||||
if (key == null || value == null) {
|
||||
throw new Error('key or value is invalid ');
|
||||
}
|
||||
let pre = this.map.get(key)
|
||||
if (pre == null ) {
|
||||
this.size++
|
||||
this.addMemorySize(value)
|
||||
}
|
||||
this.entryRemoved(key, pre, value);
|
||||
this.trimToSize();
|
||||
}
|
||||
|
||||
// 移除键为key的缓存
|
||||
remove(key: string): ImageKnifeData | undefined {
|
||||
if (key == null) {
|
||||
throw new Error('key is null,checking the parameter');
|
||||
}
|
||||
let preValue = this.map.get(key)
|
||||
if (this.map.remove(key)) {
|
||||
this.size--
|
||||
if (preValue != undefined) {
|
||||
this.removeMemorySize(preValue)
|
||||
}
|
||||
}
|
||||
return preValue
|
||||
}
|
||||
|
||||
removeAll() {
|
||||
this.map.clear()
|
||||
this.size = 0
|
||||
this.memorySize = 0;
|
||||
}
|
||||
|
||||
// 移除较少使用的缓存数据
|
||||
trimToSize(tempsize: number) {
|
||||
while (true) {
|
||||
if (tempsize < 0) {
|
||||
this.map.clear()
|
||||
this.size = 0
|
||||
break
|
||||
trimToSize() {
|
||||
LogUtil.info("MemoryLruCache maxSize:" + this.maxsize + " maxMemory: " + this.maxMemory)
|
||||
if (this.maxMemory == 0 || this.maxsize == 0 ){
|
||||
return;
|
||||
}
|
||||
if (this.size <= tempsize || this.map.isEmpty()) {
|
||||
while (true) {
|
||||
if (this.size <= this.maxsize && this.memorySize <= this.maxMemory || this.map.isEmpty()) {
|
||||
break
|
||||
}
|
||||
let delkey = this.map.getFirstKey()
|
||||
let data: ImageKnifeData | undefined = this.map.get(delkey)
|
||||
this.size--
|
||||
if (data != undefined) {
|
||||
this.removeMemorySize(data)
|
||||
data.release()
|
||||
}
|
||||
this.map.remove(delkey)
|
||||
this.size--
|
||||
}
|
||||
}
|
||||
|
||||
private removeMemorySize(value: ImageKnifeData): void {
|
||||
if (value.drawPixelMap != undefined) {
|
||||
LogUtil.info("MemoryLruCache removeMemorySize---- top drawPixelMap memorySize:" + this.memorySize)
|
||||
if (value.drawPixelMap.imagePixelMap != undefined) {
|
||||
this.memorySize -= value.drawPixelMap.imagePixelMap.getPixelBytesNumber();
|
||||
}
|
||||
LogUtil.info("MemoryLruCache removeMemorySize---- end drawPixelMap memorySize:" + this.memorySize)
|
||||
}
|
||||
if (value.drawGIFFrame != undefined) {
|
||||
LogUtil.info("MemoryLruCache removeMemorySize---- top drawGIFFrame memorySize:" + this.memorySize)
|
||||
if (value.drawGIFFrame != undefined) {
|
||||
this.memorySize -= value.drawGIFFrame.getGIFFramesBytesNumber();
|
||||
}
|
||||
LogUtil.info("MemoryLruCache removeMemorySize---- end drawGIFFrame memorySize:" + this.memorySize)
|
||||
}
|
||||
LogUtil.info("MemoryLruCache removeMemorySize---- end mapSize:" + this.map.size())
|
||||
}
|
||||
|
||||
private addMemorySize(value: ImageKnifeData): void {
|
||||
if (value.drawPixelMap != undefined) {
|
||||
LogUtil.info("MemoryLruCache addMemorySize---- top drawPixelMap memorySize:" + this.memorySize)
|
||||
if (value.drawPixelMap.imagePixelMap != undefined) {
|
||||
this.memorySize += value.drawPixelMap.imagePixelMap.getPixelBytesNumber();
|
||||
}
|
||||
LogUtil.info("MemoryLruCache addMemorySize---- end drawPixelMap memorySize:" + this.memorySize)
|
||||
}
|
||||
if (value.drawGIFFrame != undefined) {
|
||||
LogUtil.info("MemoryLruCache addMemorySize---- top drawGIFFrame memorySize:" + this.memorySize)
|
||||
if (value.drawGIFFrame != undefined) {
|
||||
this.memorySize += value.drawGIFFrame.getGIFFramesBytesNumber();
|
||||
}
|
||||
LogUtil.info("MemoryLruCache addMemorySize---- end drawGIFFrame memorySize:" + this.memorySize)
|
||||
}
|
||||
LogUtil.info("MemoryLruCache addMemorySize---- end mapSize:" + this.map.size())
|
||||
}
|
||||
|
||||
}
|
|
@ -78,7 +78,7 @@ export class ImageKnife {
|
|||
this.pausedMaps = new EasyLinkedHashMap();
|
||||
|
||||
// 构造方法传入size 为保存文件个数
|
||||
this.memoryCache = new MemoryLruCache(100);
|
||||
this.memoryCache = new MemoryLruCache(100,100*1024*1024);
|
||||
|
||||
// 创建disk缓存 传入的size 为多少比特 比如20KB 传入20*1024
|
||||
this.diskMemoryCache = DiskLruCache.create(ImageKnifeGlobal.getInstance().getHapContext());
|
||||
|
@ -174,12 +174,13 @@ export class ImageKnife {
|
|||
return new CompressBuilder();
|
||||
}
|
||||
|
||||
// 替代原来的LruCache
|
||||
public replaceLruCache(size: number) {
|
||||
|
||||
// 设置缓存张数,缓存大小,单位字节
|
||||
public setLruCacheSize(size: number,memory:number) {
|
||||
if (this.memoryCache.map.size() <= 0) {
|
||||
this.memoryCache = new MemoryLruCache(size);
|
||||
this.memoryCache = new MemoryLruCache(size,memory);
|
||||
} else {
|
||||
let newLruCache = new MemoryLruCache(size);
|
||||
let newLruCache = new MemoryLruCache(size,memory);
|
||||
this.memoryCache.foreachLruCache((value: ImageKnifeData, key: string, map: Object) => {
|
||||
newLruCache.put(key, value);
|
||||
})
|
||||
|
|
|
@ -87,6 +87,8 @@ export struct ImageKnifeComponent {
|
|||
|
||||
private detachFromLayoutGIF :DetachFromLayout|undefined = undefined;
|
||||
|
||||
private detachFromLayoutPixelMap :DetachFromLayout|undefined = undefined;
|
||||
|
||||
|
||||
build() {
|
||||
Canvas(this.context)
|
||||
|
@ -511,7 +513,6 @@ export struct ImageKnifeComponent {
|
|||
drawMainSource(context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) {
|
||||
LogUtil.log('ImageKnifeComponent default drawMainSource start!')
|
||||
if (data.isPixelMap()) {
|
||||
|
||||
data.drawPixelMap?.imagePixelMap?.getImageInfo().then((imageInfo) => {
|
||||
let scaleType = (typeof imageKnifeOption.mainScaleType == 'number') ? imageKnifeOption.mainScaleType : ScaleType.FIT_CENTER
|
||||
LogUtil.log('ImageKnifeComponent imageinfo width =' + imageInfo.size.width + ' height=' + imageInfo.size.height + 'scaleType=' + scaleType)
|
||||
|
@ -521,6 +522,10 @@ export struct ImageKnifeComponent {
|
|||
context.restore();
|
||||
LogUtil.log('ImageKnifeComponent default drawMainSource end!')
|
||||
})
|
||||
if (data.drawPixelMap != undefined) {
|
||||
data.drawPixelMap.isShowOnComponent = true;
|
||||
this.detachFromLayoutPixelMap = data.drawPixelMap.detachFromLayoutPixelMap;
|
||||
}
|
||||
} else if (data.isGIFFrame()) {
|
||||
if(data.drawGIFFrame != undefined) {
|
||||
data.drawGIFFrame.isShowOnComponent = true
|
||||
|
@ -614,6 +619,9 @@ export struct ImageKnifeComponent {
|
|||
if(this.detachFromLayoutGIF != undefined){
|
||||
this.detachFromLayoutGIF.detach();
|
||||
}
|
||||
if (this.detachFromLayoutPixelMap != undefined) {
|
||||
this.detachFromLayoutPixelMap.detach();
|
||||
}
|
||||
this.resetGifData();
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
*/
|
||||
import { GIFFrame } from './utils/gif/GIFFrame'
|
||||
import { DetachFromLayout } from './RequestOption'
|
||||
import { LogUtil } from './utils/LogUtil'
|
||||
import { Ellipse2 } from '@ohos/svg/src/main/ets/components/utils/SVGBase'
|
||||
|
||||
export enum ImageKnifeType {
|
||||
PIXELMAP = 'PixelMap',
|
||||
|
@ -24,6 +26,21 @@ export enum ImageKnifeType {
|
|||
|
||||
export class DrawPixelMap {
|
||||
imagePixelMap: PixelMap | undefined = undefined
|
||||
isShowOnComponent: boolean = false; //gif是否显示在组件上 true:显示在组件上 false:不显示在组件上
|
||||
isLruCacheRelease: boolean = false; //当前lru是否释放gif资源,true的就释放了gif资源 false就是没有释放
|
||||
|
||||
detachFromLayoutPixelMap: DetachFromLayout = {
|
||||
detach: () => {
|
||||
this.isShowOnComponent = false
|
||||
if (this.isLruCacheRelease) {
|
||||
let pixelMap = this.imagePixelMap;
|
||||
if (pixelMap != undefined) {
|
||||
pixelMap.release();
|
||||
}
|
||||
this.imagePixelMap = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class DrawString {
|
||||
|
@ -56,6 +73,20 @@ export class DrawGIFFrame {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
getGIFFramesBytesNumber(): number {
|
||||
let pixelMapBytes: number = 0;
|
||||
if (this.imageGIFFrames != undefined) {
|
||||
for (let index = 0; index < this.imageGIFFrames.length; index++) {
|
||||
let drawPixelMap = this.imageGIFFrames[index].drawPixelMap;
|
||||
if (drawPixelMap != undefined) {
|
||||
pixelMapBytes += drawPixelMap.getPixelBytesNumber();
|
||||
LogUtil.info("getGIFFramesBytesNumber gif总大小为:" + pixelMapBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
return pixelMapBytes;
|
||||
}
|
||||
}
|
||||
|
||||
export class ImageKnifeData {
|
||||
|
@ -65,9 +96,7 @@ export class ImageKnifeData {
|
|||
static PNG = 'png';
|
||||
static BMP = 'bmp';
|
||||
static WEBP = 'webp';
|
||||
|
||||
waitSaveDisk = false;
|
||||
|
||||
imageKnifeType: ImageKnifeType | undefined = undefined;
|
||||
drawPixelMap: DrawPixelMap | undefined = undefined;
|
||||
drawGIFFrame: DrawGIFFrame | undefined = undefined;
|
||||
|
@ -79,6 +108,7 @@ export class ImageKnifeData {
|
|||
data.imageKnifeType = type;
|
||||
data.drawPixelMap = new DrawPixelMap();
|
||||
data.drawPixelMap.imagePixelMap = value;
|
||||
data.drawPixelMap.isShowOnComponent = true;
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -87,6 +117,7 @@ export class ImageKnifeData {
|
|||
data.imageKnifeType = type;
|
||||
data.drawGIFFrame = new DrawGIFFrame();
|
||||
data.drawGIFFrame.imageGIFFrames = value;
|
||||
data.drawGIFFrame.isShowOnComponent = true;
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -109,6 +140,10 @@ export class ImageKnifeData {
|
|||
release() {
|
||||
if (this.isPixelMap()) {
|
||||
if (this.drawPixelMap != undefined && this.drawPixelMap.imagePixelMap != undefined) {
|
||||
this.drawPixelMap.isLruCacheRelease = true;
|
||||
if (this.drawPixelMap.isShowOnComponent){
|
||||
return;
|
||||
}else {
|
||||
this.drawPixelMap.imagePixelMap.release()
|
||||
.then(() => {
|
||||
if (this.drawPixelMap != undefined && this.drawPixelMap.imagePixelMap != undefined) {
|
||||
|
@ -116,6 +151,8 @@ export class ImageKnifeData {
|
|||
}
|
||||
})
|
||||
}
|
||||
LogUtil.info("MemoryLruCache removeMemorySize---- end 释放普通图片:")
|
||||
}
|
||||
}
|
||||
if (this.isGIFFrame()) {
|
||||
if (this.drawGIFFrame != undefined) {
|
||||
|
@ -132,6 +169,7 @@ export class ImageKnifeData {
|
|||
tempFrame.drawPixelMap.release()
|
||||
}
|
||||
}
|
||||
LogUtil.info("MemoryLruCache removeMemorySize---- end 释放GIF图片:")
|
||||
this.drawGIFFrame.imageGIFFrames = undefined
|
||||
}
|
||||
this.drawGIFFrame.imageGIFFrames = undefined
|
||||
|
@ -152,5 +190,4 @@ export class ImageKnifeData {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -26,12 +26,42 @@ export class ParseImageUtil implements IParseImage<PixelMap> {
|
|||
|
||||
// scale(0,1)
|
||||
parseImageThumbnail(scale: number, imageinfo: ArrayBuffer, onCompleteFunction: (value: PixelMap) => void | PromiseLike<PixelMap>, onErrorFunction: (reason?: BusinessError | string) => void) {
|
||||
taskPoolExecutePixelMap(imageinfo,scale,onCompleteFunction,onErrorFunction);
|
||||
// taskPoolExecutePixelMap(imageinfo,scale,onCompleteFunction,onErrorFunction);
|
||||
|
||||
let imageSource:image.ImageSource = image.createImageSource(imageinfo); // 步骤一:文件转为pixelMap 然后变换 给Image组件
|
||||
imageSource.getImageInfo((err, value) => {
|
||||
if (err) {
|
||||
onErrorFunction(err);
|
||||
return;
|
||||
}
|
||||
let hValue = Math.round(value.size.height * scale);
|
||||
let wValue = Math.round(value.size.width * scale);
|
||||
let defaultSize:image.Size = {
|
||||
height: hValue,
|
||||
width: wValue
|
||||
};
|
||||
|
||||
let opts:image.DecodingOptions = {
|
||||
editable: true,
|
||||
desiredSize: defaultSize
|
||||
};
|
||||
imageSource.createPixelMap(opts, (err, pixelmap) => {
|
||||
if (err) {
|
||||
onErrorFunction(err);
|
||||
} else {
|
||||
onCompleteFunction(pixelmap);
|
||||
}
|
||||
imageSource.release()
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// let TAG:string = "ParseImageUtil_TASK"
|
||||
|
||||
|
||||
@Concurrent
|
||||
async function taskParseImage(arrayBuffer: ArrayBuffer,scale: number): Promise<PixelMap> {
|
||||
|
|
|
@ -35,7 +35,47 @@ export interface gifBackData {
|
|||
|
||||
export class GIFParseImpl implements IParseGif {
|
||||
parseGifs(imageinfo: ArrayBuffer, callback: (data?: GIFFrame[], err?: BusinessError | string) => void) {
|
||||
taskPoolExecutePixelMapList(imageinfo,callback);
|
||||
// taskPoolExecutePixelMapList(imageinfo,callback);
|
||||
// oh解码流程
|
||||
let imageSource = image.createImageSource(imageinfo);
|
||||
let decodeOpts: image.DecodingOptions = {
|
||||
sampleSize: 1,
|
||||
editable: true,
|
||||
rotate: 0
|
||||
}
|
||||
let data:GIFFrame[] = [];
|
||||
imageSource.createPixelMapList(decodeOpts).then((pixelList: Array<PixelMap>) => {
|
||||
//sdk的api接口发生变更:从.getDelayTime() 变为.getDelayTimeList()
|
||||
imageSource.getDelayTimeList().then(delayTimes => {
|
||||
if (pixelList.length > 0) {
|
||||
let pixelmap = pixelList[0];
|
||||
pixelmap.getImageInfo().then(imageInfo => {
|
||||
for (let i = 0; i < pixelList.length; i++) {
|
||||
let frame = new GIFFrame();
|
||||
frame.drawPixelMap = pixelList[i];
|
||||
frame.dims = { width: imageInfo.size.width, height: imageInfo.size.height, top: 0, left: 0 }
|
||||
if (i < delayTimes.length) {
|
||||
frame.delay = delayTimes[i];
|
||||
} else {
|
||||
frame.delay = delayTimes[delayTimes.length - 1]
|
||||
}
|
||||
data.push(frame)
|
||||
}
|
||||
callback(data,undefined)
|
||||
imageSource.release();
|
||||
}).catch((err: string) => {
|
||||
imageSource.release();
|
||||
callback(undefined,err)
|
||||
})
|
||||
}
|
||||
}).catch((err: string) => {
|
||||
imageSource.release();
|
||||
callback(undefined,err)
|
||||
})
|
||||
}).catch((err: string) => {
|
||||
imageSource.release();
|
||||
callback(undefined,err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,8 +90,8 @@ async function taskParseGif(arrayBuffer: ArrayBuffer): Promise<GIFFrame[]> {
|
|||
}
|
||||
let pixelList = await imageSource.createPixelMapList(decodeOpts);
|
||||
if (pixelList.length > 0) {
|
||||
let pixelmap1 = pixelList[0];
|
||||
let imageInfo = await pixelmap1.getImageInfo();
|
||||
let pixelmap = pixelList[0];
|
||||
let imageInfo = await pixelmap.getImageInfo();
|
||||
let delayTimes = await imageSource.getDelayTimeList();
|
||||
for (let i = 0; i < pixelList.length; i++) {
|
||||
let frame = new GIFFrame();
|
||||
|
|
Loading…
Reference in New Issue