/* * 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.fresco.animation.bitmap.cache; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; import android.graphics.Bitmap; import android.util.SparseArray; import com.facebook.common.internal.Preconditions; import com.facebook.common.internal.VisibleForTesting; import com.facebook.common.logging.FLog; import com.facebook.common.references.CloseableReference; import com.facebook.fresco.animation.bitmap.BitmapAnimationBackend; import com.facebook.fresco.animation.bitmap.BitmapFrameCache; import com.facebook.imagepipeline.animated.impl.AnimatedFrameCache; import com.facebook.imagepipeline.image.CloseableBitmap; import com.facebook.imagepipeline.image.CloseableImage; import com.facebook.imagepipeline.image.CloseableStaticBitmap; import com.facebook.imagepipeline.image.ImmutableQualityInfo; import com.facebook.imageutils.BitmapUtil; /** * Bitmap frame cache that uses Fresco's {@link AnimatedFrameCache} to cache frames. */ public class FrescoFrameCache implements BitmapFrameCache { private static final Class<?> TAG = FrescoFrameCache.class; private final AnimatedFrameCache mAnimatedFrameCache; private final boolean mEnableBitmapReusing; @GuardedBy("this") private final SparseArray<CloseableReference<CloseableImage>> mPreparedPendingFrames; @GuardedBy("this") @Nullable private CloseableReference<CloseableImage> mLastRenderedItem; public FrescoFrameCache(AnimatedFrameCache animatedFrameCache, boolean enableBitmapReusing) { mAnimatedFrameCache = animatedFrameCache; mEnableBitmapReusing = enableBitmapReusing; mPreparedPendingFrames = new SparseArray<>(); } @Nullable @Override public synchronized CloseableReference<Bitmap> getCachedFrame(int frameNumber) { return convertToBitmapReferenceAndClose(mAnimatedFrameCache.get(frameNumber)); } @Nullable @Override public synchronized CloseableReference<Bitmap> getFallbackFrame(int frameNumber) { return convertToBitmapReferenceAndClose(CloseableReference.cloneOrNull(mLastRenderedItem)); } @Nullable @Override public synchronized CloseableReference<Bitmap> getBitmapToReuseForFrame( int frameNumber, int width, int height) { if (!mEnableBitmapReusing) { return null; } return convertToBitmapReferenceAndClose(mAnimatedFrameCache.getForReuse()); } @Override public synchronized boolean contains(int frameNumber) { return mAnimatedFrameCache.contains(frameNumber); } @Override public synchronized int getSizeInBytes() { // This currently does not include the size of the animated frame cache return getBitmapSizeBytes(mLastRenderedItem) + getPreparedPendingFramesSizeBytes(); } @Override public synchronized void clear() { CloseableReference.closeSafely(mLastRenderedItem); mLastRenderedItem = null; for (int i = 0; i < mPreparedPendingFrames.size(); i++) { CloseableReference.closeSafely(mPreparedPendingFrames.valueAt(i)); } mPreparedPendingFrames.clear(); // The frame cache will free items when needed } @Override public synchronized void onFrameRendered( int frameNumber, CloseableReference<Bitmap> bitmapReference, @BitmapAnimationBackend.FrameType int frameType) { Preconditions.checkNotNull(bitmapReference); // Close up prepared references. removePreparedReference(frameNumber); // Create the new image reference and cache it. CloseableReference<CloseableImage> closableReference = null; try { closableReference = createImageReference(bitmapReference); if (closableReference != null) { CloseableReference.closeSafely(mLastRenderedItem); mLastRenderedItem = mAnimatedFrameCache.cache(frameNumber, closableReference); } } finally { CloseableReference.closeSafely(closableReference); } } @Override public synchronized void onFramePrepared( int frameNumber, CloseableReference<Bitmap> bitmapReference, @BitmapAnimationBackend.FrameType int frameType) { Preconditions.checkNotNull(bitmapReference); CloseableReference<CloseableImage> closableReference = null; try { closableReference = createImageReference(bitmapReference); if (closableReference == null) { return; } CloseableReference<CloseableImage> newReference = mAnimatedFrameCache.cache(frameNumber, closableReference); if (CloseableReference.isValid(newReference)) { CloseableReference<CloseableImage> oldReference = mPreparedPendingFrames.get(frameNumber); CloseableReference.closeSafely(oldReference); // For performance reasons, we don't clone the reference and close the original one // but cache the reference directly. mPreparedPendingFrames.put(frameNumber, newReference); FLog.v( TAG, "cachePreparedFrame(%d) cached. Pending frames: %s", frameNumber, mPreparedPendingFrames); } } finally { CloseableReference.closeSafely(closableReference); } } @Override public void setFrameCacheListener(FrameCacheListener frameCacheListener) { // TODO (t15557326) Not supported for now } private synchronized int getPreparedPendingFramesSizeBytes() { int size = 0; for (int i = 0; i < mPreparedPendingFrames.size(); i++) { size += getBitmapSizeBytes(mPreparedPendingFrames.valueAt(i)); } return size; } private synchronized void removePreparedReference(int frameNumber) { CloseableReference<CloseableImage> existingPendingReference = mPreparedPendingFrames.get(frameNumber); if (existingPendingReference != null) { mPreparedPendingFrames.delete(frameNumber); CloseableReference.closeSafely(existingPendingReference); FLog.v( TAG, "removePreparedReference(%d) removed. Pending frames: %s", frameNumber, mPreparedPendingFrames); } } /** * Converts the given image reference to a bitmap reference * and closes the original image reference. * * @param closeableImage the image to convert. It will be closed afterwards and will be invalid * @return the closeable bitmap reference to be used */ @VisibleForTesting @Nullable static CloseableReference<Bitmap> convertToBitmapReferenceAndClose( final @Nullable CloseableReference<CloseableImage> closeableImage) { try { if (CloseableReference.isValid(closeableImage) && closeableImage.get() instanceof CloseableStaticBitmap) { CloseableStaticBitmap closeableStaticBitmap = (CloseableStaticBitmap) closeableImage.get(); if (closeableStaticBitmap != null) { // We return a clone of the underlying bitmap reference that has to be manually closed // and then close the passed CloseableStaticBitmap in order to preserve correct // cache size calculations. return closeableStaticBitmap.cloneUnderlyingBitmapReference(); } } // Not a bitmap reference, so we return null return null; } finally { CloseableReference.closeSafely(closeableImage); } } private static int getBitmapSizeBytes( @Nullable CloseableReference<CloseableImage> imageReference) { if (!CloseableReference.isValid(imageReference)) { return 0; } return getBitmapSizeBytes(imageReference.get()); } private static int getBitmapSizeBytes(@Nullable CloseableImage image) { if (!(image instanceof CloseableBitmap)) { return 0; } return BitmapUtil.getSizeInBytes(((CloseableBitmap) image).getUnderlyingBitmap()); } @Nullable private static CloseableReference<CloseableImage> createImageReference( CloseableReference<Bitmap> bitmapReference) { // The given CloseableStaticBitmap will be cached and then released by the resource releaser // of the closeable reference CloseableImage closeableImage = new CloseableStaticBitmap( bitmapReference, ImmutableQualityInfo.FULL_QUALITY, 0); return CloseableReference.of(closeableImage); } }