fresco源码分析-内存回收

           我觉得内存管理是三方图库最重要的点, 而且该知识点能够应用到项目里, 所以着重看了一下fresco是如何回收内存的。

      fresco内存释放分为2种方式:

 1、按照LruCach的方式释放引用计数为0对象, fresco内部逻辑实现;

 2、应用退到后台、手机低内存等场景下主动释放fresco的内存, 包括引用计数不为0的对象, 需要传事件给fresco。 参考: https://github.com/facebook/fresco/issues/1457

ImagePipeline imagePipeline = Fresco.getImagePipeline();
imagePipeline.clearMemoryCaches(); //内存,包括已解码和未解码
imagePipeline.clearDiskCaches();   //删除文件

// combines above two lines
imagePipeline.clearCaches();


                                         fresco源码分析-内存回收_第1张图片

          上图是fresco官方提供的架构图, 按照三级缓存。 即已解码的图片,未解码的图片和文件缓存。Android5.0以前bitmap是缓存在ashmem里, Android5.0及以上保存在java堆里。 为了释放bitmap, fresco定义了引用计数类SharedPrefrence。

public class SharedReference {

  // Keeps references to all live objects so finalization of those Objects always happens after
  // SharedReference first disposes of it. Note, this does not prevent CloseableReference's from
  // being finalized when the reference is no longer reachable.
  @GuardedBy("itself")
  private static final Map sLiveObjects = new IdentityHashMap<>();

  @GuardedBy("this")
  private T mValue;
  @GuardedBy("this")
  private int mRefCount;

  private final ResourceReleaser mResourceReleaser;

  /**
   * Construct a new shared-reference that will 'own' the supplied {@code value}.
   * The reference count will be set to 1. When the reference count decreases to zero
   * {@code resourceReleaser} will be used to release the {@code value}
   * @param value non-null value to manage
   * @param resourceReleaser non-null ResourceReleaser for the value
   */
  public SharedReference(T value, ResourceReleaser resourceReleaser) {
    mValue = Preconditions.checkNotNull(value);
    mResourceReleaser = Preconditions.checkNotNull(resourceReleaser);
    mRefCount = 1;
    addLiveReference(value);
  }

  /**
   * Increases the reference count of a live object in the static map. Adds it if it's not
   * being held.
   *
   * @param value the value to add.
   */
  private static void addLiveReference(Object value) {
    synchronized (sLiveObjects) {
      Integer count = sLiveObjects.get(value);
      if (count == null) {
        sLiveObjects.put(value, 1);
      } else {
        sLiveObjects.put(value, count + 1);
      }
    }
  }

  /**
   * Decreases the reference count of live object from the static map. Removes it if it's reference
   * count has become 0.
   *
   * @param value the value to remove.
   */
  private static void removeLiveReference(Object value) {
    synchronized (sLiveObjects) {
      Integer count = sLiveObjects.get(value);
      if (count == null) {
        // Uh oh.
        FLog.wtf(
            "SharedReference",
            "No entry in sLiveObjects for value of type %s",
            value.getClass());
      } else if (count == 1) {
        sLiveObjects.remove(value);
      } else {
        sLiveObjects.put(value, count - 1);
      }
    }
  }

  /**
   * Get the current referenced value. Null if there's no value.
   * @return the referenced value
   */
  public synchronized T get() {
    return mValue;
  }

  /**
   * Checks if this shared-reference is valid i.e. its reference count is greater than zero.
   * @return true if shared reference is valid
   */
  public synchronized boolean isValid() {
    return mRefCount > 0;
  }

  /**
   * Checks if the shared-reference is valid i.e. its reference count is greater than zero
   * @return true if the shared reference is valid
   */
  public static boolean isValid(SharedReference ref) {
    return ref != null && ref.isValid();
  }

  /**
   * Bump up the reference count for the shared reference
   * Note: The reference must be valid (aka not null) at this point
   */
  public synchronized void addReference() {
    ensureValid();
    mRefCount++;
  }

  /**
   * Decrement the reference count for the shared reference. If the reference count drops to zero,
   * then dispose of the referenced value
   */
  public void deleteReference() {
    if (decreaseRefCount() == 0) {
      T deleted;
      synchronized (this) {
        deleted = mValue;
        mValue = null;
      }
      mResourceReleaser.release(deleted);
      removeLiveReference(deleted);
    }
  }

