/* * Copyright (c) 2017-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.preparation; import java.util.concurrent.ExecutorService; import android.graphics.Bitmap; import android.util.SparseArray; import com.facebook.common.logging.FLog; import com.facebook.common.references.CloseableReference; import com.facebook.fresco.animation.backend.AnimationBackend; import com.facebook.fresco.animation.bitmap.BitmapAnimationBackend; import com.facebook.fresco.animation.bitmap.BitmapFrameCache; import com.facebook.fresco.animation.bitmap.BitmapFrameRenderer; import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory; /** * Default bitmap frame preparer that uses the given {@link ExecutorService} to schedule jobs. * An instance of this class can be shared between multiple animated images. */ public class DefaultBitmapFramePreparer implements BitmapFramePreparer { private static final Class<?> TAG = DefaultBitmapFramePreparer.class; private final PlatformBitmapFactory mPlatformBitmapFactory; private final BitmapFrameRenderer mBitmapFrameRenderer; private final Bitmap.Config mBitmapConfig; private final ExecutorService mExecutorService; private final SparseArray<Runnable> mPendingFrameDecodeJobs; public DefaultBitmapFramePreparer( PlatformBitmapFactory platformBitmapFactory, BitmapFrameRenderer bitmapFrameRenderer, Bitmap.Config bitmapConfig, ExecutorService executorService) { mPlatformBitmapFactory = platformBitmapFactory; mBitmapFrameRenderer = bitmapFrameRenderer; mBitmapConfig = bitmapConfig; mExecutorService = executorService; mPendingFrameDecodeJobs = new SparseArray<>(); } @Override public boolean prepareFrame( BitmapFrameCache bitmapFrameCache, AnimationBackend animationBackend, int frameNumber) { // Create a unique ID to identify the frame for the given backend. int frameId = getUniqueId(animationBackend, frameNumber); synchronized (mPendingFrameDecodeJobs) { // Check if already scheduled. if (mPendingFrameDecodeJobs.get(frameId) != null) { FLog.v(TAG, "Already scheduled decode job for frame %d", frameNumber); return true; } // Check if already cached. if (bitmapFrameCache.contains(frameNumber)) { FLog.v(TAG, "Frame %d is cached already.", frameNumber); return true; } Runnable frameDecodeRunnable = new FrameDecodeRunnable( animationBackend, bitmapFrameCache, frameNumber, frameId); mPendingFrameDecodeJobs.put(frameId, frameDecodeRunnable); mExecutorService.execute(frameDecodeRunnable); } return true; } private static int getUniqueId(AnimationBackend backend, int frameNumber) { int result = backend.hashCode(); result = 31 * result + frameNumber; return result; } private class FrameDecodeRunnable implements Runnable { private final BitmapFrameCache mBitmapFrameCache; private final AnimationBackend mAnimationBackend; private final int mFrameNumber; private final int mHashCode; public FrameDecodeRunnable( AnimationBackend animationBackend, BitmapFrameCache bitmapFrameCache, int frameNumber, int hashCode) { mAnimationBackend = animationBackend; mBitmapFrameCache = bitmapFrameCache; mFrameNumber = frameNumber; mHashCode = hashCode; } @Override public void run() { try { // If we have a cached frame already, we don't need to do anything. if (mBitmapFrameCache.contains(mFrameNumber)) { FLog.v(TAG, "Frame %d is cached already.", mFrameNumber); return; } // Prepare the frame. if (prepareFrameAndCache(mFrameNumber, BitmapAnimationBackend.FRAME_TYPE_REUSED)) { FLog.v(TAG, "Prepared frame frame %d.", mFrameNumber); } else { FLog.e(TAG, "Could not prepare frame %d.", mFrameNumber); } } finally { synchronized (mPendingFrameDecodeJobs) { mPendingFrameDecodeJobs.remove(mHashCode); } } } private boolean prepareFrameAndCache( int frameNumber, @BitmapAnimationBackend.FrameType int frameType) { CloseableReference<Bitmap> bitmapReference = null; boolean created; int nextFrameType; try { switch (frameType) { case BitmapAnimationBackend.FRAME_TYPE_REUSED: bitmapReference = mBitmapFrameCache.getBitmapToReuseForFrame( frameNumber, mAnimationBackend.getIntrinsicWidth(), mAnimationBackend.getIntrinsicHeight()); nextFrameType = BitmapAnimationBackend.FRAME_TYPE_CREATED; break; case BitmapAnimationBackend.FRAME_TYPE_CREATED: bitmapReference = mPlatformBitmapFactory.createBitmap( mAnimationBackend.getIntrinsicWidth(), mAnimationBackend.getIntrinsicHeight(), mBitmapConfig); nextFrameType = BitmapAnimationBackend.FRAME_TYPE_UNKNOWN; break; default: return false; } // Try to render and cache the frame created = renderFrameAndCache(frameNumber, bitmapReference, frameType); } finally { CloseableReference.closeSafely(bitmapReference); } if (created || nextFrameType == BitmapAnimationBackend.FRAME_TYPE_UNKNOWN) { return created; } else { return prepareFrameAndCache(frameNumber, nextFrameType); } } private boolean renderFrameAndCache( int frameNumber, CloseableReference<Bitmap> bitmapReference, @BitmapAnimationBackend.FrameType int frameType) { // Check if the bitmap is valid if (!CloseableReference.isValid(bitmapReference)) { return false; } // Try to render the frame if (!mBitmapFrameRenderer.renderFrame(frameNumber, bitmapReference.get())) { return false; } FLog.v(TAG, "Frame %d ready.", mFrameNumber); // Cache the frame synchronized (mPendingFrameDecodeJobs) { mBitmapFrameCache.onFramePrepared(mFrameNumber, bitmapReference, frameType); } return true; } } }