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官方提供的架構圖, 按照三級快取。 即已解碼的圖片,未解碼的圖片和檔案快取。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(); } }