Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2013 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.example.android.contactslist.util;
     18 
     19 import android.annotation.TargetApi;
     20 import android.graphics.Bitmap;
     21 import android.os.Bundle;
     22 import android.support.v4.app.Fragment;
     23 import android.support.v4.app.FragmentManager;
     24 import android.support.v4.util.LruCache;
     25 import android.util.Log;
     26 
     27 import com.example.android.contactslist.BuildConfig;
     28 
     29 /**
     30  * This class holds our bitmap caches (memory and disk).
     31  */
     32 public class ImageCache {
     33     private static final String TAG = "ImageCache";
     34     private LruCache<String, Bitmap> mMemoryCache;
     35 
     36     /**
     37      * Creating a new ImageCache object using the specified parameters.
     38      *
     39      * @param memCacheSizePercent The cache size as a percent of available app memory.
     40      */
     41     private ImageCache(float memCacheSizePercent) {
     42         init(memCacheSizePercent);
     43     }
     44 
     45     /**
     46      * Find and return an existing ImageCache stored in a {@link RetainFragment}, if not found a new
     47      * one is created using the supplied params and saved to a {@link RetainFragment}.
     48      *
     49      * @param fragmentManager The fragment manager to use when dealing with the retained fragment.
     50      * @param memCacheSizePercent The cache size as a percent of available app memory.
     51      * @return An existing retained ImageCache object or a new one if one did not exist
     52      */
     53     public static ImageCache getInstance(
     54             FragmentManager fragmentManager, float memCacheSizePercent) {
     55 
     56         // Search for, or create an instance of the non-UI RetainFragment
     57         final RetainFragment mRetainFragment = findOrCreateRetainFragment(fragmentManager);
     58 
     59         // See if we already have an ImageCache stored in RetainFragment
     60         ImageCache imageCache = (ImageCache) mRetainFragment.getObject();
     61 
     62         // No existing ImageCache, create one and store it in RetainFragment
     63         if (imageCache == null) {
     64             imageCache = new ImageCache(memCacheSizePercent);
     65             mRetainFragment.setObject(imageCache);
     66         }
     67 
     68         return imageCache;
     69     }
     70 
     71     /**
     72      * Initialize the cache.
     73      *
     74      * @param memCacheSizePercent The cache size as a percent of available app memory.
     75      */
     76     private void init(float memCacheSizePercent) {
     77         int memCacheSize = calculateMemCacheSize(memCacheSizePercent);
     78 
     79         // Set up memory cache
     80         if (BuildConfig.DEBUG) {
     81             Log.d(TAG, "Memory cache created (size = " + memCacheSize + ")");
     82         }
     83         mMemoryCache = new LruCache<String, Bitmap>(memCacheSize) {
     84             /**
     85              * Measure item size in kilobytes rather than units which is more practical
     86              * for a bitmap cache
     87              */
     88             @Override
     89             protected int sizeOf(String key, Bitmap bitmap) {
     90                 final int bitmapSize = getBitmapSize(bitmap) / 1024;
     91                 return bitmapSize == 0 ? 1 : bitmapSize;
     92             }
     93         };
     94     }
     95 
     96     /**
     97      * Adds a bitmap to both memory and disk cache.
     98      * @param data Unique identifier for the bitmap to store
     99      * @param bitmap The bitmap to store
    100      */
    101     public void addBitmapToCache(String data, Bitmap bitmap) {
    102         if (data == null || bitmap == null) {
    103             return;
    104         }
    105 
    106         // Add to memory cache
    107         if (mMemoryCache != null && mMemoryCache.get(data) == null) {
    108             mMemoryCache.put(data, bitmap);
    109         }
    110     }
    111 
    112     /**
    113      * Get from memory cache.
    114      *
    115      * @param data Unique identifier for which item to get
    116      * @return The bitmap if found in cache, null otherwise
    117      */
    118     public Bitmap getBitmapFromMemCache(String data) {
    119         if (mMemoryCache != null) {
    120             final Bitmap memBitmap = mMemoryCache.get(data);
    121             if (memBitmap != null) {
    122                 if (BuildConfig.DEBUG) {
    123                     Log.d(TAG, "Memory cache hit");
    124                 }
    125                 return memBitmap;
    126             }
    127         }
    128         return null;
    129     }
    130 
    131     /**
    132      * Get the size in bytes of a bitmap.
    133      *
    134      * @param bitmap The bitmap to calculate the size of.
    135      * @return size of bitmap in bytes.
    136      */
    137     @TargetApi(12)
    138     public static int getBitmapSize(Bitmap bitmap) {
    139         if (Utils.hasHoneycombMR1()) {
    140             return bitmap.getByteCount();
    141         }
    142         // Pre HC-MR1
    143         return bitmap.getRowBytes() * bitmap.getHeight();
    144     }
    145 
    146     /**
    147      * Calculates the memory cache size based on a percentage of the max available VM memory.
    148      * Eg. setting percent to 0.2 would set the memory cache to one fifth of the available
    149      * memory. Throws {@link IllegalArgumentException} if percent is < 0.05 or > .8.
    150      * memCacheSize is stored in kilobytes instead of bytes as this will eventually be passed
    151      * to construct a LruCache which takes an int in its constructor.
    152      *
    153      * This value should be chosen carefully based on a number of factors
    154      * Refer to the corresponding Android Training class for more discussion:
    155      * http://developer.android.com/training/displaying-bitmaps/
    156      *
    157      * @param percent Percent of available app memory to use to size memory cache.
    158      */
    159     public static int calculateMemCacheSize(float percent) {
    160         if (percent < 0.05f || percent > 0.8f) {
    161             throw new IllegalArgumentException("setMemCacheSizePercent - percent must be "
    162                     + "between 0.05 and 0.8 (inclusive)");
    163         }
    164         return Math.round(percent * Runtime.getRuntime().maxMemory() / 1024);
    165     }
    166 
    167     /**
    168      * Locate an existing instance of this Fragment or if not found, create and
    169      * add it using FragmentManager.
    170      *
    171      * @param fm The FragmentManager manager to use.
    172      * @return The existing instance of the Fragment or the new instance if just
    173      *         created.
    174      */
    175     public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
    176         // Check to see if we have retained the worker fragment.
    177         RetainFragment mRetainFragment = (RetainFragment) fm.findFragmentByTag(TAG);
    178 
    179         // If not retained (or first time running), we need to create and add it.
    180         if (mRetainFragment == null) {
    181             mRetainFragment = new RetainFragment();
    182             fm.beginTransaction().add(mRetainFragment, TAG).commitAllowingStateLoss();
    183         }
    184 
    185         return mRetainFragment;
    186     }
    187 
    188     /**
    189      * A simple non-UI Fragment that stores a single Object and is retained over configuration
    190      * changes. It will be used to retain the ImageCache object.
    191      */
    192     public static class RetainFragment extends Fragment {
    193         private Object mObject;
    194 
    195         /**
    196          * Empty constructor as per the Fragment documentation
    197          */
    198         public RetainFragment() {}
    199 
    200         @Override
    201         public void onCreate(Bundle savedInstanceState) {
    202             super.onCreate(savedInstanceState);
    203 
    204             // Make sure this Fragment is retained over a configuration change
    205             setRetainInstance(true);
    206         }
    207 
    208         /**
    209          * Store a single object in this Fragment.
    210          *
    211          * @param object The object to store
    212          */
    213         public void setObject(Object object) {
    214             mObject = object;
    215         }
    216 
    217         /**
    218          * Get the stored object.
    219          *
    220          * @return The stored object
    221          */
    222         public Object getObject() {
    223             return mObject;
    224         }
    225     }
    226 
    227 }
    228