blob: 3da706ec1aa725812b56bb96822822910c90d96c [file] [log] [blame]
/*
* Copyright (C) 2018 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 android.util.imagepool;
import com.android.tools.layoutlib.annotations.Nullable;
import com.android.tools.layoutlib.annotations.VisibleForTesting;
import android.util.imagepool.Bucket.BucketCreationMetaData;
import android.util.imagepool.ImagePool.Image.Orientation;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.lang.ref.Reference;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
import com.google.common.base.FinalizablePhantomReference;
import com.google.common.base.FinalizableReferenceQueue;
class ImagePoolImpl implements ImagePool {
private final ReentrantReadWriteLock mReentrantLock = new ReentrantReadWriteLock();
private final ImagePoolPolicy mPolicy;
@VisibleForTesting final Map<String, Bucket> mPool = new HashMap<>();
@VisibleForTesting final ImagePoolStats mImagePoolStats = new ImagePoolStatsProdImpl();
private final FinalizableReferenceQueue mFinalizableReferenceQueue = new FinalizableReferenceQueue();
private final Set<Reference<?>> mReferences = new HashSet<>();
public ImagePoolImpl(ImagePoolPolicy policy) {
mPolicy = policy;
mImagePoolStats.start();
}
@Override
public Image acquire(int w, int h, int type) {
return acquire(w, h, type, null);
}
/* package private */ Image acquire(int w, int h, int type,
@Nullable Consumer<BufferedImage> freedCallback) {
mReentrantLock.writeLock().lock();
try {
BucketCreationMetaData metaData =
ImagePoolHelper.getBucketCreationMetaData(w, h, type, mPolicy, mImagePoolStats);
if (metaData == null) {
return defaultImageImpl(w, h, type, freedCallback);
}
final Bucket existingBucket = ImagePoolHelper.getBucket(mPool, metaData, mPolicy);
final BufferedImage img =
ImagePoolHelper.getBufferedImage(existingBucket, metaData, mImagePoolStats);
if (img == null) {
return defaultImageImpl(w, h, type, freedCallback);
}
// Clear the image. - is this necessary?
if (img.getRaster().getDataBuffer().getDataType() == java.awt.image.DataBuffer.TYPE_INT) {
Arrays.fill(((DataBufferInt)img.getRaster().getDataBuffer()).getData(), 0);
}
return prepareImage(
new ImageImpl(w, h, img, metaData.mOrientation),
true,
img,
existingBucket,
freedCallback);
} finally {
mReentrantLock.writeLock().unlock();
}
}
/**
* Add statistics as well as dispose behaviour before returning image.
*/
private Image prepareImage(
Image image,
boolean offerBackToBucket,
@Nullable BufferedImage img,
@Nullable Bucket existingBucket,
@Nullable Consumer<BufferedImage> freedCallback) {
final Integer imageHash = image.hashCode();
mImagePoolStats.acquiredImage(imageHash);
FinalizablePhantomReference<Image> reference =
new FinalizablePhantomReference<ImagePool.Image>(image, mFinalizableReferenceQueue) {
@Override
public void finalizeReferent() {
// This method might be called twice if the user has manually called the free() method. The second call will have no effect.
if (mReferences.remove(this)) {
mImagePoolStats.disposeImage(imageHash);
if (offerBackToBucket) {
if (!mImagePoolStats.fitsMaxCacheSize(img.getWidth(), img.getHeight(),
mPolicy.mBucketMaxCacheSize)) {
mImagePoolStats.tooBigForCache();
// Adding this back would go over the max cache size we set for ourselves. Release it.
return;
}
// else stat does not change.
existingBucket.offer(img);
}
if (freedCallback != null) {
freedCallback.accept(img);
}
}
}
};
mReferences.add(reference);
return image;
}
/**
* Default Image Impl to be used when the pool is not big enough.
*/
private Image defaultImageImpl(int w, int h, int type,
@Nullable Consumer<BufferedImage> freedCallback) {
BufferedImage bufferedImage = new BufferedImage(w, h, type);
mImagePoolStats.tooBigForCache();
mImagePoolStats.recordAllocOutsidePool(w, h);
return prepareImage(new ImageImpl(w, h, bufferedImage, Orientation.NONE),
false, null, null, freedCallback);
}
@Override
public void dispose() {
mReentrantLock.writeLock().lock();
try {
for (Bucket bucket : mPool.values()) {
bucket.clear();
}
mImagePoolStats.clear();
} finally {
mReentrantLock.writeLock().unlock();
}
}
/* package private */ void printStat() {
System.out.println(mImagePoolStats.getStatistic());
}
}