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 org.junit.Test;
     20 
     21 import android.util.imagepool.ImagePool.Image;
     22 import android.util.imagepool.ImagePool.ImagePoolPolicy;
     23 
     24 import java.awt.image.BufferedImage;
     25 import java.lang.ref.SoftReference;
     26 import java.util.concurrent.CountDownLatch;
     27 import java.util.concurrent.TimeUnit;
     28 
     29 import static org.junit.Assert.assertEquals;
     30 import static org.junit.Assert.assertNotEquals;
     31 import static org.junit.Assert.assertNotNull;
     32 import static org.junit.Assert.assertTrue;
     33 
     34 public class ImagePoolImplTest {
     35 
     36     private static final long TIMEOUT_SEC = 3;
     37 
     38     @Test
     39     public void testImagePoolInstance() {
     40         ImagePool pool1 = ImagePoolProvider.get();
     41         ImagePool pool2 = ImagePoolProvider.get();
     42         assertNotNull(pool1);
     43         assertNotNull(pool2);
     44         assertEquals(pool1, pool2);
     45     }
     46 
     47 
     48     @Test
     49     public void testImageDispose() throws InterruptedException {
     50         int width = 700;
     51         int height = 800;
     52         int type = BufferedImage.TYPE_INT_ARGB;
     53         CountDownLatch countDownLatch = new CountDownLatch(1);
     54         ImagePoolImpl pool = getSimpleSingleBucketPool(width, height);
     55         Image img1 = pool.acquire(width, height, type,
     56                 bufferedImage -> countDownLatch.countDown());
     57         BufferedImage img = getImg(img1);
     58         assertNotNull(img);
     59         img1 = null;
     60 
     61         // ensure dispose actually loses buffered image link so it can be gc'd
     62         gc();
     63         assertTrue(countDownLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS));
     64     }
     65     @Test
     66     public void testImageDisposeFromFunction() throws InterruptedException {
     67         int width = 700;
     68         int height = 800;
     69         int type = BufferedImage.TYPE_INT_ARGB;
     70         CountDownLatch cd = new CountDownLatch(1);
     71         ImagePoolImpl pool = getSimpleSingleBucketPool(width, height);
     72 
     73         BufferedImage img = createImageAndReturnBufferedImage(pool, width, height, type, cd);
     74         assertNotNull(img);
     75 
     76         // ensure dispose actually loses buffered image link so it can be gc'd
     77         gc();
     78         assertTrue(cd.await(TIMEOUT_SEC, TimeUnit.SECONDS));
     79     }
     80 
     81     @Test
     82     public void testImageDisposedAndRecycled() throws InterruptedException {
     83         int width = 700;
     84         int height = 800;
     85         int bucketWidth = 800;
     86         int bucketHeight = 800;
     87         int variant = 1;
     88         int type = BufferedImage.TYPE_INT_ARGB;
     89         ImagePoolImpl pool = new ImagePoolImpl(new ImagePoolPolicy(
     90                 new int[]{bucketWidth, bucketHeight},
     91                 new int[]{1, 1},
     92                 bucketHeight * bucketWidth * 4 * 3));
     93 
     94         // acquire first image and draw something.
     95         BufferedImage bufferedImageForImg1;
     96         final CountDownLatch countDownLatch1 = new CountDownLatch(1);
     97         {
     98             Image img1 = pool.acquire(width, height, type,
     99                     bufferedImage -> countDownLatch1.countDown());
    100             bufferedImageForImg1 = getImg(img1);
    101             img1 = null; // this is still needed.
    102         }
    103         // dispose
    104         gc();
    105         assertTrue(countDownLatch1.await(TIMEOUT_SEC, TimeUnit.SECONDS));
    106 
    107         // ensure dispose actually loses buffered image link so it can be gc'd
    108         assertNotNull(bufferedImageForImg1);
    109         assertEquals(bufferedImageForImg1.getWidth(), bucketWidth);
    110         assertEquals(bufferedImageForImg1.getHeight(), bucketHeight);
    111 
    112         // get 2nd image with the same spec
    113         final CountDownLatch countDownLatch2 = new CountDownLatch(1);
    114         BufferedImage bufferedImageForImg2;
    115         {
    116             Image img2 = pool.acquire(width - variant, height - variant, type,
    117                     bufferedImage -> countDownLatch2.countDown());
    118             bufferedImageForImg2 = getImg(img2);
    119             assertEquals(bufferedImageForImg1, bufferedImageForImg2);
    120             img2 = null;
    121         }
    122         // dispose
    123         gc();
    124         assertTrue(countDownLatch2.await(TIMEOUT_SEC, TimeUnit.SECONDS));
    125 
    126         // ensure that we're recycling previously created buffered image.
    127         assertNotNull(bufferedImageForImg1);
    128         assertNotNull(bufferedImageForImg2);
    129     }
    130 
    131 
    132     @Test
    133     public void testBufferedImageReleased() throws InterruptedException {
    134         int width = 700;
    135         int height = 800;
    136         int bucketWidth = 800;
    137         int bucketHeight = 800;
    138         ImagePoolImpl pool = new ImagePoolImpl(new ImagePoolPolicy(
    139                 new int[]{bucketWidth, bucketHeight},
    140                 new int[]{1, 1},
    141                 bucketWidth * bucketWidth * 4 * 2));
    142         CountDownLatch countDownLatch = new CountDownLatch(1);
    143         Image image1 = pool.acquire(width, height, BufferedImage.TYPE_INT_ARGB,
    144                 bufferedImage -> countDownLatch.countDown());
    145         BufferedImage internalPtr = getImg(image1);
    146         // dispose
    147         image1 = null;
    148         gc();
    149         assertTrue(countDownLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS));
    150 
    151         // Simulate BufferedBitmaps being gc'd. Bucket filled with null soft refs.
    152         for (Bucket bucket : ((ImagePoolImpl) pool).mPool.values()) {
    153             bucket.mBufferedImageRef.clear();
    154             bucket.mBufferedImageRef.add(new SoftReference<>(null));
    155             bucket.mBufferedImageRef.add(new SoftReference<>(null));
    156         }
    157 
    158         assertNotEquals(internalPtr,
    159                 getImg(pool.acquire(width, height, BufferedImage.TYPE_INT_ARGB)));
    160     }
    161 
    162     @Test
    163     public void testPoolWidthHeightNotBigEnough() {
    164         int width = 1000;
    165         int height = 1000;
    166         int bucketWidth = 999;
    167         int bucketHeight = 800;
    168         ImagePool pool = new ImagePoolImpl(
    169                 new ImagePoolPolicy(new int[]{bucketWidth, bucketHeight}, new int[]{1, 1},
    170                         bucketWidth * bucketWidth * 4 * 2));
    171         ImageImpl image = (ImageImpl) pool.acquire(width, height, BufferedImage.TYPE_INT_ARGB);
    172 
    173         assertEquals(getTooBigForPoolCount(pool), 1);
    174     }
    175 
    176     @Test
    177     public void testSizeNotBigEnough() {
    178         int width = 500;
    179         int height = 500;
    180         int bucketWidth = 800;
    181         int bucketHeight = 800;
    182         ImagePoolImpl pool = new ImagePoolImpl(
    183                 new ImagePoolPolicy(new int[]{bucketWidth, bucketHeight}, new int[]{1, 1},
    184                         bucketWidth * bucketWidth)); // cache not big enough.
    185         ImageImpl image = (ImageImpl) pool.acquire(width, height, BufferedImage.TYPE_INT_ARGB);
    186 
    187         assertEquals(getTooBigForPoolCount(pool), 1);
    188         assertEquals(image.getWidth(), width);
    189         assertEquals(image.getHeight(), height);
    190     }
    191 
    192     @Test
    193     public void testImageMultipleCopies() throws InterruptedException {
    194         int width = 700;
    195         int height = 800;
    196         int bucketWidth = 800;
    197         int bucketHeight = 800;
    198         int type = BufferedImage.TYPE_INT_ARGB;
    199         ImagePoolImpl pool = new ImagePoolImpl(new ImagePoolPolicy(
    200                 new int[]{bucketWidth, bucketHeight},
    201                 new int[]{2, 2},
    202                 bucketHeight * bucketWidth * 4 * 4));
    203 
    204         // create 1, and 2 different instances.
    205         final CountDownLatch cd1 = new CountDownLatch(1);
    206         Image img1 = pool.acquire(width, height, type, bufferedImage -> cd1.countDown());
    207         BufferedImage bufferedImg1 = getImg(img1);
    208 
    209         Image img2 = pool.acquire(width, height, type);
    210         BufferedImage bufferedImg2 = getImg(img2);
    211 
    212         assertNotEquals(bufferedImg1, bufferedImg2);
    213 
    214         // disposing img1. Since # of copies == 2, this buffer should be recycled.
    215         img1 = null;
    216         gc();
    217         cd1.await(TIMEOUT_SEC, TimeUnit.SECONDS);
    218 
    219         // Ensure bufferedImg1 is recycled in newly acquired img3.
    220         Image img3 = pool.acquire(width, height, type);
    221         BufferedImage bufferedImage3 = getImg(img3);
    222         assertNotEquals(bufferedImg2, bufferedImage3);
    223         assertEquals(bufferedImg1, bufferedImage3);
    224     }
    225 
    226     @Test
    227     public void testPoolDispose() throws InterruptedException {
    228         int width = 700;
    229         int height = 800;
    230         int bucketWidth = 800;
    231         int bucketHeight = 800;
    232         int type = BufferedImage.TYPE_INT_ARGB;
    233 
    234         // Pool barely enough for 1 image.
    235         ImagePoolImpl pool = new ImagePoolImpl(new ImagePoolPolicy(
    236                 new int[]{bucketWidth, bucketHeight},
    237                 new int[]{2, 2},
    238                 bucketHeight * bucketWidth * 4));
    239 
    240         // create 1, and 2 different instances.
    241         final CountDownLatch cd1 = new CountDownLatch(1);
    242         Image img1 = pool.acquire(width, height, type, bufferedImage -> cd1.countDown());
    243         BufferedImage bufferedImg1 = getImg(img1);
    244         assertEquals(getAllocatedTotalBytes(pool), bucketWidth * bucketHeight * 4);
    245         assertEquals(getTooBigForPoolCount(pool), 0);
    246 
    247         // Release the img1.
    248         img1 = null;
    249         gc();
    250         cd1.await(TIMEOUT_SEC, TimeUnit.SECONDS);
    251 
    252         // Dispose pool.
    253         pool.dispose();
    254         assertEquals(getAllocatedTotalBytes(pool), 0);
    255 
    256         // Request the same sized image as previous.
    257         // If the pool was not disposed, this would return the image with bufferedImg1.
    258         Image img2 = pool.acquire(width, height, type);
    259         BufferedImage bufferedImg2 = getImg(img2);
    260         assertEquals(getAllocatedTotalBytes(pool), bucketWidth * bucketHeight * 4);
    261         assertEquals(getTooBigForPoolCount(pool), 0);
    262 
    263         // Pool disposed before. No buffered image should be recycled.
    264         assertNotEquals(img1, img2);
    265         assertNotEquals(bufferedImg1, bufferedImg2);
    266     }
    267 
    268     private static BufferedImage createImageAndReturnBufferedImage(ImagePoolImpl pool, int width,
    269             int height
    270             , int type, CountDownLatch cd) {
    271         Image img1 = pool.acquire(width, height, type, bufferedImage -> cd.countDown());
    272         return getImg(img1);
    273         // At this point img1 should have no reference, causing finalizable to trigger
    274     }
    275 
    276     private static ImagePoolImpl getSimpleSingleBucketPool(int width, int height) {
    277 
    278         int bucketWidth = Math.max(width, height);
    279         int bucketHeight = Math.max(width, height);
    280         return new ImagePoolImpl(new ImagePoolPolicy(
    281                 new int[]{bucketWidth, bucketHeight},
    282                 new int[]{1, 1},
    283                 bucketHeight * bucketWidth * 4 * 3));
    284     }
    285 
    286     // Try to force a gc round
    287     private static void gc() {
    288         System.gc();
    289         System.gc();
    290         System.gc();
    291     }
    292 
    293     private static int getTooBigForPoolCount(ImagePool pool) {
    294         return ((ImagePoolStatsProdImpl) ((ImagePoolImpl) pool).mImagePoolStats).mTooBigForPoolCount;
    295     }
    296 
    297     private static long getAllocatedTotalBytes(ImagePool pool) {
    298         return ((ImagePoolStatsProdImpl) ((ImagePoolImpl) pool).mImagePoolStats).mAllocateTotalBytes;
    299     }
    300 
    301     private static BufferedImage getImg(Image image) {
    302         return ((ImageImpl) image).mImg;
    303     }
    304 }
    305