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 ImagePoolImpl(ImagePoolPolicy policy)48 public ImagePoolImpl(ImagePoolPolicy policy) { 49 mPolicy = policy; 50 mImagePoolStats.start(); 51 } 52 53 @Override acquire(int w, int h, int type)54 public Image acquire(int w, int h, int type) { 55 return acquire(w, h, type, null); 56 } 57 acquire(int w, int h, int type, @Nullable Consumer<BufferedImage> freedCallback)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 */ prepareImage( Image image, boolean offerBackToBucket, @Nullable BufferedImage img, @Nullable Bucket existingBucket, @Nullable Consumer<BufferedImage> freedCallback)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 */ defaultImageImpl(int w, int h, int type, @Nullable Consumer<BufferedImage> freedCallback)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 dispose()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 printStat()155 /* package private */ void printStat() { 156 System.out.println(mImagePoolStats.getStatistic()); 157 } 158 }