fr.sedona.volley.manager.HttpImageLoader.java Source code

Java tutorial

Introduction

Here is the source code for fr.sedona.volley.manager.HttpImageLoader.java

Source

/*
 * Copyright (C) 2014Modified by Sedona Paris
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package fr.sedona.volley.manager;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.StatFs;
import android.util.Log;
import android.support.v4.util.LruCache;
import com.android.volley.RequestQueue;
import com.android.volley.VolleyLog;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;
import com.jakewharton.disklrucache.DiskLruCache;

import java.io.BufferedOutputStream;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.ConcurrentHashMap;

public class HttpImageLoader {
    private static final int DEFAULT_CACHE_SIZE = (int) (Runtime.getRuntime().maxMemory()) / 8;
    private static HttpImageLoader instance;
    private ImageLoader imgLoader;
    private ImageLoader imgLoaderPNG;
    private ImageLoader imgLoaderPermanent;

    private RamImageCache ramCache;
    private DiskImageCache diskCache;
    private DiskImageCache diskCachePNG;
    private DiskImageCache diskCachePermanent;
    private WeakRamCache weakCache;
    private RequestQueue mRequestQueue;

    private HttpImageLoader() {

    }

    public ImageLoader getLoader() {
        if (imgLoader == null) {
            Log.e("HttpImageLoader", "Must call initImageLoader method of HttpImageLoader");
        }
        return imgLoader;
    }

    public ImageLoader getLoader(boolean needTransparency, boolean needPermanency) {
        //TODO Freeze  l'appel du cache Permanency:  corriger
        //        if (needPermanency) {
        //            return imgLoaderPermanent;
        //        }

        if (needTransparency) {
            return imgLoaderPNG;
        }
        return getLoader();
    }

    public static HttpImageLoader get() {
        if (instance == null) {
            instance = new HttpImageLoader();
        }
        return instance;
    }

    /**
     * Must be called once at app startup
     * @param ctx
     * @param ratioTransparency part of disk cache reserved for transparent images - 0 mean no transparent images
     */
    public void initImageLoader(Context ctx, int ratioTransparency) {
        initImageLoader(ctx, ratioTransparency, false);
    }

    /**
     * Must be called once at app startup
     * @param ctx
     * @param ratioTransparency part of disk cache reserved for transparent images - 0 mean no transparent images
     */
    public void initImageLoader(Context ctx, float ratioTransparency, boolean hasPermanentImages) {
        ramCache = new RamImageCache();
        weakCache = new WeakRamCache();

        long diskSpace = getDiskCacheSpace(ctx);
        if (ratioTransparency > 1 || ratioTransparency < 0) {
            ratioTransparency = 0;
        }

        // Share the disk space between PNG and JPEG (ratio must be between 0 and 1)
        diskCache = new DiskImageCache(ctx, Bitmap.CompressFormat.JPEG,
                (int) (diskSpace * (1 - ratioTransparency)));
        if (ratioTransparency > 0) {
            diskCachePNG = new DiskImageCache(ctx, Bitmap.CompressFormat.PNG,
                    (int) (diskSpace * ratioTransparency));
        }
        if (hasPermanentImages) {
            diskCachePermanent = new DiskImageCache(ctx, Bitmap.CompressFormat.JPEG, 1024 * 3);
        }

        mRequestQueue = Volley.newRequestQueue(ctx);

        //"Null" and not "this" as second argument: We do not provide cache for Volley - implement it ourself instead, this is faster and synchrone
        imgLoader = new ImageLoader(mRequestQueue, volleyCacheProxy);
        imgLoaderPNG = new ImageLoader(mRequestQueue, volleyCachePNGProxy);
        imgLoaderPermanent = new ImageLoader(mRequestQueue, volleyCachePermanentProxy);
    }

    public void initImageLoader(Context ctx) {
        initImageLoader(ctx, 0);
    }

    private class ProxyCache implements ImageLoader.ImageCache {

        private boolean isPngCache = false;
        private boolean isPermanentCache = false;

        private ProxyCache(boolean isPngCache, boolean isPermanentCache) {
            this.isPngCache = isPngCache;
            this.isPermanentCache = isPermanentCache;
        }

        @Override
        public Bitmap getBitmap(String s) {
            //We getBitmapRam ourselves to do it synchronously - No need to implement it again
            //But we want ROM image to load async:
            if (diskCache != null) {
                if (s.startsWith("#W0#H0"))
                    s = s.substring(6);

                Bitmap bmp = null;
                if (isPngCache) {
                    bmp = diskCachePNG.getBitmap(s);
                } else if (isPermanentCache) {
                    bmp = diskCachePermanent.getBitmap(s);
                } else {
                    bmp = diskCache.getBitmap(s);
                }

                if (bmp != null) {
                    ramCache.putBitmap(s, bmp);
                }
                return bmp;
            }
            return null;
        }

        @Override
        public void putBitmap(String s, Bitmap bitmap) {
            //Remove volley tags
            if (s.startsWith("#W0#H0")) {
                s = s.substring(6);
            }

            ramCache.put(s, bitmap);
            if (isPngCache && diskCachePNG != null) {
                diskCachePNG.putBitmap(s, bitmap);
            } else if (isPermanentCache && diskCachePermanent != null) {
                diskCachePermanent.putBitmap(s, bitmap);
            } else if (!isPngCache && !isPermanentCache && diskCache != null) {
                diskCache.putBitmap(s, bitmap);
            }
        }
    }

    private ImageLoader.ImageCache volleyCacheProxy = new ProxyCache(false, false);
    private ImageLoader.ImageCache volleyCachePNGProxy = new ProxyCache(true, false);
    private ImageLoader.ImageCache volleyCachePermanentProxy = new ProxyCache(false, true);

    public Bitmap getBitmapRam(String url) {
        Bitmap bmp = ramCache.getBitmap(url);

        if (bmp == null) {
            bmp = weakCache.getBitmap(url);
        }
        return bmp;
    }

    public void cacheModifiedBitmap(String url, Bitmap bitmap, boolean isPNG) {
        cacheModifiedBitmap(url, bitmap, isPNG, false);
    }

    public void cacheModifiedBitmap(String url, Bitmap bitmap, boolean isPNG, boolean isPermanent) {
        ramCache.put(url, bitmap);
        if (diskCache != null && !isPNG) {
            diskCache.putBitmap(url, bitmap);
        }
        if (diskCachePNG != null && isPNG) {
            diskCachePNG.putBitmap(url, bitmap);
        }
        if (diskCachePermanent != null && isPermanent) {
            diskCachePermanent.putBitmap(url, bitmap);
        }
    }

    // /////////////////////////////////////////////////////////////////////////////////////
    // ////////////////// GESTION DU CACHE : ROM et RAM /////////////////////////
    // ///////////////////////////////////////////////////////////////////////////////////

    private class WeakRamCache implements ImageLoader.ImageCache {
        private ConcurrentHashMap<String, WeakReference<Bitmap>> sWeakBitmapCache = new ConcurrentHashMap<String, WeakReference<Bitmap>>(
                50);

        @Override
        public Bitmap getBitmap(String url) {
            WeakReference<Bitmap> bitmapReference = sWeakBitmapCache.get(url);

            if (bitmapReference != null) {
                final Bitmap bitmap = bitmapReference.get();
                if (bitmap != null) {
                    // Bitmap found in soft cache
                    return bitmap;
                } else {
                    // Weak reference has been Garbage Collected
                    sWeakBitmapCache.remove(url);
                }
            }
            return null;
        }

        @Override
        public void putBitmap(String url, Bitmap bitmap) {
            sWeakBitmapCache.put(url, new WeakReference<Bitmap>(bitmap));
        }
    }

    private class RamImageCache extends LruCache<String, Bitmap> implements ImageLoader.ImageCache {

        public RamImageCache() {
            super(DEFAULT_CACHE_SIZE);
            if (VolleyLog.DEBUG) {
                Log.d("RamImageCache", "Cache size = " + DEFAULT_CACHE_SIZE);
            }
        }

        @Override
        protected int sizeOf(String key, Bitmap value) {
            int bytes = (value.getRowBytes() * value.getHeight());
            return bytes;
        }

        @Override
        public Bitmap getBitmap(String url) {
            Bitmap bmp = get(url);
            if (bmp != null && bmp.isRecycled()) {
                return null;
            }
            return bmp;
        }

        @Override
        public void putBitmap(String url, Bitmap bitmap) {
            put(url, bitmap);
        }

        @Override
        protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
            if (!evicted) {
                return;
            }
            weakCache.putBitmap(key, oldValue);
            if (diskCache != null) {
                //TODO diskCache.cacheModifiedBitmap: Stack the url to a SdCardSaverPool that asynchronously retrieve the image from ramCache and save it to sdCard - Save it here only if it has not be done previously
                //diskCache.cacheModifiedBitmap(key, oldValue);
            }
        }
    }

    @SuppressLint("NewApi")
    private long getDiskCacheSpace(Context context) {
        String cachePath = context.getCacheDir().getPath();
        File diskCacheDir = new File(cachePath);

        if (Build.VERSION.SDK_INT >= 9) {
            return Math.min(diskCacheDir.getFreeSpace() - (1024 * 1024 * 30), (long) 1024 * 1024 * 20);
        }
        StatFs statFs = new StatFs(diskCacheDir.getAbsolutePath());
        return (long) Math.min(statFs.getAvailableBlocks() * (long) statFs.getBlockSize() - (1024 * 1024 * 30),
                (long) 1024 * 1024 * 20);
    }

    private class DiskImageCache implements ImageLoader.ImageCache {

        private final int IO_BUFFER_SIZE = 8 * 1024;
        private final int APP_VERSION = 1;
        private final int VALUE_COUNT = 1;
        private DiskLruCache mDiskCache;
        private Bitmap.CompressFormat mCompressFormat;
        private int mCompressQuality = 75;

        public DiskImageCache(Context context, Bitmap.CompressFormat format, int diskCache) {
            mCompressFormat = format;

            if (context.getCacheDir() == null) {
                return;
            }
            try {
                String cachePath = context.getCacheDir().getPath();
                File diskCacheDir = new File(cachePath + File.separator + "img");
                diskCacheDir.mkdirs();

                // Prend 20Mo de prfrence, en laissant toujours au moins 30Mo de libre  Android
                long diskCacheSize = diskCache <= 0 ? (getDiskCacheSpace(context)) : diskCache;
                if (VolleyLog.DEBUG) {
                    Log.d("DiskImageCache", "Cache size = " + diskCacheSize);
                }

                if (diskCacheSize > 0) {
                    mDiskCache = DiskLruCache.open(diskCacheDir, APP_VERSION, VALUE_COUNT, diskCacheSize);
                } else {
                    if (VolleyLog.DEBUG) {
                        Log.w("DiskImageCache", "Available space is null! No Hard Cache");
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        private boolean writeBitmapToFile(Bitmap bitmap, DiskLruCache.Editor editor)
                throws IOException, FileNotFoundException {
            OutputStream out = null;
            try {
                out = new BufferedOutputStream(editor.newOutputStream(0), IO_BUFFER_SIZE);
                return bitmap.compress(mCompressFormat, mCompressQuality, out);
            } finally {
                if (out != null) {
                    out.close();
                }
            }
        }

        @Override
        public void putBitmap(String key, Bitmap data) {
            if (mDiskCache == null) {
                return;
            }
            DiskLruCache.Editor editor = null;
            try {
                editor = mDiskCache.edit(md5(key));
                if (editor == null) {
                    return;
                }

                if (writeBitmapToFile(data, editor)) {
                    mDiskCache.flush();
                    editor.commit();
                } else {
                    editor.abort();
                }
            } catch (IOException e) {
                try {
                    if (editor != null) {
                        editor.abort();
                    }
                } catch (IOException ignored) {
                }
            }

        }

        @Override
        public Bitmap getBitmap(String key) {
            if (mDiskCache == null) {
                return null;
            }

            Bitmap bitmap = null;
            DiskLruCache.Snapshot snapshot = null;

            try {
                snapshot = mDiskCache.get(md5(key));
                if (snapshot == null) {
                    return null;
                }
                final InputStream in = snapshot.getInputStream(0);
                if (in != null) {
                    final BufferedInputStream buffIn = new BufferedInputStream(in, IO_BUFFER_SIZE);
                    bitmap = BitmapFactory.decodeStream(buffIn);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (snapshot != null) {
                    snapshot.close();
                }
            }

            return bitmap;
        }

        public void clearCache() {
            try {
                mDiskCache.delete();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static String md5(String s) {
        if (s == null) {
            return null;
        }
        try {
            byte messageDigest[] = MessageDigest.getInstance("MD5").digest(s.getBytes());
            // Create Hex String
            BigInteger bi = new BigInteger(1, messageDigest);
            String result = bi.toString(16);
            if (result.length() % 2 != 0)
                result = (new StringBuilder("0")).append(result).toString();

            return result;

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";
    }

    public static String md5(ByteArrayOutputStream s) {
        if (s == null) {
            return null;
        }
        try {
            byte messageDigest[] = MessageDigest.getInstance("MD5").digest(s.toByteArray());
            // Create Hex String
            BigInteger bi = new BigInteger(1, messageDigest);
            String result = bi.toString(16);
            if (result.length() % 2 != 0)
                result = (new StringBuilder("0")).append(result).toString();

            return result;

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";
    }
}