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现象

这里写图片描述

2 0
原创粉丝点击