高效省内存的播放序列帧控件,支持从文件,resource读取序列帧,内存复用,读取多张只需一张图片内存,流式API,一行代码即可实现序列帧动画
由于腾讯的文章向来只有描述,从无具体代码,此项目为该文章的具体实现,
基本原理依旧是双线程协作,子线程负责解码图片,主线程负责接收图片后设置到View上去,由于内部内存复用,同时只占用一张图片的内存
2.性能测试,内存占用,CPU占用
(1)直接读取200张图,直接OOM
(2)边读边写,OPPO x9007上内存占用如下:

(3)采用本项目后,x9007上内存占用如下:

内存只占用一张图片内存,CPU占用14%左右,已符合大部分项目场景,无兼容及解码效率的问题;
project build.gradle加入jitpack仓库
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
项目的build.gradle加入
dependencies {
compile 'com.github.Mr-wangyong:ImageFrame:v1.2.0'
}
项目进行重构,部分API已标记为过时@Deprecated,不建议使用,结构改为ImageFrameHandler负责处理序列帧加载,通过回调OnImageLoadListener
返回BitmapDrawable给外部的View设置图片,专注于序列帧处理,便于外界进行自由定制操作;同时API参考AlertDialog 流式API,更加简洁好用;
增加的API:
播放
build.start();//开始播放序列帧
暂停
build.pause();//暂停播放
指定从某一帧开始:
build.setStartIndex(10)
指定从某一帧结束:
build.setEndIndex(10)
支持从文件/Resource目录读取.流式API,简洁好用
从文件读取:
简洁模式,你可以创建一个自定义的View,内部封装这些功能,参考ImageFrameCustomView,我这里给你的实现(当然,建议根据项目需求定义更好更实用的),直接调用startImageFrame方法即可开启序列帧动画
imageFrame.startImageFrame(new ImageFrameHandler.FileHandlerBuilder(file.listFiles())
.setFps(40)// 设置fps(每秒播放多少帧,建议不大于30 默认30 太大了设备性能不够磁盘可能读取不过来丢帧)
.setWidth(100)// 设置图片宽度 非必须 设置了会内部压缩 否则不会
.setHeight(300)// 设置图片高度 非必须 同上
.setStartIndex(10)// 设置开始的序列帧图片index,可以指定从那张图片开始
.setEndIndex(30)// 设置结束的序列帧图片index,可以指定从那张图片结束
.setLoop(true)// 设置是否循环播放
.openLruCache(true)// 设置是否开启LRU缓存,如果不循环,建议不开启,如果循环,建议开启,不过多次测试性能相差并不大
.build());// 构建一个
``` File file = new File(testDir); //创建一个FileHandlerBuilder,API和Android系统AlertDialog相似 ImageFrameHandler build = new ImageFrameHandler.FileHandlerBuilder(file.listFiles()) //必须传入File[]座位处理源 .setFps(40) // 设置fps(每秒播放多少帧,建议不大于30 默认30 太大了设备性能不够磁盘可能读取不过来丢帧) .setWidth(100) // 设置图片宽度 非必须 设置了会内部压缩 否则不会 .setHeight(300)// 设置图片高度 非必须 同上 .setStartIndex(10) // 设置开始的序列帧图片index,可以指定从那张图片开始 .setEndIndex(30) // 设置结束的序列帧图片index,可以指定从那张图片结束 .setLoop(true)// 设置是否循环播放 .openLruCache(true)// 设置是否开启LRU缓存,如果不循环,建议不开启,如果循环,建议开启,不过多次测试性能相差并不大 .setOnImageLoaderListener(new ImageFrameHandler.OnImageLoadListener() { //创建一个监听器 监听解析成功 以及完成解析 @Override public void onImageLoad(BitmapDrawable drawable) { //给你的View设置上图片 ViewCompat.setBackground(view,drawable); }
@Override
public void onPlayFinish() {
}
})
.build();// 构建一个Handler 来处理
build.start();//开始播放序列帧
```
当然,你还可以有stop,pause,更自由的控制加载的过程;
``` build.start();//开始播放序列帧
build.pause();//暂停播放
build.stop();//停止播放 ```
原理同上,只是构建的是ResourceHandlerBuilder
``` final int[] resIds = new int[210]; ImageFrameHandler build = new ImageFrameHandler.ResourceHandlerBuilder(getResources(),resIds)//必须传入Resource和resIds资源id集合作为处理源 .setFps(40)// 设置fps(每秒播放多少帧,建议不大于30 默认30 太大了设备性能不够磁盘可能读取不过来丢帧) .setWidth(100) // 设置图片宽度 非必须 设置了会内部压缩 否则不会 .setHeight(300)// 设置图片高度 非必须 同上 .setStartIndex(10) // 设置开始的序列帧图片index,可以指定从那张图片开始 .setEndIndex(30) // 设置结束的序列帧图片index,可以指定从那张图片结束 .setLoop(true)// 设置是否循环播放 .openLruCache(true)// 设置是否开启LRU缓存,如果不循环,建议不开启,如果循环,建议开启,不过多次测试性能相差并不大 .setOnImageLoaderListener(new ImageFrameHandler.OnImageLoadListener() { //创建一个监听器 监听解析成功 以及完成解析 @Override public void onImageLoad(BitmapDrawable drawable) { //给你的View设置上图片 ViewCompat.setBackground(view,drawable); }
@Override
public void onPlayFinish() {
}
})
.build();// 构建一个Handler 来处理
build.start();//开始播放序列帧
```
将图片加载部分抽取成一个模块ImageFrameProxy;API和ImageFrameView一样,ImageFrameView变为一个示例;你可以在获取bitmap后添加更多的处理;
注意:使用proxy请在页面退出时调用proxy.stop()防止内存泄露
@Override
protected void onDestroy() {
proxy.stop();
super.onDestroy();
}
imageFrame.setLoop(true);
imageFrame.stop();
BitmapFactory.decodeResource
方式读取图片,设置inBitmap有坑,图片会闪烁部分加载不完整,
解决办法:直接用
decodeStream(resources.openRawResource(resId),null,options)
代替BitmapFactory.decodeResource
官网 关于Bitmap复用:
https://developer.android.com/training/displaying-bitmaps/manage-memory.html#recycle
$ claude mcp add ImageFrame \
-- python -m otcore.mcp_server <graph>