/*
* 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.memory;
import javax.annotation.concurrent.GuardedBy;
import java.util.ArrayList;
import java.util.List;
import android.graphics.Bitmap;
import android.os.Build;
import com.facebook.common.internal.Preconditions;
import com.facebook.common.internal.Throwables;
import com.facebook.common.references.CloseableReference;
import com.facebook.common.references.ResourceReleaser;
import com.facebook.imagepipeline.common.TooManyBitmapsException;
import com.facebook.imagepipeline.nativecode.Bitmaps;
import com.facebook.imageutils.BitmapUtil;
/**
* Counts bitmaps - keeps track of both, count and total size in bytes.
*/
public class BitmapCounter {
@GuardedBy("this")
private int mCount;
@GuardedBy("this")
private long mSize;
private final int mMaxCount;
private final int mMaxSize;
private final ResourceReleaser<Bitmap> mUnpooledBitmapsReleaser;
public BitmapCounter(int maxCount, int maxSize) {
Preconditions.checkArgument(maxCount > 0);
Preconditions.checkArgument(maxSize > 0);
mMaxCount = maxCount;
mMaxSize = maxSize;
mUnpooledBitmapsReleaser = new ResourceReleaser<Bitmap>() {
@Override
public void release(Bitmap value) {
try {
decrease(value);
} finally {
value.recycle();
}
}
};
}
/**
* Includes given bitmap in the bitmap count. The bitmap is included only if doing so
* does not violate configured limit
*
* @param bitmap to include in the count
* @return true if and only if bitmap is successfully included in the count
*/
public synchronized boolean increase(Bitmap bitmap) {
final int bitmapSize = BitmapUtil.getSizeInBytes(bitmap);
if (mCount >= mMaxCount || mSize + bitmapSize > mMaxSize) {
return false;
}
mCount++;
mSize += bitmapSize;
return true;
}
/**
* Excludes given bitmap from the count.
*
* @param bitmap to be excluded from the count
*/
public synchronized void decrease(Bitmap bitmap) {
final int bitmapSize = BitmapUtil.getSizeInBytes(bitmap);
Preconditions.checkArgument(mCount > 0, "No bitmaps registered.");
Preconditions.checkArgument(
bitmapSize <= mSize,
"Bitmap size bigger than the total registered size: %d, %d",
bitmapSize,
mSize);
mSize -= bitmapSize;
mCount--;
}
/**
* @return number of counted bitmaps
*/
public synchronized int getCount() {
return mCount;
}
/**
* @return total size in bytes of counted bitmaps
*/
public synchronized long getSize() {
return mSize;
}
public ResourceReleaser<Bitmap> getReleaser() {
return mUnpooledBitmapsReleaser;
}
/**
* Associates bitmaps with the bitmap counter. <p/> <p>If this method throws
* TooManyBitmapsException, the code will have called {@link Bitmap#recycle} on the
* bitmaps.</p>
*
* @param bitmaps the bitmaps to associate
* @return the references to the bitmaps that are now tied to the bitmap pool
* @throws TooManyBitmapsException if the pool is full
*/
public List<CloseableReference<Bitmap>> associateBitmapsWithBitmapCounter(
final List<Bitmap> bitmaps) {
int countedBitmaps = 0;
try {
for (; countedBitmaps < bitmaps.size(); ++countedBitmaps) {
final Bitmap bitmap = bitmaps.get(countedBitmaps);
// 'Pin' the bytes of the purgeable bitmap, so it is now not purgeable
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
Bitmaps.pinBitmap(bitmap);
}
if (!increase(bitmap)) {
throw new TooManyBitmapsException();
}
}
List<CloseableReference<Bitmap>> ret = new ArrayList<>();
for (Bitmap bitmap : bitmaps) {
ret.add(CloseableReference.of(bitmap, mUnpooledBitmapsReleaser));
}
return ret;
} catch (Exception exception) {
if (bitmaps != null) {
for (Bitmap bitmap : bitmaps) {
if (countedBitmaps-- > 0) {
decrease(bitmap);
}
bitmap.recycle();
}
}
throw Throwables.propagate(exception);
}
}
}