Fresco 5.0以上内存持续增长问题优化

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

下面通过实例测试

//图片1
ImageView 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以上内存持续增长问题优化_第1张图片

你可能感兴趣的:(代码优化,android,内存,Fresco,fresco-5-0,内存优化)