Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2015 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 com.android.tv.util;
     18 
     19 import android.support.annotation.VisibleForTesting;
     20 import android.util.Log;
     21 import android.util.LruCache;
     22 
     23 import com.android.tv.common.MemoryManageable;
     24 import com.android.tv.util.BitmapUtils.ScaledBitmapInfo;
     25 
     26 /**
     27  * A convenience class for caching bitmap.
     28  */
     29 public class ImageCache implements MemoryManageable {
     30     private static final float MAX_CACHE_SIZE_PERCENT = 0.8f;
     31     private static final float MIN_CACHE_SIZE_PERCENT = 0.05f;
     32     private static final float DEFAULT_CACHE_SIZE_PERCENT = 0.1f;
     33     private static final boolean DEBUG = false;
     34     private static final String TAG = "ImageCache";
     35     private static final int MIN_CACHE_SIZE_KBYTES = 1024;
     36 
     37     private final LruCache<String, ScaledBitmapInfo> mMemoryCache;
     38 
     39     /**
     40      * Creates a new ImageCache object with a given cache size percent.
     41      *
     42      * @param memCacheSizePercent The cache size as a percent of available app memory.
     43      */
     44     private ImageCache(float memCacheSizePercent) {
     45         int memCacheSize = calculateMemCacheSize(memCacheSizePercent);
     46 
     47         // Set up memory cache
     48         if (DEBUG) {
     49             Log.d(TAG, "Memory cache created (size = " + memCacheSize + " Kbytes)");
     50         }
     51         mMemoryCache = new LruCache<String, ScaledBitmapInfo>(memCacheSize) {
     52             /**
     53              * Measure item size in kilobytes rather than units which is more practical for a bitmap
     54              * cache
     55              */
     56             @Override
     57             protected int sizeOf(String key, ScaledBitmapInfo bitmapInfo) {
     58                 return (bitmapInfo.bitmap.getByteCount() + 1023) / 1024;
     59             }
     60         };
     61     }
     62 
     63     private static ImageCache sImageCache;
     64 
     65     /**
     66      * Returns an existing ImageCache, if it doesn't exist, a new one is created using the supplied
     67      * param.
     68      *
     69      * @param memCacheSizePercent The cache size as a percent of available app memory. Should be in
     70      *                            range of MIN_CACHE_SIZE_PERCENT(0.05) ~ MAX_CACHE_SIZE_PERCENT(0.8).
     71      * @return An existing retained ImageCache object or a new one if one did not exist
     72      */
     73     public static synchronized ImageCache getInstance(float memCacheSizePercent) {
     74         if (sImageCache == null) {
     75             sImageCache = newInstance(memCacheSizePercent);
     76         }
     77         return sImageCache;
     78     }
     79 
     80     @VisibleForTesting
     81     static ImageCache newInstance(float memCacheSizePercent) {
     82         return new ImageCache(memCacheSizePercent);
     83     }
     84 
     85 
     86     /**
     87      * Returns an existing ImageCache, if it doesn't exist, a new one is created using
     88      * DEFAULT_CACHE_SIZE_PERCENT (0.1).
     89      *
     90      * @return An existing retained ImageCache object or a new one if one did not exist
     91      */
     92     public static ImageCache getInstance() {
     93         return getInstance(DEFAULT_CACHE_SIZE_PERCENT);
     94     }
     95 
     96     /**
     97      * Adds a bitmap to memory cache.
     98      *
     99      * <p>If there is an existing bitmap only replace it if
    100      * {@link ScaledBitmapInfo#needToReload(ScaledBitmapInfo)} is true.
    101      *
    102      * @param bitmapInfo The {@link ScaledBitmapInfo} object to store
    103      */
    104     public void putIfNeeded(ScaledBitmapInfo bitmapInfo) {
    105         if (bitmapInfo == null || bitmapInfo.id == null) {
    106             throw new IllegalArgumentException("Neither bitmap nor bitmap.id should be null.");
    107         }
    108         String key = bitmapInfo.id;
    109         // Add to memory cache
    110         synchronized (mMemoryCache) {
    111             ScaledBitmapInfo old = mMemoryCache.put(key, bitmapInfo);
    112             if (old != null && !old.needToReload(bitmapInfo)) {
    113                 mMemoryCache.put(key, old);
    114                 if (DEBUG) {
    115                     Log.d(TAG,
    116                             "Kept original " + old + " in memory cache because it was larger than "
    117                                     + bitmapInfo + ".");
    118                 }
    119             } else {
    120                 if (DEBUG) {
    121                     Log.d(TAG, "Add " + bitmapInfo + " to memory cache. Current size is " +
    122                             mMemoryCache.size() + " / " + mMemoryCache.maxSize() + " Kbytes");
    123                 }
    124             }
    125         }
    126     }
    127 
    128     /**
    129      * Get from memory cache.
    130      *
    131      * @param key Unique identifier for which item to get
    132      * @return The bitmap if found in cache, null otherwise
    133      */
    134     public ScaledBitmapInfo get(String key) {
    135         ScaledBitmapInfo memBitmapInfo = mMemoryCache.get(key);
    136         if (DEBUG) {
    137             int hit = mMemoryCache.hitCount();
    138             int miss = mMemoryCache.missCount();
    139             String result = memBitmapInfo == null ? "miss" : "hit";
    140             double ratio = ((double) hit) / (hit + miss) * 100;
    141             Log.d(TAG, "Memory cache " + result + " for  " + key);
    142             Log.d(TAG, "Memory cache " + hit + "h:" + miss + "m " + ratio + "%");
    143         }
    144         return memBitmapInfo;
    145     }
    146 
    147     /**
    148      * Calculates the memory cache size based on a percentage of the max available VM memory. Eg.
    149      * setting percent to 0.2 would set the memory cache to one fifth of the available memory.
    150      * Throws {@link IllegalArgumentException} if percent is < 0.05 or > .8. memCacheSize is stored
    151      * in kilobytes instead of bytes as this will eventually be passed to construct a LruCache
    152      * which takes an int in its constructor. This value should be chosen carefully based on a
    153      * number of factors Refer to the corresponding Android Training class for more discussion:
    154      * http://developer.android.com/training/displaying-bitmaps/
    155      *
    156      * @param percent Percent of available app memory to use to size memory cache.
    157      */
    158     public static int calculateMemCacheSize(float percent) {
    159         if (percent < MIN_CACHE_SIZE_PERCENT || percent > MAX_CACHE_SIZE_PERCENT) {
    160             throw new IllegalArgumentException("setMemCacheSizePercent - percent must be "
    161                     + "between 0.05 and 0.8 (inclusive)");
    162         }
    163         return Math.max(MIN_CACHE_SIZE_KBYTES,
    164                 Math.round(percent * Runtime.getRuntime().maxMemory() / 1024));
    165     }
    166 
    167     @Override
    168     public void performTrimMemory(int level) {
    169         mMemoryCache.evictAll();
    170     }
    171 }
    172