Home | History | Annotate | Download | only in imagepool
      1 /*
      2  * Copyright (C) 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.util.imagepool;
     18 
     19 import com.android.tools.layoutlib.annotations.Nullable;
     20 import com.android.tools.layoutlib.annotations.VisibleForTesting;
     21 
     22 import android.util.imagepool.Bucket.BucketCreationMetaData;
     23 import android.util.imagepool.ImagePool.Image.Orientation;
     24 
     25 import java.awt.image.BufferedImage;
     26 import java.awt.image.DataBufferInt;
     27 import java.lang.ref.Reference;
     28 import java.util.Arrays;
     29 import java.util.HashMap;
     30 import java.util.HashSet;
     31 import java.util.Map;
     32 import java.util.Set;
     33 import java.util.concurrent.locks.ReentrantReadWriteLock;
     34 import java.util.function.Consumer;
     35 
     36 import com.google.common.base.FinalizablePhantomReference;
     37 import com.google.common.base.FinalizableReferenceQueue;
     38 
     39 class ImagePoolImpl implements ImagePool {
     40 
     41     private final ReentrantReadWriteLock mReentrantLock = new ReentrantReadWriteLock();
     42     private final ImagePoolPolicy mPolicy;
     43     @VisibleForTesting final Map<String, Bucket> mPool = new HashMap<>();
     44     @VisibleForTesting final ImagePoolStats mImagePoolStats = new ImagePoolStatsProdImpl();
     45     private final FinalizableReferenceQueue mFinalizableReferenceQueue = new FinalizableReferenceQueue();
     46     private final Set<Reference<?>> mReferences = new HashSet<>();
     47 
     48     public ImagePoolImpl(ImagePoolPolicy policy) {
     49         mPolicy = policy;
     50         mImagePoolStats.start();
     51     }
     52 
     53     @Override
     54     public Image acquire(int w, int h, int type) {
     55         return acquire(w, h, type, null);
     56     }
     57 
     58     /* package private */ Image acquire(int w, int h, int type,
     59             @Nullable Consumer<BufferedImage> freedCallback) {
     60         mReentrantLock.writeLock().lock();
     61         try {
     62             BucketCreationMetaData metaData =
     63                     ImagePoolHelper.getBucketCreationMetaData(w, h, type, mPolicy, mImagePoolStats);
     64             if (metaData == null) {
     65                 return defaultImageImpl(w, h, type, freedCallback);
     66             }
     67 
     68             final Bucket existingBucket = ImagePoolHelper.getBucket(mPool, metaData, mPolicy);
     69             final BufferedImage img =
     70                     ImagePoolHelper.getBufferedImage(existingBucket, metaData, mImagePoolStats);
     71             if (img == null) {
     72                 return defaultImageImpl(w, h, type, freedCallback);
     73             }
     74 
     75             // Clear the image. - is this necessary?
     76             if (img.getRaster().getDataBuffer().getDataType() == java.awt.image.DataBuffer.TYPE_INT) {
     77                 Arrays.fill(((DataBufferInt)img.getRaster().getDataBuffer()).getData(), 0);
     78             }
     79 
     80             return prepareImage(
     81                     new ImageImpl(w, h, img, metaData.mOrientation),
     82                     true,
     83                     img,
     84                     existingBucket,
     85                     freedCallback);
     86         } finally {
     87             mReentrantLock.writeLock().unlock();
     88         }
     89     }
     90 
     91     /**
     92      * Add statistics as well as dispose behaviour before returning image.
     93      */
     94     private Image prepareImage(
     95             Image image,
     96             boolean offerBackToBucket,
     97             @Nullable BufferedImage img,
     98             @Nullable Bucket existingBucket,
     99             @Nullable Consumer<BufferedImage> freedCallback) {
    100         final Integer imageHash = image.hashCode();
    101         mImagePoolStats.acquiredImage(imageHash);
    102         FinalizablePhantomReference<Image> reference =
    103                 new FinalizablePhantomReference<ImagePool.Image>(image, mFinalizableReferenceQueue) {
    104                     @Override
    105                     public void finalizeReferent() {
    106                         // This method might be called twice if the user has manually called the free() method. The second call will have no effect.
    107                         if (mReferences.remove(this)) {
    108                             mImagePoolStats.disposeImage(imageHash);
    109                             if (offerBackToBucket) {
    110                                 if (!mImagePoolStats.fitsMaxCacheSize(img.getWidth(), img.getHeight(),
    111                                         mPolicy.mBucketMaxCacheSize)) {
    112                                     mImagePoolStats.tooBigForCache();
    113                                     // Adding this back would go over the max cache size we set for ourselves. Release it.
    114                                     return;
    115                                 }
    116 
    117                                 // else stat does not change.
    118                                 existingBucket.offer(img);
    119                             }
    120                             if (freedCallback != null) {
    121                                 freedCallback.accept(img);
    122                             }
    123                         }
    124                     }
    125                 };
    126         mReferences.add(reference);
    127         return image;
    128     }
    129 
    130     /**
    131      * Default Image Impl to be used when the pool is not big enough.
    132      */
    133     private Image defaultImageImpl(int w, int h, int type,
    134             @Nullable Consumer<BufferedImage> freedCallback) {
    135         BufferedImage bufferedImage = new BufferedImage(w, h, type);
    136         mImagePoolStats.tooBigForCache();
    137         mImagePoolStats.recordAllocOutsidePool(w, h);
    138         return prepareImage(new ImageImpl(w, h, bufferedImage, Orientation.NONE),
    139                 false,  null, null, freedCallback);
    140     }
    141 
    142     @Override
    143     public void dispose() {
    144         mReentrantLock.writeLock().lock();
    145         try {
    146             for (Bucket bucket : mPool.values()) {
    147                 bucket.clear();
    148             }
    149             mImagePoolStats.clear();
    150         } finally {
    151             mReentrantLock.writeLock().unlock();
    152         }
    153     }
    154 
    155     /* package private */ void printStat() {
    156         System.out.println(mImagePoolStats.getStatistic());
    157     }
    158 }