  /**
   * Decrements reference count for the shared reference. Returns value of mRefCount after
   * decrementing
   */
  private synchronized int decreaseRefCount() {
    ensureValid();
    Preconditions.checkArgument(mRefCount > 0);

    mRefCount--;
    return mRefCount;
  }

  /**
   * Assert that there is a valid referenced value. Throw a NullReferenceException otherwise
   * @throws NullReferenceException, if the reference is invalid (i.e.) the underlying value is null
   */
  private void ensureValid() {
    if (!isValid(this)) {
      throw new NullReferenceException();
    }
  }

  /**
   * A test-only method to get the ref count
   * DO NOT USE in regular code
   */
  public synchronized int getRefCountTestOnly() {
    return mRefCount;
  }

  /**
   * The moral equivalent of NullPointerException for SharedReference. Indicates that the
   * referenced object is null
   */
  public static class NullReferenceException extends RuntimeException {
    public NullReferenceException() {
      super("Null shared reference");
    }
  }
}

       这个类中有这么两个重要方法:addReference()和deleteReference(),通过这两个基本方法来对引用进行计数,一旦计数为零时,则调用ResourceReleaser的release方法(调用了如:nativeFree、删除引用或Bitmap.recycle()等)。

         在Android5.0后Fresco又封装了CloseableReference类实现了Cloneable、Closeable接口,它在调用.clone()方法时同时会调用addReference()来增加一个引用计数,在调用.close()方法时同时会调用deleteReference()来删除一个引用计数。 fresco对CloseableReference的注释:This class allows reference-counting semantics in a Java-friendlier way. A single object can have any number of CloseableReferences pointing to it. When all of these have been closed, the object either has its {@link Closeable#close} method called, if it implements {@link Closeable}, or its designated {@link ResourceReleaser#release}。 翻译过来就是CloseReference类提供了一种友好的引用计数方式,多个CloseReference对象可以指向同一个对象, 当所有的CloseReference都关闭时则调用Closeable接口的close方法(如果实现了Closeable接口),或者ResourceReleaser接口的release方法。

      1、在赋值CloseableReference给新对象的时候,调用.clone()进行赋值, 引用计数加1。
      2、在超出作用域范围的时候,必须调用.close(),通常会在finally代码块中调用, 引用计数减1。

/**
 * Decrement the reference count for the shared reference. If the reference count drops to zero,
 * then dispose of the referenced value
 */
public void deleteReference() {
  if (decreaseRefCount() == 0) {
    T deleted;
    synchronized (this) {
      deleted = mValue;
      mValue = null;
    }
    mResourceReleaser.release(deleted);
    removeLiveReference(deleted);
  }
}
         当引用计数等于0时, 调用release方法释放内存。 Fresco代码里随处可见CloseReference的身影, 这个类是可以抽出来用作我们的内存管理类。


        前面提到了已解码的mBitmapMemoryCache和未解码的mEncodedMemoryCache图片缓存, 实际上都是用CountingMemoryCache类实例化(InstrumentedMemoryCache的基类)的对象。    其内部成员变量有2个重要的对象,即mCacheEntries和mExclusiveEntries。  正在使用的数据放在mCachedEntries里,  等待复用(待回收)的数据放在mExclusiveEntries。 而mCachedEntries和mExclusiveEntries都是CountingLruMap的对象,类似于LruCache, 即从最远使用开始移除。


/*
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

package com.facebook.imagepipeline.cache;

import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;

import java.util.ArrayList;
import java.util.concurrent.TimeUnit;

import android.os.SystemClock;
import android.util.Log;

import com.facebook.common.internal.Preconditions;
import com.facebook.common.internal.Supplier;
import com.facebook.common.internal.VisibleForTesting;
import com.facebook.common.memory.MemoryTrimType;
import com.facebook.common.memory.MemoryTrimmable;
import com.facebook.common.references.CloseableReference;
import com.facebook.common.references.ResourceReleaser;

import com.android.internal.util.Predicate;

/**
 * Layer of memory cache stack responsible for managing eviction of the the cached items.
 *
 * 

This layer is responsible for LRU eviction strategy and for maintaining the size boundaries * of the cached items. * *

Only the exclusively owned elements, i.e. the elements not referenced by any client, can be * evicted. * * @param the key type * @param the value type */ @ThreadSafe public class CountingMemoryCache implements MemoryCache, MemoryTrimmable { /** * Interface used to specify the trimming strategy for the cache. */ public interface CacheTrimStrategy { double getTrimRatio(MemoryTrimType trimType); } /** * Interface used to observe the state changes of an entry. */ public interface EntryStateObserver { /** * Called when the exclusivity status of the entry changes. * *

The item can be reused if it is exclusively owned by the cache. */ void onExclusivityChanged(K key, boolean isExclusive); } /** * The internal representation of a key-value pair stored by the cache. */ @VisibleForTesting static class Entry { public final K key; public final CloseableReference valueRef; // The number of clients that reference the value. public int clientCount; // Whether or not this entry is tracked by this cache. Orphans are not tracked by the cache and // as soon as the last client of an orphaned entry closes their reference, the entry's copy is // closed too. public boolean isOrphan; @Nullable public final EntryStateObserver observer; private Entry(K key, CloseableReference valueRef, @Nullable EntryStateObserver observer) { this.key = Preconditions.checkNotNull(key); this.valueRef = Preconditions.checkNotNull(CloseableReference.cloneOrNull(valueRef)); this.clientCount = 0; this.isOrphan = false; this.observer = observer; } /** Creates a new entry with the usage count of 0. */ @VisibleForTesting static Entry of( final K key, final CloseableReference valueRef, final @Nullable EntryStateObserver observer) { return new Entry<>(key, valueRef, observer); } } // How often the cache checks for a new cache configuration. @VisibleForTesting static final long PARAMS_INTERCHECK_INTERVAL_MS = TimeUnit.MINUTES.toMillis(5); // Contains the items that are not being used by any client and are hence viable for eviction. @GuardedBy("this") @VisibleForTesting final CountingLruMap> mExclusiveEntries; // Contains all the cached items including the exclusively owned ones. @GuardedBy("this") @VisibleForTesting final CountingLruMap> mCachedEntries; private final ValueDescriptor mValueDescriptor; private final CacheTrimStrategy mCacheTrimStrategy; // Cache size constraints. private final Supplier mMemoryCacheParamsSupplier; @GuardedBy("this") protected MemoryCacheParams mMemoryCacheParams; @GuardedBy("this") private long mLastCacheParamsCheck; public CountingMemoryCache( ValueDescriptor valueDescriptor, CacheTrimStrategy cacheTrimStrategy, Supplier memoryCacheParamsSupplier) { mValueDescriptor = valueDescriptor; mExclusiveEntries = new CountingLruMap<>(wrapValueDescriptor(valueDescriptor)); mCachedEntries = new CountingLruMap<>(wrapValueDescriptor(valueDescriptor)); mCacheTrimStrategy = cacheTrimStrategy; mMemoryCacheParamsSupplier = memoryCacheParamsSupplier; mMemoryCacheParams = mMemoryCacheParamsSupplier.get(); mLastCacheParamsCheck = SystemClock.uptimeMillis(); } private ValueDescriptor> wrapValueDescriptor( final ValueDescriptor evictableValueDescriptor) { return new ValueDescriptor>() { @Override public int getSizeInBytes(Entry entry) { return evictableValueDescriptor.getSizeInBytes(entry.valueRef.get()); } }; } /** * Caches the given key-value pair. * *

Important: the client should use the returned reference instead of the original one. * It is the caller's responsibility to close the returned reference once not needed anymore. * * @return the new reference to be used, null if the value cannot be cached */ public CloseableReference cache(final K key, final CloseableReference valueRef) { return cache(key, valueRef, null); } /** * Caches the given key-value pair. * *

Important: the client should use the returned reference instead of the original one. * It is the caller's responsibility to close the returned reference once not needed anymore. * * @return the new reference to be used, null if the value cannot be cached */ public CloseableReference cache( final K key, final CloseableReference valueRef, final EntryStateObserver observer) { Preconditions.checkNotNull(key); Preconditions.checkNotNull(valueRef); maybeUpdateCacheParams(); Entry oldExclusive; CloseableReference oldRefToClose = null; CloseableReference clientRef = null; synchronized (this) { // remove the old item (if any) as it is stale now oldExclusive = mExclusiveEntries.remove(key); Entry oldEntry = mCachedEntries.remove(key); if (oldEntry != null) { makeOrphan(oldEntry); oldRefToClose = referenceToClose(oldEntry); } if (canCacheNewValue(valueRef.get())) { Entry newEntry = Entry.of(key, valueRef, observer); mCachedEntries.put(key, newEntry); clientRef = newClientReference(newEntry); } } CloseableReference.closeSafely(oldRefToClose); maybeNotifyExclusiveEntryRemoval(oldExclusive); maybeEvictEntries(); return clientRef; } /** 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 item with the given key, or null if there is no such item. * *

It is the caller's responsibility to close the returned reference once not needed anymore. */ @Nullable public CloseableReference get(final K key) { Preconditions.checkNotNull(key); Entry oldExclusive; CloseableReference clientRef = null; synchronized (this) { oldExclusive = mExclusiveEntries.remove(key); Entry entry = mCachedEntries.get(key); if (entry != null) { clientRef = newClientReference(entry); } } maybeNotifyExclusiveEntryRemoval(oldExclusive); maybeUpdateCacheParams(); maybeEvictEntries(); return clientRef; } /** Creates a new reference for the client. */ private synchronized CloseableReference newClientReference(final Entry entry) { increaseClientCount(entry); return CloseableReference.of( entry.valueRef.get(), new ResourceReleaser() { @Override public void release(V unused) { releaseClientReference(entry); } }); } /** Called when the client closes its reference. */ private void releaseClientReference(final Entry entry) { Preconditions.checkNotNull(entry); boolean isExclusiveAdded; CloseableReference oldRefToClose; synchronized (this) { decreaseClientCount(entry); isExclusiveAdded = maybeAddToExclusives(entry); oldRefToClose = referenceToClose(entry); } CloseableReference.closeSafely(oldRefToClose); maybeNotifyExclusiveEntryInsertion(isExclusiveAdded ? entry : null); maybeUpdateCacheParams(); maybeEvictEntries(); } /** Adds the entry to the exclusively owned queue if it is viable for eviction. */ private synchronized boolean maybeAddToExclusives(Entry entry) { if (!entry.isOrphan && entry.clientCount == 0) { mExclusiveEntries.put(entry.key, entry); return true; } return false; } /** * Gets the value with the given key to be reused, or null if there is no such value. * *

The item can be reused only if it is exclusively owned by the cache. */ @Nullable public CloseableReference reuse(K key) { Preconditions.checkNotNull(key); CloseableReference clientRef = null; boolean removed = false; Entry oldExclusive = null; synchronized (this) { oldExclusive = mExclusiveEntries.remove(key); if (oldExclusive != null) { Entry entry = mCachedEntries.remove(key); Preconditions.checkNotNull(entry); Preconditions.checkState(entry.clientCount == 0); // optimization: instead of cloning and then closing the original reference, // we just do a move clientRef = entry.valueRef; removed = true; } } if (removed) { maybeNotifyExclusiveEntryRemoval(oldExclusive); } return clientRef; } /** * Removes all the items from the cache whose key matches the specified predicate. * * @param predicate returns true if an item with the given key should be removed * @return number of the items removed from the cache */ public int removeAll(Predicate predicate) { ArrayList> oldExclusives; ArrayList> oldEntries; synchronized (this) { oldExclusives = mExclusiveEntries.removeAll(predicate); oldEntries = mCachedEntries.removeAll(predicate); makeOrphans(oldEntries); } maybeClose(oldEntries); maybeNotifyExclusiveEntryRemoval(oldExclusives); maybeUpdateCacheParams(); maybeEvictEntries(); return oldEntries.size(); } /** Removes all the items from the cache. */ public void clear() { ArrayList> oldExclusives; ArrayList> oldEntries; synchronized (this) { oldExclusives = mExclusiveEntries.clear(); oldEntries = mCachedEntries.clear(); makeOrphans(oldEntries); } maybeClose(oldEntries); maybeNotifyExclusiveEntryRemoval(oldExclusives); maybeUpdateCacheParams(); } /** * Check if any items from the cache whose key matches the specified predicate. * * @param predicate returns true if an item with the given key matches * @return true is any items matches from the cache */ @Override public synchronized boolean contains(Predicate predicate) { return !mCachedEntries.getMatchingEntries(predicate).isEmpty(); } /** Trims the cache according to the specified trimming strategy and the given trim type. */ @Override public void trim(MemoryTrimType trimType) { ArrayList> oldEntries; final double trimRatio = mCacheTrimStrategy.getTrimRatio(trimType); synchronized (this) { int targetCacheSize = (int) (mCachedEntries.getSizeInBytes() * (1 - trimRatio)); int targetEvictionQueueSize = Math.max(0, targetCacheSize - getInUseSizeInBytes()); oldEntries = trimExclusivelyOwnedEntries(Integer.MAX_VALUE, targetEvictionQueueSize); makeOrphans(oldEntries); } maybeClose(oldEntries); maybeNotifyExclusiveEntryRemoval(oldEntries); maybeUpdateCacheParams(); maybeEvictEntries(); } /** * Updates the cache params (constraints) if enough time has passed since the last update. */ private synchronized void maybeUpdateCacheParams() { if (mLastCacheParamsCheck + PARAMS_INTERCHECK_INTERVAL_MS > SystemClock.uptimeMillis()) { return; } mLastCacheParamsCheck = SystemClock.uptimeMillis(); mMemoryCacheParams = mMemoryCacheParamsSupplier.get(); } /** * Removes the exclusively owned items until the cache constraints are met. * *

This method invokes the external {@link CloseableReference#close} method, * so it must not be called while holding the this lock. */ private void maybeEvictEntries() { ArrayList> oldEntries; synchronized (this) { int maxCount = Math.min( mMemoryCacheParams.maxEvictionQueueEntries, mMemoryCacheParams.maxCacheEntries - getInUseCount()); int maxSize = Math.min( mMemoryCacheParams.maxEvictionQueueSize, mMemoryCacheParams.maxCacheSize - getInUseSizeInBytes()); oldEntries = trimExclusivelyOwnedEntries(maxCount, maxSize); if (oldEntries != null) { Log.d("gaorui", "oldEntries no memory"); } makeOrphans(oldEntries); } maybeClose(oldEntries); maybeNotifyExclusiveEntryRemoval(oldEntries); } /** * Removes the exclusively owned items until there is at most count of them * and they occupy no more than size bytes. * *

This method returns the removed items instead of actually closing them, so it is safe to * be called while holding the this lock. */ @Nullable private synchronized ArrayList> trimExclusivelyOwnedEntries(int count, int size) { count = Math.max(count, 0); size = Math.max(size, 0); // fast path without array allocation if no eviction is necessary if (mExclusiveEntries.getCount() <= count && mExclusiveEntries.getSizeInBytes() <= size) { return null; } ArrayList> oldEntries = new ArrayList<>(); while (mExclusiveEntries.getCount() > count || mExclusiveEntries.getSizeInBytes() > size) { K key = mExclusiveEntries.getFirstKey(); mExclusiveEntries.remove(key); oldEntries.add(mCachedEntries.remove(key)); } return oldEntries; } /** * Notifies the client that the cache no longer tracks the given items. * *

This method invokes the external {@link CloseableReference#close} method, * so it must not be called while holding the this lock. */ private void maybeClose(@Nullable ArrayList> oldEntries) { if (oldEntries != null) { for (Entry oldEntry : oldEntries) { CloseableReference.closeSafely(referenceToClose(oldEntry)); } } } private void maybeNotifyExclusiveEntryRemoval(@Nullable ArrayList> entries) { if (entries != null) { for (Entry entry : entries) { maybeNotifyExclusiveEntryRemoval(entry); } } } private static void maybeNotifyExclusiveEntryRemoval(@Nullable Entry entry) { if (entry != null && entry.observer != null) { entry.observer.onExclusivityChanged(entry.key, false); } } private static void maybeNotifyExclusiveEntryInsertion(@Nullable Entry entry) { if (entry != null && entry.observer != null) { entry.observer.onExclusivityChanged(entry.key, true); } } /** Marks the given entries as orphans. */ private synchronized void makeOrphans(@Nullable ArrayList> oldEntries) { if (oldEntries != null) { for (Entry oldEntry : oldEntries) { makeOrphan(oldEntry); } } } /** Marks the entry as orphan. */ private synchronized void makeOrphan(Entry entry) { Preconditions.checkNotNull(entry); Preconditions.checkState(!entry.isOrphan); entry.isOrphan = true; } /** Increases the entry's client count. */ private synchronized void increaseClientCount(Entry entry) { Preconditions.checkNotNull(entry); Preconditions.checkState(!entry.isOrphan); entry.clientCount++; } /** Decreases the entry's client count. */ private synchronized void decreaseClientCount(Entry entry) { Preconditions.checkNotNull(entry); Preconditions.checkState(entry.clientCount > 0); entry.clientCount--; } /** Returns the value reference of the entry if it should be closed, null otherwise. */ @Nullable private synchronized CloseableReference referenceToClose(Entry entry) { Preconditions.checkNotNull(entry); return (entry.isOrphan && entry.clientCount == 0) ? entry.valueRef : null; } /** Gets the total number of all currently cached items. */ public synchronized int getCount() { return mCachedEntries.getCount(); } /** Gets the total size in bytes of all currently cached items. */ public synchronized int getSizeInBytes() { return mCachedEntries.getSizeInBytes(); } /** 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(); } /** Gets the number of the exclusively owned items. */ public synchronized int getEvictionQueueCount() { return mExclusiveEntries.getCount(); } /** Gets the total size in bytes of the exclusively owned items. */ public synchronized int getEvictionQueueSizeInBytes() { return mExclusiveEntries.getSizeInBytes(); } }


       fresco定义了MemoryTrimmable和DiskTrimmable接口类, 用于在app内存不足时回收内存。fresco已经帮实现了函数体, 只要在适当的地方调用一下就可以了。  但奇怪的是搜索fresco源码找不到在哪里调用trim方法!!!  网上有人说用fresco遇到了OOM的问题, 我觉得跟没调用trim方法有关。

        从github的issue看出大部分OOM问题的解决方式是setDownSampleEnabled(true)或android:largeHeap="true"。 当手机内存低时应该释放一些资源, 包括引用计数不为0的图片, 即最终执行CountingMemoryCache类的trim方法。

我提了issue: https://github.com/facebook/fresco/issues/1455  和 https://github.com/facebook/fresco/issues/1457 ,  关键在于如何拿到图片缓存对象实例。

        issue里已经有我的解决方案了,   为了帮助英文不熟的同学,在这里再翻译一下

1. 声明一个静态变量,  用于缓存内存对象。
private static ArrayList sMemoryTrimmable;
public static ArrayList getMemoryTrimmable() {
return sMemoryTrimmable;
}
2.  在调用setMemoryTrimmableRegistry函数时, 在registerMemoryTrimmable里将MemoryTrimmable对象保存到静态对象里。
public static ImagePipelineConfig getImagePipelineConfig(Context context) {
      if (sImagePipelineConfig == null) {
            ....
          sMemoryTrimmable = new ArrayList<>();
          configBuilder.setMemoryTrimmableRegistry(new MemoryTrimmableRegistry() {
                @Override
                public void registerMemoryTrimmable(MemoryTrimmable trimmable) {
                       sMemoryTrimmable.add(trimmable);
                       Log.d("brycegao", "registerMemoryTrimmable size: " + sMemoryTrimmable.size());
                 }
            ......
3.  覆盖Application的onTrimMemory方法或者Activity的onTrimMemory的方法,  遍历静态变量并调用trim方法。 注意参数不同, fresco释放的内存大小不同!
@Override
public void onTrimMemory(int level) {
       super.onTrimMemory(level);
      ArrayList array = ImagePipelineConfigFactory.getMemoryTrimmable();
      if (array != null) {
           for (MemoryTrimmable trimmable:array) {
           //just demo, it should be proper params according to level.
           trimmable.trim(MemoryTrimType.OnSystemLowMemoryWhileAppInBackground);
       }
    }
}

09-07 09:57:15.088 2235-2235/com.facebook.samples.comparison D/brycegao: registerMemoryTrimmable size: 4

实际测试验证, fresco默认有4个MemoryTrimmable对象。


CountingMemoryCache.java:

public void trim(MemoryTrimType trimType) {
    ArrayList> oldEntries;
    final double trimRatio = mCacheTrimStrategy.getTrimRatio(trimType);
    synchronized (this) {
      int targetCacheSize = (int) (mCachedEntries.getSizeInBytes() * (1 - trimRatio));
      int targetEvictionQueueSize = Math.max(0, targetCacheSize - getInUseSizeInBytes());
      oldEntries = trimExclusivelyOwnedEntries(Integer.MAX_VALUE, targetEvictionQueueSize);
      makeOrphans(oldEntries);
    }
    ....
  }

BasePool.java :

public void trim(MemoryTrimType memoryTrimType) {
  trimToNothing();
}
SharedByteArray.java:

public void trim(MemoryTrimType trimType) {
  if (!mSemaphore.tryAcquire()) {
    return;
  }
  try {
    mByteArraySoftRef.clear();
  } finally {
    mSemaphore.release();
  }
}

你可能感兴趣的:(Java,Android)