Please note that the contents of this offline web site may be out of date. To access the most recent documentation visit the online version .
Note that links that point to online resources are green in color and will open in a new window.
We would love it if you could give us feedback about this material by filling this form (You have to be online to fill it)
DisplayingBitmaps / src / com.example.android.displayingbitmaps / util /

ImageCache.java

       
        1
       
       
        /*
       
       
        2
       
       
        * Copyright (C) 2012 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.displayingbitmaps.util;
       
       
        18
       
       
       
       
        19
       
       
        import android.annotation.TargetApi;
       
       
        20
       
       
        import android.content.Context;
       
       
        21
       
       
        import android.graphics.Bitmap;
       
       
        22
       
       
        import android.graphics.Bitmap.CompressFormat;
       
       
        23
       
       
        import android.graphics.Bitmap.Config;
       
       
        24
       
       
        import android.graphics.BitmapFactory;
       
       
        25
       
       
        import android.graphics.drawable.BitmapDrawable;
       
       
        26
       
       
        import android.os.Build.VERSION_CODES;
       
       
        27
       
       
        import android.os.Bundle;
       
       
        28
       
       
        import android.os.Environment;
       
       
        29
       
       
        import android.os.StatFs;
       
       
        30
       
       
        import android.support.v4.app.Fragment;
       
       
        31
       
       
        import android.support.v4.app.FragmentManager;
       
       
        32
       
       
        import android.support.v4.util.LruCache;
       
       
        33
       
       
       
       
        34
       
       
        import com.example.android.common.logger.Log;
       
       
        35
       
       
        import com.example.android.displayingbitmaps.BuildConfig;
       
       
        36
       
       
       
       
        37
       
       
        import java.io.File;
       
       
        38
       
       
        import java.io.FileDescriptor;
       
       
        39
       
       
        import java.io.FileInputStream;
       
       
        40
       
       
        import java.io.IOException;
       
       
        41
       
       
        import java.io.InputStream;
       
       
        42
       
       
        import java.io.OutputStream;
       
       
        43
       
       
        import java.lang.ref.SoftReference;
       
       
        44
       
       
        import java.security.MessageDigest;
       
       
        45
       
       
        import java.security.NoSuchAlgorithmException;
       
       
        46
       
       
        import java.util.Collections;
       
       
        47
       
       
        import java.util.HashSet;
       
       
        48
       
       
        import java.util.Iterator;
       
       
        49
       
       
        import java.util.Set;
       
       
        50
       
       
       
       
        51
       
       
        /**
       
       
        52
       
       
        * This class handles disk and memory caching of bitmaps in conjunction with the
       
       
        53
       
       
        * {@link ImageWorker} class and its subclasses. Use
       
       
        54
       
       
        * {@link ImageCache#getInstance(android.support.v4.app.FragmentManager, ImageCacheParams)} to get an instance of this
       
       
        55
       
       
        * class, although usually a cache should be added directly to an {@link ImageWorker} by calling
       
       
        56
       
       
        * {@link ImageWorker#addImageCache(android.support.v4.app.FragmentManager, ImageCacheParams)}.
       
       
        57
       
       
        */
       
       
        58
       
       
        public class ImageCache {
       
       
        59
       
       
        private static final String TAG = "ImageCache";
       
       
        60
       
       
       
       
        61
       
       
        // Default memory cache size in kilobytes
       
       
        62
       
       
        private static final int DEFAULT_MEM_CACHE_SIZE = 1024 * 5; // 5MB
       
       
        63
       
       
       
       
        64
       
       
        // Default disk cache size in bytes
       
       
        65
       
       
        private static final int DEFAULT_DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
       
       
        66
       
       
       
       
        67
       
       
        // Compression settings when writing images to disk cache
       
       
        68
       
       
        private static final CompressFormat DEFAULT_COMPRESS_FORMAT = CompressFormat.JPEG;
       
       
        69
       
       
        private static final int DEFAULT_COMPRESS_QUALITY = 70;
       
       
        70
       
       
        private static final int DISK_CACHE_INDEX = 0;
       
       
        71
       
       
       
       
        72
       
       
        // Constants to easily toggle various caches
       
       
        73
       
       
        private static final boolean DEFAULT_MEM_CACHE_ENABLED = true;
       
       
        74
       
       
        private static final boolean DEFAULT_DISK_CACHE_ENABLED = true;
       
       
        75
       
       
        private static final boolean DEFAULT_INIT_DISK_CACHE_ON_CREATE = false;
       
       
        76
       
       
       
       
        77
       
       
        private DiskLruCache mDiskLruCache;
       
       
        78
       
       
        private LruCache<String, BitmapDrawable> mMemoryCache;
       
       
        79
       
       
        private ImageCacheParams mCacheParams;
       
       
        80
       
       
        private final Object mDiskCacheLock = new Object();
       
       
        81
       
       
        private boolean mDiskCacheStarting = true;
       
       
        82
       
       
       
       
        83
       
       
        private Set<SoftReference<Bitmap>> mReusableBitmaps;
       
       
        84
       
       
       
       
        85
       
       
        /**
       
       
        86
       
       
        * Create a new ImageCache object using the specified parameters. This should not be
       
       
        87
       
       
        * called directly by other classes, instead use
       
       
        88
       
       
        * {@link ImageCache#getInstance(android.support.v4.app.FragmentManager, ImageCacheParams)} to fetch an ImageCache
       
       
        89
       
       
        * instance.
       
       
        90
       
       
        *
       
       
        91
       
       
        * @param cacheParams The cache parameters to use to initialize the cache
       
       
        92
       
       
        */
       
       
        93
       
       
        private ImageCache(ImageCacheParams cacheParams) {
       
       
        94
       
       
        init(cacheParams);
       
       
        95
       
       
        }
       
       
        96
       
       
       
       
        97
       
       
        /**
       
       
        98
       
       
        * Return an {@link ImageCache} instance. A {@link RetainFragment} is used to retain the
       
       
        99
       
       
        * ImageCache object across configuration changes such as a change in device orientation.
       
       
        100
       
       
        *
       
       
        101
       
       
        * @param fragmentManager The fragment manager to use when dealing with the retained fragment.
       
       
        102
       
       
        * @param cacheParams The cache parameters to use if the ImageCache needs instantiation.
       
       
        103
       
       
        * @return An existing retained ImageCache object or a new one if one did not exist
       
       
        104
       
       
        */
       
       
        105
       
       
        public static ImageCache getInstance(
       
       
        106
       
       
        FragmentManager fragmentManager, ImageCacheParams cacheParams) {
       
       
        107
       
       
       
       
        108
       
       
        // Search for, or create an instance of the non-UI RetainFragment
       
       
        109
       
       
        final RetainFragment mRetainFragment = findOrCreateRetainFragment(fragmentManager);
       
       
        110
       
       
       
       
        111
       
       
        // See if we already have an ImageCache stored in RetainFragment
       
       
        112
       
       
        ImageCache imageCache = (ImageCache) mRetainFragment.getObject();
       
       
        113
       
       
       
       
        114
       
       
        // No existing ImageCache, create one and store it in RetainFragment
       
       
        115
       
       
        if (imageCache == null) {
       
       
        116
       
       
        imageCache = new ImageCache(cacheParams);
       
       
        117
       
       
        mRetainFragment.setObject(imageCache);
       
       
        118
       
       
        }
       
       
        119
       
       
       
       
        120
       
       
        return imageCache;
       
       
        121
       
       
        }
       
       
        122
       
       
       
       
        123
       
       
        /**
       
       
        124
       
       
        * Initialize the cache, providing all parameters.
       
       
        125
       
       
        *
       
       
        126
       
       
        * @param cacheParams The cache parameters to initialize the cache
       
       
        127
       
       
        */
       
       
        128
       
       
        private void init(ImageCacheParams cacheParams) {
       
       
        129
       
       
        mCacheParams = cacheParams;
       
       
        130
       
       
       
       
        132
       
       
        // Set up memory cache
       
       
        133
       
       
        if (mCacheParams.memoryCacheEnabled) {
       
       
        134
       
       
        if (BuildConfig.DEBUG) {
       
       
        135
       
       
        Log.d(TAG, "Memory cache created (size = " + mCacheParams.memCacheSize + ")");
       
       
        136
       
       
        }
       
       
        137
       
       
       
       
        138
       
       
        // If we're running on Honeycomb or newer, create a set of reusable bitmaps that can be
       
       
        139
       
       
        // populated into the inBitmap field of BitmapFactory.Options. Note that the set is
       
       
        140
       
       
        // of SoftReferences which will actually not be very effective due to the garbage
       
       
        141
       
       
        // collector being aggressive clearing Soft/WeakReferences. A better approach
       
       
        142
       
       
        // would be to use a strongly references bitmaps, however this would require some
       
       
        143
       
       
        // balancing of memory usage between this set and the bitmap LruCache. It would also
       
       
        144
       
       
        // require knowledge of the expected size of the bitmaps. From Honeycomb to JellyBean
       
       
        145
       
       
        // the size would need to be precise, from KitKat onward the size would just need to
       
       
        146
       
       
        // be the upper bound (due to changes in how inBitmap can re-use bitmaps).
       
       
        147
       
       
        if (Utils.hasHoneycomb()) {
       
       
        148
       
       
        mReusableBitmaps =
       
       
        149
       
       
        Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());
       
       
        150
       
       
        }
       
       
        151
       
       
       
       
        152
       
       
        mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {
       
       
        153
       
       
       
       
        154
       
       
        /**
       
       
        155
       
       
        * Notify the removed entry that is no longer being cached
       
       
        156
       
       
        */
       
       
        157
       
       
        @Override
       
       
        158
       
       
        protected void entryRemoved(boolean evicted, String key,
       
       
        159
       
       
        BitmapDrawable oldValue, BitmapDrawable newValue) {
       
       
        160
       
       
        if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
       
       
        161
       
       
        // The removed entry is a recycling drawable, so notify it
       
       
        162
       
       
        // that it has been removed from the memory cache
       
       
        163
       
       
        ((RecyclingBitmapDrawable) oldValue).setIsCached(false);
       
       
        164
       
       
        } else {
       
       
        165
       
       
        // The removed entry is a standard BitmapDrawable
       
       
        166
       
       
       
       
        167
       
       
        if (Utils.hasHoneycomb()) {
       
       
        168
       
       
        // We're running on Honeycomb or later, so add the bitmap
       
       
        169
       
       
        // to a SoftReference set for possible use with inBitmap later
       
       
        170
       
       
        mReusableBitmaps.add(new SoftReference<Bitmap>(oldValue.getBitmap()));
       
       
        171
       
       
        }
       
       
        172
       
       
        }
       
       
        173
       
       
        }
       
       
        174
       
       
       
       
        175
       
       
        /**
       
       
        176
       
       
        * Measure item size in kilobytes rather than units which is more practical
       
       
        177
       
       
        * for a bitmap cache
       
       
        178
       
       
        */
       
       
        179
       
       
        @Override
       
       
        180
       
       
        protected int sizeOf(String key, BitmapDrawable value) {
       
       
        181
       
       
        final int bitmapSize = getBitmapSize(value) / 1024;
       
       
        182
       
       
        return bitmapSize == 0 ? 1 : bitmapSize;
       
       
        183
       
       
        }
       
       
        184
       
       
        };
       
       
        185
       
       
        }
       
       
        187
       
       
       
       
        188
       
       
        // By default the disk cache is not initialized here as it should be initialized
       
       
        189
       
       
        // on a separate thread due to disk access.
       
       
        190
       
       
        if (cacheParams.initDiskCacheOnCreate) {
       
       
        191
       
       
        // Set up disk cache
       
       
        192
       
       
        initDiskCache();
       
       
        193
       
       
        }
       
       
        194
       
       
        }
       
       
        195
       
       
       
       
        196
       
       
        /**
       
       
        197
       
       
        * Initializes the disk cache.  Note that this includes disk access so this should not be
       
       
        198
       
       
        * executed on the main/UI thread. By default an ImageCache does not initialize the disk
       
       
        199
       
       
        * cache when it is created, instead you should call initDiskCache() to initialize it on a
       
       
        200
       
       
        * background thread.
       
       
        201
       
       
        */
       
       
        202
       
       
        public void initDiskCache() {
       
       
        203
       
       
        // Set up disk cache
       
       
        204
       
       
        synchronized (mDiskCacheLock) {
       
       
        205
       
       
        if (mDiskLruCache == null || mDiskLruCache.isClosed()) {
       
       
        206
       
       
        File diskCacheDir = mCacheParams.diskCacheDir;
       
       
        207
       
       
        if (mCacheParams.diskCacheEnabled && diskCacheDir != null) {
       
       
        208
       
       
        if (!diskCacheDir.exists()) {
       
       
        209
       
       
        diskCacheDir.mkdirs();
       
       
        210
       
       
        }
       
       
        211
       
       
        if (getUsableSpace(diskCacheDir) > mCacheParams.diskCacheSize) {
       
       
        212
       
       
        try {
       
       
        213
       
       
        mDiskLruCache = DiskLruCache.open(
       
       
        214
       
       
        diskCacheDir, 1, 1, mCacheParams.diskCacheSize);
       
       
        215
       
       
        if (BuildConfig.DEBUG) {
       
       
        216
       
       
        Log.d(TAG, "Disk cache initialized");
       
       
        217
       
       
        }
       
       
        218
       
       
        } catch (final IOException e) {
       
       
        219
       
       
        mCacheParams.diskCacheDir = null;
       
       
        220
       
       
        Log.e(TAG, "initDiskCache - " + e);
       
       
        221
       
       
        }
       
       
        222
       
       
        }
       
       
        223
       
       
        }
       
       
        224
       
       
        }
       
       
        225
       
       
        mDiskCacheStarting = false;
       
       
        226
       
       
        mDiskCacheLock.notifyAll();
       
       
        227
       
       
        }
       
       
        228
       
       
        }
       
       
        229
       
       
       
       
        230
       
       
        /**
       
       
        231
       
       
        * Adds a bitmap to both memory and disk cache.
       
       
        232
       
       
        * @param data Unique identifier for the bitmap to store
       
       
        233
       
       
        * @param value The bitmap drawable to store
       
       
        234
       
       
        */
       
       
        235
       
       
        public void addBitmapToCache(String data, BitmapDrawable value) {
       
       
        237
       
       
        if (data == null || value == null) {
       
       
        238
       
       
        return;
       
       
        239
       
       
        }
       
       
        240
       
       
       
       
        241
       
       
        // Add to memory cache
       
       
        242
       
       
        if (mMemoryCache != null) {
       
       
        243
       
       
        if (RecyclingBitmapDrawable.class.isInstance(value)) {
       
       
        244
       
       
        // The removed entry is a recycling drawable, so notify it
       
       
        245
       
       
        // that it has been added into the memory cache
       
       
        246
       
       
        ((RecyclingBitmapDrawable) value).setIsCached(true);
       
       
        247
       
       
        }
       
       
        248
       
       
        mMemoryCache.put(data, value);
       
       
        249
       
       
        }
       
       
        250
       
       
       
       
        251
       
       
        synchronized (mDiskCacheLock) {
       
       
        252
       
       
        // Add to disk cache
       
       
        253
       
       
        if (mDiskLruCache != null) {
       
       
        254
       
       
        final String key = hashKeyForDisk(data);
       
       
        255
       
       
        OutputStream out = null;
       
       
        256
       
       
        try {
       
       
        257
       
       
        DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
       
       
        258
       
       
        if (snapshot == null) {
       
       
        259
       
       
        final DiskLruCache.Editor editor = mDiskLruCache.edit(key);
       
       
        260
       
       
        if (editor != null) {
       
       
        261
       
       
        out = editor.newOutputStream(DISK_CACHE_INDEX);
       
       
        262
       
       
        value.getBitmap().compress(
       
       
        263
       
       
        mCacheParams.compressFormat, mCacheParams.compressQuality, out);
       
       
        264
       
       
        editor.commit();
       
       
        265
       
       
        out.close();
       
       
        266
       
       
        }
       
       
        267
       
       
        } else {
       
       
        268
       
       
        snapshot.getInputStream(DISK_CACHE_INDEX).close();
       
       
        269
       
       
        }
       
       
        270
       
       
        } catch (final IOException e) {
       
       
        271
       
       
        Log.e(TAG, "addBitmapToCache - " + e);
       
       
        272
       
       
        } catch (Exception e) {
       
       
        273
       
       
        Log.e(TAG, "addBitmapToCache - " + e);
       
       
        274
       
       
        } finally {
       
       
        275
       
       
        try {
       
       
        276
       
       
        if (out != null) {
       
       
        277
       
       
        out.close();
       
       
        278
       
       
        }
       
       
        279
       
       
        } catch (IOException e) {}
       
       
        280
       
       
        }
       
       
        281
       
       
        }
       
       
        282
       
       
        }
       
       
        284
       
       
        }
       
       
        285
       
       
       
       
        286
       
       
        /**
       
       
        287
       
       
        * Get from memory cache.
       
       
        288
       
       
        *
       
       
        289
       
       
        * @param data Unique identifier for which item to get
       
       
        290
       
       
        * @return The bitmap drawable if found in cache, null otherwise
       
       
        291
       
       
        */
       
       
        292
       
       
        public BitmapDrawable getBitmapFromMemCache(String data) {
       
       
        294
       
       
        BitmapDrawable memValue = null;
       
       
        295
       
       
       
       
        296
       
       
        if (mMemoryCache != null) {
       
       
        297
       
       
        memValue = mMemoryCache.get(data);
       
       
        298
       
       
        }
       
       
        299
       
       
       
       
        300
       
       
        if (BuildConfig.DEBUG && memValue != null) {
       
       
        301
       
       
        Log.d(TAG, "Memory cache hit");
       
       
        302
       
       
        }
       
       
        303
       
       
       
       
        304
       
       
        return memValue;
       
       
        306
       
       
        }
       
       
        307
       
       
       
       
        308
       
       
        /**
       
       
        309
       
       
        * Get from disk cache.
       
       
        310
       
       
        *
       
       
        311
       
       
        * @param data Unique identifier for which item to get
       
       
        312
       
       
        * @return The bitmap if found in cache, null otherwise
       
       
        313
       
       
        */
       
       
        314
       
       
        public Bitmap getBitmapFromDiskCache(String data) {
       
       
        316
       
       
        final String key = hashKeyForDisk(data);
       
       
        317
       
       
        Bitmap bitmap = null;
       
       
        318
       
       
       
       
        319
       
       
        synchronized (mDiskCacheLock) {
       
       
        320
       
       
        while (mDiskCacheStarting) {
       
       
        321
       
       
        try {
       
       
        322
       
       
        mDiskCacheLock.wait();
       
       
        323
       
       
        } catch (InterruptedException e) {}
       
       
        324
       
       
        }
       
       
        325
       
       
        if (mDiskLruCache != null) {
       
       
        326
       
       
        InputStream inputStream = null;
       
       
        327
       
       
        try {
       
       
        328
       
       
        final DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
       
       
        329
       
       
        if (snapshot != null) {
       
       
        330
       
       
        if (BuildConfig.DEBUG) {
       
       
        331
       
       
        Log.d(TAG, "Disk cache hit");
       
       
        332
       
       
        }
       
       
        333
       
       
        inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
       
       
        334
       
       
        if (inputStream != null) {
       
       
        335
       
       
        FileDescriptor fd = ((FileInputStream) inputStream).getFD();
       
       
        336
       
       
       
       
        337
       
       
        // Decode bitmap, but we don't want to sample so give
       
       
        338
       
       
        // MAX_VALUE as the target dimensions
       
       
        339
       
       
        bitmap = ImageResizer.decodeSampledBitmapFromDescriptor(
       
       
        340
       
       
        fd, Integer.MAX_VALUE, Integer.MAX_VALUE, this);
       
       
        341
       
       
        }
       
       
        342
       
       
        }
       
       
        343
       
       
        } catch (final IOException e) {
       
       
        344
       
       
        Log.e(TAG, "getBitmapFromDiskCache - " + e);
       
       
        345
       
       
        } finally {
       
       
        346
       
       
        try {
       
       
        347
       
       
        if (inputStream != null) {
       
       
        348
       
       
        inputStream.close();
       
       
        349
       
       
        }
       
       
        350
       
       
        } catch (IOException e) {}
       
       
        351
       
       
        }
       
       
        352
       
       
        }
       
       
        353
       
       
        return bitmap;
       
       
        354
       
       
        }
       
       
        356
       
       
        }
       
       
        357
       
       
       
       
        358
       
       
        /**
       
       
        359
       
       
        * @param options - BitmapFactory.Options with out* options populated
       
       
        360
       
       
        * @return Bitmap that case be used for inBitmap
       
       
        361
       
       
        */
       
       
        362
       
       
        protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
       
       
        364
       
       
        Bitmap bitmap = null;
       
       
        365
       
       
       
       
        366
       
       
        if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
       
       
        367
       
       
        synchronized (mReusableBitmaps) {
       
       
        368
       
       
        final Iterator<SoftReference<Bitmap>> iterator = mReusableBitmaps.iterator();
       
       
        369
       
       
        Bitmap item;
       
       
        370
       
       
       
       
        371
       
       
        while (iterator.hasNext()) {
       
       
        372
       
       
        item = iterator.next().get();
       
       
        373
       
       
       
       
        374
       
       
        if (null != item && item.isMutable()) {
       
       
        375
       
       
        // Check to see it the item can be used for inBitmap
       
       
        376
       
       
        if (canUseForInBitmap(item, options)) {
       
       
        377
       
       
        bitmap = item;
       
       
        378
       
       
       
       
        379
       
       
        // Remove from reusable set so it can't be used again
       
       
        380
       
       
        iterator.remove();
       
       
        381
       
       
        break;
       
       
        382
       
       
        }
       
       
        383
       
       
        } else {
       
       
        384
       
       
        // Remove from the set if the reference has been cleared.
       
       
        385
       
       
        iterator.remove();
       
       
        386
       
       
        }
       
       
        387
       
       
        }
       
       
        388
       
       
        }
       
       
        389
       
       
        }
       
       
        390
       
       
       
       
        391
       
       
        return bitmap;
       
       
        393
       
       
        }
       
       
        394
       
       
       
       
        395
       
       
        /**
       
       
        396
       
       
        * Clears both the memory and disk cache associated with this ImageCache object. Note that
       
       
        397
       
       
        * this includes disk access so this should not be executed on the main/UI thread.
       
       
        398
       
       
        */
       
       
        399
       
       
        public void clearCache() {
       
       
        400
       
       
        if (mMemoryCache != null) {
       
       
        401
       
       
        mMemoryCache.evictAll();
       
       
        402
       
       
        if (BuildConfig.DEBUG) {
       
       
        403
       
       
        Log.d(TAG, "Memory cache cleared");
       
       
        404
       
       
        }
       
       
        405
       
       
        }
       
       
        406
       
       
       
       
        407
       
       
        synchronized (mDiskCacheLock) {
       
       
        408
       
       
        mDiskCacheStarting = true;
       
       
        409
       
       
        if (mDiskLruCache != null && !mDiskLruCache.isClosed()) {
       
       
        410
       
       
        try {
       
       
        411
       
       
        mDiskLruCache.delete();
       
       
        412
       
       
        if (BuildConfig.DEBUG) {
       
       
        413
       
       
        Log.d(TAG, "Disk cache cleared");
       
       
        414
       
       
        }
       
       
        415
       
       
        } catch (IOException e) {
       
       
        416
       
       
        Log.e(TAG, "clearCache - " + e);
       
       
        417
       
       
        }
       
       
        418
       
       
        mDiskLruCache = null;
       
       
        419
       
       
        initDiskCache();
       
       
        420
       
       
        }
       
       
        421
       
       
        }
       
       
        422
       
       
        }
       
       
        423
       
       
       
       
        424
       
       
        /**
       
       
        425
       
       
        * Flushes the disk cache associated with this ImageCache object. Note that this includes
       
       
        426
       
       
        * disk access so this should not be executed on the main/UI thread.
       
       
        427
       
       
        */
       
       
        428
       
       
        public void flush() {
       
       
        429
       
       
        synchronized (mDiskCacheLock) {
       
       
        430
       
       
        if (mDiskLruCache != null) {
       
       
        431
       
       
        try {
       
       
        432
       
       
        mDiskLruCache.flush();
       
       
        433
       
       
        if (BuildConfig.DEBUG) {
       
       
        434
       
       
        Log.d(TAG, "Disk cache flushed");
       
       
        435
       
       
        }
       
       
        436
       
       
        } catch (IOException e) {
       
       
        437
       
       
        Log.e(TAG, "flush - " + e);
       
       
        438
       
       
        }
       
       
        439
       
       
        }
       
       
        440
       
       
        }
       
       
        441
       
       
        }
       
       
        442
       
       
       
       
        443
       
       
        /**
       
       
        444
       
       
        * Closes the disk cache associated with this ImageCache object. Note that this includes
       
       
        445
       
       
        * disk access so this should not be executed on the main/UI thread.
       
       
        446
       
       
        */
       
       
        447
       
       
        public void close() {
       
       
        448
       
       
        synchronized (mDiskCacheLock) {
       
       
        449
       
       
        if (mDiskLruCache != null) {
       
       
        450
       
       
        try {
       
       
        451
       
       
        if (!mDiskLruCache.isClosed()) {
       
       
        452
       
       
        mDiskLruCache.close();
       
       
        453
       
       
        mDiskLruCache = null;
       
       
        454
       
       
        if (BuildConfig.DEBUG) {
       
       
        455
       
       
        Log.d(TAG, "Disk cache closed");
       
       
        456
       
       
        }
       
       
        457
       
       
        }
       
       
        458
       
       
        } catch (IOException e) {
       
       
        459
       
       
        Log.e(TAG, "close - " + e);
       
       
        460
       
       
        }
       
       
        461
       
       
        }
       
       
        462
       
       
        }
       
       
        463
       
       
        }
       
       
        464
       
       
       
       
        465
       
       
        /**
       
       
        466
       
       
        * A holder class that contains cache parameters.
       
       
        467
       
       
        */
       
       
        468
       
       
        public static class ImageCacheParams {
       
       
        469
       
       
        public int memCacheSize = DEFAULT_MEM_CACHE_SIZE;
       
       
        470
       
       
        public int diskCacheSize = DEFAULT_DISK_CACHE_SIZE;
       
       
        471
       
       
        public File diskCacheDir;
       
       
        472
       
       
        public CompressFormat compressFormat = DEFAULT_COMPRESS_FORMAT;
       
       
        473
       
       
        public int compressQuality = DEFAULT_COMPRESS_QUALITY;
       
       
        474
       
       
        public boolean memoryCacheEnabled = DEFAULT_MEM_CACHE_ENABLED;
       
       
        475
       
       
        public boolean diskCacheEnabled = DEFAULT_DISK_CACHE_ENABLED;
       
       
        476
       
       
        public boolean initDiskCacheOnCreate = DEFAULT_INIT_DISK_CACHE_ON_CREATE;
       
       
        477
       
       
       
       
        478
       
       
        /**
       
       
        479
       
       
        * Create a set of image cache parameters that can be provided to
       
       
        480
       
       
        * {@link ImageCache#getInstance(android.support.v4.app.FragmentManager, ImageCacheParams)} or
       
       
        481
       
       
        * {@link ImageWorker#addImageCache(android.support.v4.app.FragmentManager, ImageCacheParams)}.
       
       
        482
       
       
        * @param context A context to use.
       
       
        483
       
       
        * @param diskCacheDirectoryName A unique subdirectory name that will be appended to the
       
       
        484
       
       
        *                               application cache directory. Usually "cache" or "images"
       
       
        485
       
       
        *                               is sufficient.
       
       
        486
       
       
        */
       
       
        487
       
       
        public ImageCacheParams(Context context, String diskCacheDirectoryName) {
       
       
        488
       
       
        diskCacheDir = getDiskCacheDir(context, diskCacheDirectoryName);
       
       
        489
       
       
        }
       
       
        490
       
       
       
       
        491
       
       
        /**
       
       
        492
       
       
        * Sets the memory cache size based on a percentage of the max available VM memory.
       
       
        493
       
       
        * Eg. setting percent to 0.2 would set the memory cache to one fifth of the available
       
       
        494
       
       
        * memory. Throws {@link IllegalArgumentException} if percent is < 0.01 or > .8.
       
       
        495
       
       
        * memCacheSize is stored in kilobytes instead of bytes as this will eventually be passed
       
       
        496
       
       
        * to construct a LruCache which takes an int in its constructor.
       
       
        497
       
       
        *
       
       
        498
       
       
        * This value should be chosen carefully based on a number of factors
       
       
        499
       
       
        * Refer to the corresponding Android Training class for more discussion:
       
       
        500
       
       
        * http://developer.android.com/training/displaying-bitmaps/
       
       
        501
       
       
        *
       
       
        502
       
       
        * @param percent Percent of available app memory to use to size memory cache
       
       
        503
       
       
        */
       
       
        504
       
       
        public void setMemCacheSizePercent(float percent) {
       
       
        505
       
       
        if (percent < 0.01f || percent > 0.8f) {
       
       
        506
       
       
        throw new IllegalArgumentException("setMemCacheSizePercent - percent must be "
       
       
        507
       
       
        + "between 0.01 and 0.8 (inclusive)");
       
       
        508
       
       
        }
       
       
        509
       
       
        memCacheSize = Math.round(percent * Runtime.getRuntime().maxMemory() / 1024);
       
       
        510
       
       
        }
       
       
        511
       
       
        }
       
       
        512
       
       
       
       
        513
       
       
        /**
       
       
        514
       
       
        * @param candidate - Bitmap to check
       
       
        515
       
       
        * @param targetOptions - Options that have the out* value populated
       
       
        516
       
       
        * @return true if <code>candidate</code> can be used for inBitmap re-use with
       
       
        517
       
       
        *      <code>targetOptions</code>
       
       
        518
       
       
        */
       
       
        519
       
       
        @TargetApi(VERSION_CODES.KITKAT)
       
       
        520
       
       
        private static boolean canUseForInBitmap(
       
       
        521
       
       
        Bitmap candidate, BitmapFactory.Options targetOptions) {
       
       
        523
       
       
        if (!Utils.hasKitKat()) {
       
       
        524
       
       
        // On earlier versions, the dimensions must match exactly and the inSampleSize must be 1
       
       
        525
       
       
        return candidate.getWidth() == targetOptions.outWidth
       
       
        526
       
       
        && candidate.getHeight() == targetOptions.outHeight
       
       
        527
       
       
        && targetOptions.inSampleSize == 1;
       
       
        528
       
       
        }
       
       
        529
       
       
       
       
        530
       
       
        // From Android 4.4 (KitKat) onward we can re-use if the byte size of the new bitmap
       
       
        531
       
       
        // is smaller than the reusable bitmap candidate allocation byte count.
       
       
        532
       
       
        int width = targetOptions.outWidth / targetOptions.inSampleSize;
       
       
        533
       
       
        int height = targetOptions.outHeight / targetOptions.inSampleSize;
       
       
        534
       
       
        int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
       
       
        535
       
       
        return byteCount <= candidate.getAllocationByteCount();
       
       
        537
       
       
        }
       
       
        538
       
       
       
       
        539
       
       
        /**
       
       
        540
       
       
        * Return the byte usage per pixel of a bitmap based on its configuration.
       
       
        541
       
       
        * @param config The bitmap configuration.
       
       
        542
       
       
        * @return The byte usage per pixel.
       
       
        543
       
       
        */
       
       
        544
       
       
        private static int getBytesPerPixel(Config config) {
       
       
        545
       
       
        if (config == Config.ARGB_8888) {
       
       
        546
       
       
        return 4;
       
       
        547
       
       
        } else if (config == Config.RGB_565) {
       
       
        548
       
       
        return 2;
       
       
        549
       
       
        } else if (config == Config.ARGB_4444) {
       
       
        550
       
       
        return 2;
       
       
        551
       
       
        } else if (config == Config.ALPHA_8) {
       
       
        552
       
       
        return 1;
       
       
        553
       
       
        }
       
       
        554
       
       
        return 1;
       
       
        555
       
       
        }
       
       
        556
       
       
       
       
        557
       
       
        /**
       
       
        558
       
       
        * Get a usable cache directory (external if available, internal otherwise).
       
       
        559
       
       
        *
       
       
        560
       
       
        * @param context The context to use
       
       
        561
       
       
        * @param uniqueName A unique directory name to append to the cache dir
       
       
        562
       
       
        * @return The cache dir
       
       
        563
       
       
        */
       
       
        564
       
       
        public static File getDiskCacheDir(Context context, String uniqueName) {
       
       
        565
       
       
        // Check if media is mounted or storage is built-in, if so, try and use external cache dir
       
       
        566
       
       
        // otherwise use internal cache dir
       
       
        567
       
       
        final String cachePath =
       
       
        568
       
       
        Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
       
       
        569
       
       
        !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
       
       
        570
       
       
        context.getCacheDir().getPath();
       
       
        571
       
       
       
       
        572
       
       
        return new File(cachePath + File.separator + uniqueName);
       
       
        573
       
       
        }
       
       
        574
       
       
       
       
        575
       
       
        /**
       
       
        576
       
       
        * A hashing method that changes a string (like a URL) into a hash suitable for using as a
       
       
        577
       
       
        * disk filename.
       
       
        578
       
       
        */
       
       
        579
       
       
        public static String hashKeyForDisk(String key) {
       
       
        580
       
       
        String cacheKey;
       
       
        581
       
       
        try {
       
       
        582
       
       
        final MessageDigest mDigest = MessageDigest.getInstance("MD5");
       
       
        583
       
       
        mDigest.update(key.getBytes());
       
       
        584
       
       
        cacheKey = bytesToHexString(mDigest.digest());
       
       
        585
       
       
        } catch (NoSuchAlgorithmException e) {
       
       
        586
       
       
        cacheKey = String.valueOf(key.hashCode());
       
       
        587
       
       
        }
       
       
        588
       
       
        return cacheKey;
       
       
        589
       
       
        }
       
       
        590
       
       
       
       
        591
       
       
        private static String bytesToHexString(byte[] bytes) {
       
       
        592
       
       
        // http://stackoverflow.com/questions/332079
       
       
        593
       
       
        StringBuilder sb = new StringBuilder();
       
       
        594
       
       
        for (int i = 0; i < bytes.length; i++) {
       
       
        595
       
       
        String hex = Integer.toHexString(0xFF & bytes[i]);
       
       
        596
       
       
        if (hex.length() == 1) {
       
       
        597
       
       
        sb.append('0');
       
       
        598
       
       
        }
       
       
        599
       
       
        sb.append(hex);
       
       
        600
       
       
        }
       
       
        601
       
       
        return sb.toString();
       
       
        602
       
       
        }
       
       
        603
       
       
       
       
        604
       
       
        /**
       
       
        605
       
       
        * Get the size in bytes of a bitmap in a BitmapDrawable. Note that from Android 4.4 (KitKat)
       
       
        606
       
       
        * onward this returns the allocated memory size of the bitmap which can be larger than the
       
       
        607
       
       
        * actual bitmap data byte count (in the case it was re-used).
       
       
        608
       
       
        *
       
       
        609
       
       
        * @param value
       
       
        610
       
       
        * @return size in bytes
       
       
        611
       
       
        */
       
       
        612
       
       
        @TargetApi(VERSION_CODES.KITKAT)
       
       
        613
       
       
        public static int getBitmapSize(BitmapDrawable value) {
       
       
        614
       
       
        Bitmap bitmap = value.getBitmap();
       
       
        615
       
       
       
       
        616
       
       
        // From KitKat onward use getAllocationByteCount() as allocated bytes can potentially be
       
       
        617
       
       
        // larger than bitmap byte count.
       
       
        618
       
       
        if (Utils.hasKitKat()) {
       
       
        619
       
       
        return bitmap.getAllocationByteCount();
       
       
        620
       
       
        }
       
       
        621
       
       
       
       
        622
       
       
        if (Utils.hasHoneycombMR1()) {
       
       
        623
       
       
        return bitmap.getByteCount();
       
       
        624
       
       
        }
       
       
        625
       
       
       
       
        626
       
       
        // Pre HC-MR1
       
       
        627
       
       
        return bitmap.getRowBytes() * bitmap.getHeight();
       
       
        628
       
       
        }
       
       
        629
       
       
       
       
        630
       
       
        /**
       
       
        631
       
       
        * Check if external storage is built-in or removable.
       
       
        632
       
       
        *
       
       
        633
       
       
        * @return True if external storage is removable (like an SD card), false
       
       
        634
       
       
        *         otherwise.
       
       
        635
       
       
        */
       
       
        636
       
       
        @TargetApi(VERSION_CODES.GINGERBREAD)
       
       
        637
       
       
        public static boolean isExternalStorageRemovable() {
       
       
        638
       
       
        if (Utils.hasGingerbread()) {
       
       
        639
       
       
        return Environment.isExternalStorageRemovable();
       
       
        640
       
       
        }
       
       
        641
       
       
        return true;
       
       
        642
       
       
        }
       
       
        643
       
       
       
       
        644
       
       
        /**
       
       
        645
       
       
        * Get the external app cache directory.
       
       
        646
       
       
        *
       
       
        647
       
       
        * @param context The context to use
       
       
        648
       
       
        * @return The external cache dir
       
       
        649
       
       
        */
       
       
        650
       
       
        @TargetApi(VERSION_CODES.FROYO)
       
       
        651
       
       
        public static File getExternalCacheDir(Context context) {
       
       
        652
       
       
        if (Utils.hasFroyo()) {
       
       
        653
       
       
        return context.getExternalCacheDir();
       
       
        654
       
       
        }
       
       
        655
       
       
       
       
        656
       
       
        // Before Froyo we need to construct the external cache dir ourselves
       
       
        657
       
       
        final String cacheDir = "/Android/data/" + context.getPackageName() + "/cache/";
       
       
        658
       
       
        return new File(Environment.getExternalStorageDirectory().getPath() + cacheDir);
       
       
        659
       
       
        }
       
       
        660
       
       
       
       
        661
       
       
        /**
       
       
        662
       
       
        * Check how much usable space is available at a given path.
       
       
        663
       
       
        *
       
       
        664
       
       
        * @param path The path to check
       
       
        665
       
       
        * @return The space available in bytes
       
       
        666
       
       
        */
       
       
        667
       
       
        @TargetApi(VERSION_CODES.GINGERBREAD)
       
       
        668
       
       
        public static long getUsableSpace(File path) {
       
       
        669
       
       
        if (Utils.hasGingerbread()) {
       
       
        670
       
       
        return path.getUsableSpace();
       
       
        671
       
       
        }
       
       
        672
       
       
        final StatFs stats = new StatFs(path.getPath());
       
       
        673
       
       
        return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
       
       
        674
       
       
        }
       
       
        675
       
       
       
       
        676
       
       
        /**
       
       
        677
       
       
        * Locate an existing instance of this Fragment or if not found, create and
       
       
        678
       
       
        * add it using FragmentManager.
       
       
        679
       
       
        *
       
       
        680
       
       
        * @param fm The FragmentManager manager to use.
       
       
        681
       
       
        * @return The existing instance of the Fragment or the new instance if just
       
       
        682
       
       
        *         created.
       
       
        683
       
       
        */
       
       
        684
       
       
        private static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
       
       
        686
       
       
        // Check to see if we have retained the worker fragment.
       
       
        687
       
       
        RetainFragment mRetainFragment = (RetainFragment) fm.findFragmentByTag(TAG);
       
       
        688
       
       
       
       
        689
       
       
        // If not retained (or first time running), we need to create and add it.
       
       
        690
       
       
        if (mRetainFragment == null) {
       
       
        691
       
       
        mRetainFragment = new RetainFragment();
       
       
        692
       
       
        fm.beginTransaction().add(mRetainFragment, TAG).commitAllowingStateLoss();
       
       
        693
       
       
        }
       
       
        694
       
       
       
       
        695
       
       
        return mRetainFragment;
       
       
        697
       
       
        }
       
       
        698
       
       
       
       
        699
       
       
        /**
       
       
        700
       
       
        * A simple non-UI Fragment that stores a single Object and is retained over configuration
       
       
        701
       
       
        * changes. It will be used to retain the ImageCache object.
       
       
        702
       
       
        */
       
       
        703
       
       
        public static class RetainFragment extends Fragment {
       
       
        704
       
       
        private Object mObject;
       
       
        705
       
       
       
       
        706
       
       
        /**
       
       
        707
       
       
        * Empty constructor as per the Fragment documentation
       
       
        708
       
       
        */
       
       
        709
       
       
        public RetainFragment() {}
       
       
        710
       
       
       
       
        711
       
       
        @Override
       
       
        712
       
       
        public void onCreate(Bundle savedInstanceState) {
       
       
        713
       
       
        super.onCreate(savedInstanceState);
       
       
        714
       
       
       
       
        715
       
       
        // Make sure this Fragment is retained over a configuration change
       
       
        716
       
       
        setRetainInstance(true);
       
       
        717
       
       
        }
       
       
        718
       
       
       
       
        719
       
       
        /**
       
       
        720
       
       
        * Store a single object in this Fragment.
       
       
        721
       
       
        *
       
       
        722
       
       
        * @param object The object to store
       
       
        723
       
       
        */
       
       
        724
       
       
        public void setObject(Object object) {
       
       
        725
       
       
        mObject = object;
       
       
        726
       
       
        }
       
       
        727
       
       
       
       
        728
       
       
        /**
       
       
        729
       
       
        * Get the stored object.
       
       
        730
       
       
        *
       
       
        731
       
       
        * @return The stored object
       
       
        732
       
       
        */
       
       
        733
       
       
        public Object getObject() {
       
       
        734
       
       
        return mObject;
       
       
        735
       
       
        }
       
       
        736
       
       
        }
       
       
        737
       
       
       
       
        738
       
       
        }