Fresco 5.0以上内存持续增长问题优化
来源:互联网 发布:win10翻墙软件 编辑:程序博客网 时间:2024/05/02 17:57
fresco是android一款比较好的图片处理框架,特别是在5.0以下,效果很佳。
在5.0以下系统,Fresco将图片放到一个特别的内存区域ashmem中。这块内存我们通过android studio查看时不会显示,回收机制与java回收机制差不多。当然,在图片不显示的时候,占用的内存会自动被释放。这会使得APP更加流畅,减少因图片内存占用而引发的OOM。
5.0以上系统,我们使用了Fresco,但是看到的效果是内存持续增长到200M,甚至300M,线上的OutOfMemoryError成百上千的。当时一度怀疑Fresco这个框架是不是支持的不够好。静下心来研究了一下fresco内部机制,最终解决了Fresco 5.0以上内存优化的问题。
是否能使用Fresco把5.0以上系统bitmap 保存到ashmem中
也是网上广为流传的方式,通过BitmapFactory.Options的属性:
options.inPurgeable
下面通过实例测试
//图片1ImageView iv = new ImageView(this); iv.setLayoutParams(new ViewGroup.LayoutParams(500,300)); options = new BitmapFactory.Options(); options.inPurgeable = true; iv1.setImageBitmap( BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options));//图片2 ImageView iv1 = new ImageView(this); iv1.setLayoutParams(new ViewGroup.LayoutParams(500,300)); iv1.setImageBitmap( BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options));//图片3 ImageView iv2 = new ImageView(this); iv2.setLayoutParams(new ViewGroup.LayoutParams(500,300)); iv2.setImageBitmap( BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options));
3张图片,单独显示一张在21M左右,两两组合 内存在28M左右,3张图片一起显示在32M左右。使用了inPurgeable没啥效果。查看文档在api21已经废弃
@deprecated As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this is ignored.
再则,如果选择把5.0以上bitmap都保存在ashmem中,就得抛弃fresco,自写一套图片框架,这个工程比较大,也没能力写的比fresco好。放弃了这条路。
能否监听Fresco回调方法,及时释放内存
memoryTrimmableRegistry它持有一个MemoryTrimmable的集合。Fresco的缓存就在它们中间。当你接受到一个系统的内存事件时,你可以调用MemoryTrimmable的对应方法来释放资源。
当我们继承memoryTrimmableRegistry设置一个监听后,发现只是在注册时候收到回调,缓存时候没有收到对应的回调,查看源码,在CountingMemoryCache中有下面一段代码:
public void trim(MemoryTrimType trimType) { ....... final double trimRatio = mCacheTrimStrategy.getTrimRatio(trimType); ....... }
最终调用了BitmapMemoryCacheTrimStrategy.getTrimRatio(trimType),这样就不会回调到我们自定义的MyMemoryTrimmableRegistry了,这样memoryTrimmableRegistry回调中手动释放内存行不通。
ImageCacheStatsTracker统计缓存的命中率,你可以实现ImageCacheStatsTracker, 在这个类中,每个缓存时间都有回调通知,基于这些事件,可以实现缓存的计数和统计。
当添加缓存是put收到回调,命中缓存是Hit回调,没有命中缓存miss回调
@Override public void onMemoryCachePut() { } @Override public void onMemoryCacheHit() { } @Override public void onMemoryCacheMiss() { }
不过这些回调不是我们注册的那个ImageCacheStatsTracker,而是fresco内部类中的回调,并且这些回调也没有参数,我们不能清楚的知道当前内存多少,是不是该清除内存了,所以这个方式也是行不通的。
清除Fresco标记为eviction的缓存
eviction: 逐出,收回
既然被你标记为回收,这部分内存就是用处不大的,可以及时清除掉这部分内存,就不会出现内存持续增长了。
/** Checks the cache constraints to determine whether the new value can be cached or not. */ private synchronized boolean canCacheNewValue(V value) { int newValueSize = mValueDescriptor.getSizeInBytes(value); return (newValueSize <= mMemoryCacheParams.maxCacheEntrySize) && (getInUseCount() <= mMemoryCacheParams.maxCacheEntries - 1) && (getInUseSizeInBytes() <= mMemoryCacheParams.maxCacheSize - newValueSize); } /** Gets the number of the cached items that are used by at least one client. */ public synchronized int getInUseCount() { return mCachedEntries.getCount() - mExclusiveEntries.getCount(); } /** Gets the total size in bytes of the cached items that are used by at least one client. */ public synchronized int getInUseSizeInBytes() { return mCachedEntries.getSizeInBytes() - mExclusiveEntries.getSizeInBytes(); }
从源码中看出,在缓存之前需要检查能否缓存canCacheNewValue,比较当前使用的缓存cacheSize与maxCacheSize;当前缓存数量cacheEntrys与maxCacheEntrys大小。只要不超过maxCacheSize且不超过maxCacheEntrys,就可以添加到缓存队列中。
上面都没啥问题,注意到cacheSize与cacheEntrys的计算方式
mCachedEntries.getCount() - mExclusiveEntries.getCount()mCachedEntries.getSizeInBytes() - mExclusiveEntries.getSizeInBytes()
可以看到是先减去将要被回收的那部分bitmap的数量和size,问题就在这里,mExclusiveEntries是将要被回收的缓存,但是还没有被回收,如果这部分内存足够大时,又没有被Fresco计算在内,可能引起OOM。经过测试,这部分内存,经常保持在40-60M之间,这么大块内存没有被及时回收,不发生OOM才怪呢。所以我们只需要减小ExclusiveEntries的大小,就能及时的回收fresco内存了
找到了方向,主要减小ExclusiveEntries池大小
Fresco默认DefaultBitmapMemoryCacheParamsSupplier中设置了EVICTION池为Integer.MAX_VALUE,我们只需仿照这个DefaultBitmapMemoryCacheParamsSupplier,把EVICTION池该成足够小,就可以了了
private static final int MAX_EVICTION_QUEUE_SIZE = Integer.MAX_VALUE; private static final int MAX_EVICTION_QUEUE_ENTRIES = Integer.MAX_VALUE; private static final int MAX_CACHE_ENTRY_SIZE = Integer.MAX_VALUE;
自定义Supplier 完整代码
这里设置EVICTION为5M,eviction entry数量为5条记录,单一entry大小为1M,设置为5是为了减少GC的次数,5M内存积累也需要一段时间,不会影响app使用体验。当然你可以设置为1M或者更小。
public class MyBitmapMemoryCacheParamsSupplier implements Supplier<MemoryCacheParams> { private static final int MAX_CACHE_ENTRIES = 56; private static final int MAX_CACHE_ASHM_ENTRIES = 128; private static final int MAX_CACHE_EVICTION_SIZE = 5; private static final int MAX_CACHE_EVICTION_ENTRIES = 5; private final ActivityManager mActivityManager; public MyBitmapMemoryCacheParamsSupplier(ActivityManager activityManager) { mActivityManager = activityManager; } @Override public MemoryCacheParams get() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return new MemoryCacheParams(getMaxCacheSize(), MAX_CACHE_ENTRIES, MAX_CACHE_EVICTION_SIZE, MAX_CACHE_EVICTION_ENTRIES, 1); } else { return new MemoryCacheParams( getMaxCacheSize(), MAX_CACHE_ASHM_ENTRIES, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); } } private int getMaxCacheSize() { final int maxMemory = Math.min(mActivityManager.getMemoryClass() * ByteConstants.MB, Integer.MAX_VALUE); if (maxMemory < 32 * ByteConstants.MB) { return 4 * ByteConstants.MB; } else if (maxMemory < 64 * ByteConstants.MB) { return 6 * ByteConstants.MB; } else { return maxMemory / 5; } }}
在初始化Fresco的时候把MyBitmapMemoryCacheParamsSupplier设置到config中。
然后重写下application的 onTrimMemory,onLowMemory
@Override public void onTrimMemory(int level) { super.onTrimMemory(level); FrescoUtils.TrimMemory(level); } @Override public void onLowMemory() { super.onLowMemory(); FrescoUtils.clearAllMemoryCaches(); }
当然防止单张图片过大导致的OOM,需要使用Resize属性
public void setImageURIListener(Uri uri, ControllerListener listener, boolean isSmall) { if(uri == null){ return; } PipelineDraweeControllerBuilder builder = Fresco.getDraweeControllerBuilderSupplier().get() .setCallerContext(null) .setUri(uri) .setOldController(getController()); builder.setControllerListener(listener); ResizeOptions resizeOptions; if (isSmall) { resizeOptions = new ResizeOptions(Util.dip2px(mContext, 144), Util.dip2px(mContext, 144)); } else { resizeOptions = new ResizeOptions(AppConfig.sScreenWidth, AppConfig.sScreenWidth / 2); } ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri) .setResizeOptions(resizeOptions).build(); builder.setImageRequest(request); setController(builder.build());}
Resize对jpg格式有效,对于png等其他格式的图片也支持这个属性,需要设置Downsample
ImagePipelineConfig.newBuilder(context) .setDownsampleEnabled(true)
大功告成,再也不用担心5.0以上使用Fresco出现OOM了
效果:停留在一个页面,一段时间后出现明显的GC现象
- Fresco 5.0以上内存持续增长问题优化
- php大数据量查询时内存持续增长问题
- 【iOS自动约束】使用Masonry导致内存持续增长问题分析
- pthread_create后没有detach导致内存持续增长
- pthread_create后没有detach导致内存持续增长
- Fresco加载图片优化
- Fresco内存管理机制学习
- 内存性能优化问题
- 程序更新后tomcat内存持续增长原因分析
- Linux_Swap持续增长的问题(tcpdump引入,与使用方法)
- 关于SYSAUX表空间持续增长问题的排查
- JVM的Perm区持续增长导致OOM问题记录
- fresco 编译问题
- fresco源码分析-内存回收
- Android内存讲解与Fresco
- Java内存问题分析优化
- DatePicker 控件在5.0以上版本 焦点释放问题 手动输入点击确定按钮不能正确获取到日期优化
- 一个隐蔽的内存泄漏——pthread_create后没有detach导致内存持续增长
- Android中定时器的3种实现方法
- spark pipeline原理学习和记录
- [leetcode]53. Maximum Subarray
- [ZETCODE]wxWidgets教程五:布局管理
- ChemDrew如何翻转化学结构
- Fresco 5.0以上内存持续增长问题优化
- React native 拨打电话功能
- Android M wifi 分配IP地址失败
- MyEclipse8.5 jad 反编译工具 插件安装
- jdbc.url,请记得带上我,allowMultiQueries=true
- JS判断客户端是否是iOS或者Android还是电脑端
- Android Material Design简单使用
- angular2学习(1)
- hdu 4991 dp+数状数组