fresco源码分析-内存回收

本文深入探讨了Fresco的内存管理,包括通过LruCache的引用计数策略进行内存释放,以及在应用退到后台或手机低内存时如何主动回收内存。文章介绍了Fresco的内存结构、引用计数类SharedPrefs和CloseableReference的工作原理,并提供了在Android应用中触发内存回收的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

           我觉得内存管理是三方图库最重要的点, 而且该知识点能够应用到项目里, 所以着重看了一下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();


                                         qfyyziz.png

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

public class SharedReference<T> {

  // 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<Object, Integer> sLiveObjects = new IdentityHashMap<>();

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

  private final ResourceReleaser<T> 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<T> 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.
 *
 * <p> This layer is responsible for LRU eviction strategy and for maintaining the size boundaries
 * of the cached items.
 *
 * <p> Only the exclusively owned elements, i.e. the elements not referenced by any client, can be
 * evicted.
 *
 * @param <K> the key type
 * @param <V> the value type
 */
@ThreadSafe
public class CountingMemoryCache<K, V> implements MemoryCache<K, V>, 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<K> {

    /**
     * Called when the exclusivity status of the entry changes.
     *
     * <p> 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<K, V> {
    public final K key;
    public final CloseableReference<V> 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<K> observer;

    private Entry(K key, CloseableReference<V> valueRef, @Nullable EntryStateObserver<K> 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 <K, V> Entry<K, V> of(
        final K key,
        final CloseableReference<V> valueRef,
        final @Nullable EntryStateObserver<K> 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<K, Entry<K, V>> mExclusiveEntries;

  // Contains all the cached items including the exclusively owned ones.
  @GuardedBy("this")
  @VisibleForTesting
  final CountingLruMap<K, Entry<K, V>> mCachedEntries;

  private final ValueDescriptor<V> mValueDescriptor;

  private final CacheTrimStrategy mCacheTrimStrategy;

  // Cache size constraints.
  private final Supplier<MemoryCacheParams> mMemoryCacheParamsSupplier;
  @GuardedBy("this")
  protected MemoryCacheParams mMemoryCacheParams;
  @GuardedBy("this")
  private long mLastCacheParamsCheck;

  public CountingMemoryCache(
      ValueDescriptor<V> valueDescriptor,
      CacheTrimStrategy cacheTrimStrategy,
      Supplier<MemoryCacheParams> 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<Entry<K, V>> wrapValueDescriptor(
      final ValueDescriptor<V> evictableValueDescriptor) {
    return new ValueDescriptor<Entry<K,V>>() {
      @Override
      public int getSizeInBytes(Entry<K, V> entry) {
        return evictableValueDescriptor.getSizeInBytes(entry.valueRef.get());
      }
    };
  }

  /**
   * Caches the given key-value pair.
   *
   * <p> 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<V> cache(final K key, final CloseableReference<V> valueRef) {
    return cache(key, valueRef, null);
  }

  /**
   * Caches the given key-value pair.
   *
   * <p> 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<V> cache(
      final K key,
      final CloseableReference<V> valueRef,
      final EntryStateObserver<K> observer) {
    Preconditions.checkNotNull(key);
    Preconditions.checkNotNull(valueRef);

    maybeUpdateCacheParams();

    Entry<K, V> oldExclusive;
    CloseableReference<V> oldRefToClose = null;
    CloseableReference<V> clientRef = null;
    synchronized (this) {
      // remove the old item (if any) as it is stale now
      oldExclusive = mExclusiveEntries.remove(key);
      Entry<K, V> oldEntry = mCachedEntries.remove(key);
      if (oldEntry != null) {
        makeOrphan(oldEntry);
        oldRefToClose = referenceToClose(oldEntry);
      }

      if (canCacheNewValue(valueRef.get())) {
        Entry<K, V> 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.
   *
   * <p> It is the caller's responsibility to close the returned reference once not needed anymore.
   */
  @Nullable
  public CloseableReference<V> get(final K key) {
    Preconditions.checkNotNull(key);
    Entry<K, V> oldExclusive;
    CloseableReference<V> clientRef = null;
    synchronized (this) {
      oldExclusive = mExclusiveEntries.remove(key);
      Entry<K, V> 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<V> newClientReference(final Entry<K, V> entry) {
    increaseClientCount(entry);
    return CloseableReference.of(
        entry.valueRef.get(),
        new ResourceReleaser<V>() {
          @Override
          public void release(V unused) {
            releaseClientReference(entry);
          }
        });
  }

  /** Called when the client closes its reference. */
  private void releaseClientReference(final Entry<K, V> entry) {
    Preconditions.checkNotNull(entry);
    boolean isExclusiveAdded;
    CloseableReference<V> 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<K, V> 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.
   *
   * <p> The item can be reused only if it is exclusively owned by the cache.
   */
  @Nullable
  public CloseableReference<V> reuse(K key) {
    Preconditions.checkNotNull(key);
    CloseableReference<V> clientRef = null;
    boolean removed = false;
    Entry<K, V> oldExclusive = null;
    synchronized (this) {
      oldExclusive = mExclusiveEntries.remove(key);
      if (oldExclusive != null) {
        Entry<K, V> 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<K> predicate) {
    ArrayList<Entry<K, V>> oldExclusives;
    ArrayList<Entry<K, V>> 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<Entry<K, V>> oldExclusives;
    ArrayList<Entry<K, V>> 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<K> 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<Entry<K, V>> 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.
   *
   * <p> This method invokes the external {@link CloseableReference#close} method,
   * so it must not be called while holding the <code>this</code> lock.
   */
  private void maybeEvictEntries() {
    ArrayList<Entry<K, V>> 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 <code>count</code> of them
   * and they occupy no more than <code>size</code> bytes.
   *
   * <p> This method returns the removed items instead of actually closing them, so it is safe to
   * be called while holding the <code>this</code> lock.
   */
  @Nullable
  private synchronized ArrayList<Entry<K, V>> 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<Entry<K, V>> 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.
   *
   * <p> This method invokes the external {@link CloseableReference#close} method,
   * so it must not be called while holding the <code>this</code> lock.
   */
  private void maybeClose(@Nullable ArrayList<Entry<K, V>> oldEntries) {
    if (oldEntries != null) {
      for (Entry<K, V> oldEntry : oldEntries) {
        CloseableReference.closeSafely(referenceToClose(oldEntry));
      }
    }
  }

  private void maybeNotifyExclusiveEntryRemoval(@Nullable ArrayList<Entry<K, V>> entries) {
    if (entries != null) {
      for (Entry<K, V> entry : entries) {
        maybeNotifyExclusiveEntryRemoval(entry);
      }
    }
  }

  private static <K, V> void maybeNotifyExclusiveEntryRemoval(@Nullable Entry<K, V> entry) {
    if (entry != null && entry.observer != null) {
      entry.observer.onExclusivityChanged(entry.key, false);
    }
  }

  private static <K, V> void maybeNotifyExclusiveEntryInsertion(@Nullable Entry<K, V> 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<Entry<K, V>> oldEntries) {
    if (oldEntries != null) {
      for (Entry<K, V> oldEntry : oldEntries) {
        makeOrphan(oldEntry);
      }
    }
  }

  /** Marks the entry as orphan. */
  private synchronized void makeOrphan(Entry<K, V> entry) {
    Preconditions.checkNotNull(entry);
    Preconditions.checkState(!entry.isOrphan);
    entry.isOrphan = true;
  }

  /** Increases the entry's client count. */
  private synchronized void increaseClientCount(Entry<K, V> entry) {
    Preconditions.checkNotNull(entry);
    Preconditions.checkState(!entry.isOrphan);
    entry.clientCount++;
  }

  /** Decreases the entry's client count. */
  private synchronized void decreaseClientCount(Entry<K, V> 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<V> referenceToClose(Entry<K, V> 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<Entry<K, V>> oldEntries;
    <span style="color:#FF0000;">final double trimRatio = mCacheTrimStrategy.getTrimRatio(trimType);</span>
    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();
  }
}

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值