diff options
Diffstat (limited to 'src/com/android/messaging/datamodel/media/PoolableImageCache.java')
-rw-r--r-- | src/com/android/messaging/datamodel/media/PoolableImageCache.java | 419 |
1 files changed, 0 insertions, 419 deletions
diff --git a/src/com/android/messaging/datamodel/media/PoolableImageCache.java b/src/com/android/messaging/datamodel/media/PoolableImageCache.java deleted file mode 100644 index df814ba..0000000 --- a/src/com/android/messaging/datamodel/media/PoolableImageCache.java +++ /dev/null @@ -1,419 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.datamodel.media; - -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Color; -import android.os.SystemClock; -import android.support.annotation.NonNull; -import android.util.SparseArray; - -import com.android.messaging.Factory; -import com.android.messaging.util.Assert; -import com.android.messaging.util.LogUtil; - -import java.io.IOException; -import java.io.InputStream; -import java.util.LinkedList; - -/** - * A media cache that holds image resources, which doubles as a bitmap pool that allows the - * consumer to optionally decode image resources using unused bitmaps stored in the cache. - */ -public class PoolableImageCache extends MediaCache<ImageResource> { - private static final int MIN_TIME_IN_POOL = 5000; - - /** Encapsulates bitmap pool representation of the image cache */ - private final ReusableImageResourcePool mReusablePoolAccessor = new ReusableImageResourcePool(); - - public PoolableImageCache(final int id, final String name) { - this(DEFAULT_MEDIA_RESOURCE_CACHE_SIZE_IN_KILOBYTES, id, name); - } - - public PoolableImageCache(final int maxSize, final int id, final String name) { - super(maxSize, id, name); - } - - /** - * Creates a new BitmapFactory.Options for using the self-contained bitmap pool. - */ - public static BitmapFactory.Options getBitmapOptionsForPool(final boolean scaled, - final int inputDensity, final int targetDensity) { - final BitmapFactory.Options options = new BitmapFactory.Options(); - options.inScaled = scaled; - options.inDensity = inputDensity; - options.inTargetDensity = targetDensity; - options.inSampleSize = 1; - options.inJustDecodeBounds = false; - options.inMutable = true; - return options; - } - - @Override - public synchronized ImageResource addResourceToCache(final String key, - final ImageResource imageResource) { - mReusablePoolAccessor.onResourceEnterCache(imageResource); - return super.addResourceToCache(key, imageResource); - } - - @Override - protected synchronized void entryRemoved(final boolean evicted, final String key, - final ImageResource oldValue, final ImageResource newValue) { - mReusablePoolAccessor.onResourceLeaveCache(oldValue); - super.entryRemoved(evicted, key, oldValue, newValue); - } - - /** - * Returns a representation of the image cache as a reusable bitmap pool. - */ - public ReusableImageResourcePool asReusableBitmapPool() { - return mReusablePoolAccessor; - } - - /** - * A bitmap pool representation built on top of the image cache. It treats the image resources - * stored in the image cache as a self-contained bitmap pool and is able to create or - * reclaim bitmap resource as needed. - */ - public class ReusableImageResourcePool { - private static final int MAX_SUPPORTED_IMAGE_DIMENSION = 0xFFFF; - private static final int INVALID_POOL_KEY = 0; - - /** - * Number of reuse failures to skip before reporting. - * For debugging purposes, change to a lower number for more frequent reporting. - */ - private static final int FAILED_REPORTING_FREQUENCY = 100; - - /** - * Count of reuse failures which have occurred. - */ - private volatile int mFailedBitmapReuseCount = 0; - - /** - * Count of reuse successes which have occurred. - */ - private volatile int mSucceededBitmapReuseCount = 0; - - /** - * A sparse array from bitmap size to a list of image cache entries that match the - * given size. This map is used to quickly retrieve a usable bitmap to be reused by an - * incoming ImageRequest. We need to ensure that this sparse array always contains only - * elements currently in the image cache with no other consumer. - */ - private final SparseArray<LinkedList<ImageResource>> mImageListSparseArray; - - public ReusableImageResourcePool() { - mImageListSparseArray = new SparseArray<LinkedList<ImageResource>>(); - } - - /** - * Load an input stream into a bitmap. Uses a bitmap from the pool if possible to reduce - * memory turnover. - * @param inputStream InputStream load. Cannot be null. - * @param optionsTmp Should be the same options returned from getBitmapOptionsForPool(). - * Cannot be null. - * @param width The width of the bitmap. - * @param height The height of the bitmap. - * @return The decoded Bitmap with the resource drawn in it. - * @throws IOException - */ - public Bitmap decodeSampledBitmapFromInputStream(@NonNull final InputStream inputStream, - @NonNull final BitmapFactory.Options optionsTmp, - final int width, final int height) throws IOException { - if (width <= 0 || height <= 0) { - // This is an invalid / corrupted image of zero size. - LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "PoolableImageCache: Decoding bitmap with " + - "invalid size"); - throw new IOException("Invalid size / corrupted image"); - } - Assert.notNull(inputStream); - assignPoolBitmap(optionsTmp, width, height); - Bitmap b = null; - try { - b = BitmapFactory.decodeStream(inputStream, null, optionsTmp); - mSucceededBitmapReuseCount++; - } catch (final IllegalArgumentException e) { - // BitmapFactory couldn't decode the file, try again without an inputBufferBitmap. - if (optionsTmp.inBitmap != null) { - optionsTmp.inBitmap.recycle(); - optionsTmp.inBitmap = null; - b = BitmapFactory.decodeStream(inputStream, null, optionsTmp); - onFailedToReuse(); - } - } catch (final OutOfMemoryError e) { - LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "Oom decoding inputStream"); - Factory.get().reclaimMemory(); - } - return b; - } - - /** - * Turn encoded bytes into a bitmap. Uses a bitmap from the pool if possible to reduce - * memory turnover. - * @param bytes Encoded bytes to draw on the bitmap. Cannot be null. - * @param optionsTmp The bitmap will set here and the input should be generated from - * getBitmapOptionsForPool(). Cannot be null. - * @param width The width of the bitmap. - * @param height The height of the bitmap. - * @return A Bitmap with the encoded bytes drawn in it. - * @throws IOException - */ - public Bitmap decodeByteArray(@NonNull final byte[] bytes, - @NonNull final BitmapFactory.Options optionsTmp, final int width, - final int height) throws OutOfMemoryError, IOException { - if (width <= 0 || height <= 0) { - // This is an invalid / corrupted image of zero size. - LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "PoolableImageCache: Decoding bitmap with " + - "invalid size"); - throw new IOException("Invalid size / corrupted image"); - } - Assert.notNull(bytes); - Assert.notNull(optionsTmp); - assignPoolBitmap(optionsTmp, width, height); - Bitmap b = null; - try { - b = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, optionsTmp); - mSucceededBitmapReuseCount++; - } catch (final IllegalArgumentException e) { - // BitmapFactory couldn't decode the file, try again without an inputBufferBitmap. - // (i.e. without the bitmap from the pool) - if (optionsTmp.inBitmap != null) { - optionsTmp.inBitmap.recycle(); - optionsTmp.inBitmap = null; - b = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, optionsTmp); - onFailedToReuse(); - } - } catch (final OutOfMemoryError e) { - LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "Oom decoding inputStream"); - Factory.get().reclaimMemory(); - } - return b; - } - - /** - * Called when a new image resource is added to the cache. We add the resource to the - * pool so it's properly keyed into the pool structure. - */ - void onResourceEnterCache(final ImageResource imageResource) { - if (getPoolKey(imageResource) != INVALID_POOL_KEY) { - addResourceToPool(imageResource); - } - } - - /** - * Called when an image resource is evicted from the cache. Bitmap pool's entries are - * strictly tied to their presence in the image cache. Once an image is evicted from the - * cache, it should be removed from the pool. - */ - void onResourceLeaveCache(final ImageResource imageResource) { - if (getPoolKey(imageResource) != INVALID_POOL_KEY) { - removeResourceFromPool(imageResource); - } - } - - private void addResourceToPool(final ImageResource imageResource) { - synchronized (PoolableImageCache.this) { - final int poolKey = getPoolKey(imageResource); - Assert.isTrue(poolKey != INVALID_POOL_KEY); - LinkedList<ImageResource> imageList = mImageListSparseArray.get(poolKey); - if (imageList == null) { - imageList = new LinkedList<ImageResource>(); - mImageListSparseArray.put(poolKey, imageList); - } - imageList.addLast(imageResource); - } - } - - private void removeResourceFromPool(final ImageResource imageResource) { - synchronized (PoolableImageCache.this) { - final int poolKey = getPoolKey(imageResource); - Assert.isTrue(poolKey != INVALID_POOL_KEY); - final LinkedList<ImageResource> imageList = mImageListSparseArray.get(poolKey); - if (imageList != null) { - imageList.remove(imageResource); - } - } - } - - /** - * Try to get a reusable bitmap from the pool with the given width and height. As a - * result of this call, the caller will assume ownership of the returned bitmap. - */ - private Bitmap getReusableBitmapFromPool(final int width, final int height) { - synchronized (PoolableImageCache.this) { - final int poolKey = getPoolKey(width, height); - if (poolKey != INVALID_POOL_KEY) { - final LinkedList<ImageResource> images = mImageListSparseArray.get(poolKey); - if (images != null && images.size() > 0) { - // Try to reuse the first available bitmap from the pool list. We start from - // the least recently added cache entry of the given size. - ImageResource imageToUse = null; - for (int i = 0; i < images.size(); i++) { - final ImageResource image = images.get(i); - if (image.getRefCount() == 1) { - image.acquireLock(); - if (image.getRefCount() == 1) { - // The image is only used by the cache, so it's reusable. - imageToUse = images.remove(i); - break; - } else { - // Logically, this shouldn't happen, because as soon as the - // cache is the only user of this resource, it will not be - // used by anyone else until the next cache access, but we - // currently hold on to the cache lock. But technically - // future changes may violate this assumption, so warn about - // this. - LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "Image refCount changed " + - "from 1 in getReusableBitmapFromPool()"); - image.releaseLock(); - } - } - } - - if (imageToUse == null) { - return null; - } - - try { - imageToUse.assertLockHeldByCurrentThread(); - - // Only reuse the bitmap if the last time we use was greater than 5s. - // This allows the cache a chance to reuse instead of always taking the - // oldest. - final long timeSinceLastRef = SystemClock.elapsedRealtime() - - imageToUse.getLastRefAddTimestamp(); - if (timeSinceLastRef < MIN_TIME_IN_POOL) { - if (LogUtil.isLoggable(LogUtil.BUGLE_IMAGE_TAG, LogUtil.VERBOSE)) { - LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, "Not reusing reusing " + - "first available bitmap from the pool because it " + - "has not been in the pool long enough. " + - "timeSinceLastRef=" + timeSinceLastRef); - } - // Put back the image and return no reuseable bitmap. - images.addLast(imageToUse); - return null; - } - - // Add a temp ref on the image resource so it won't be GC'd after - // being removed from the cache. - imageToUse.addRef(); - - // Remove the image resource from the image cache. - final ImageResource removed = remove(imageToUse.getKey()); - Assert.isTrue(removed == imageToUse); - - // Try to reuse the bitmap from the image resource. This will transfer - // ownership of the bitmap object to the caller of this method. - final Bitmap reusableBitmap = imageToUse.reuseBitmap(); - - imageToUse.release(); - return reusableBitmap; - } finally { - // We are either done with the reuse operation, or decided not to use - // the image. Either way, release the lock. - imageToUse.releaseLock(); - } - } - } - } - return null; - } - - /** - * Try to locate and return a reusable bitmap from the pool, or create a new bitmap. - * @param width desired bitmap width - * @param height desired bitmap height - * @return the created or reused mutable bitmap that has its background cleared to - * {@value Color#TRANSPARENT} - */ - public Bitmap createOrReuseBitmap(final int width, final int height) { - return createOrReuseBitmap(width, height, Color.TRANSPARENT); - } - - /** - * Try to locate and return a reusable bitmap from the pool, or create a new bitmap. - * @param width desired bitmap width - * @param height desired bitmap height - * @param backgroundColor the background color for the returned bitmap - * @return the created or reused mutable bitmap with the requested background color - */ - public Bitmap createOrReuseBitmap(final int width, final int height, - final int backgroundColor) { - Bitmap retBitmap = null; - try { - final Bitmap poolBitmap = getReusableBitmapFromPool(width, height); - retBitmap = (poolBitmap != null) ? poolBitmap : - Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - retBitmap.eraseColor(backgroundColor); - } catch (final OutOfMemoryError e) { - LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "PoolableImageCache:try to createOrReuseBitmap"); - Factory.get().reclaimMemory(); - } - return retBitmap; - } - - private void assignPoolBitmap(final BitmapFactory.Options optionsTmp, final int width, - final int height) { - if (optionsTmp.inJustDecodeBounds) { - return; - } - optionsTmp.inBitmap = getReusableBitmapFromPool(width, height); - } - - /** - * @return The pool key for the provided image dimensions or 0 if either width or height is - * greater than the max supported image dimension. - */ - private int getPoolKey(final int width, final int height) { - if (width > MAX_SUPPORTED_IMAGE_DIMENSION || height > MAX_SUPPORTED_IMAGE_DIMENSION) { - return INVALID_POOL_KEY; - } - return (width << 16) | height; - } - - /** - * @return the pool key for a given image resource. - */ - private int getPoolKey(final ImageResource imageResource) { - if (imageResource.supportsBitmapReuse()) { - final Bitmap bitmap = imageResource.getBitmap(); - if (bitmap != null && bitmap.isMutable()) { - final int width = bitmap.getWidth(); - final int height = bitmap.getHeight(); - if (width > 0 && height > 0) { - return getPoolKey(width, height); - } - } - } - return INVALID_POOL_KEY; - } - - /** - * Called when bitmap reuse fails. Conditionally report the failure with statistics. - */ - private void onFailedToReuse() { - mFailedBitmapReuseCount++; - if (mFailedBitmapReuseCount % FAILED_REPORTING_FREQUENCY == 0) { - LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, - "Pooled bitmap consistently not being reused. Failure count = " + - mFailedBitmapReuseCount + ", success count = " + - mSucceededBitmapReuseCount); - } - } - } -} |