com.nttec.everychan.cache.BitmapCache.java Source code

Java tutorial

Introduction

Here is the source code for com.nttec.everychan.cache.BitmapCache.java

Source

/*
 * Everychan Android (Meta Imageboard Client)
 * Copyright (C) 2014-2016  miku-nyan <https://github.com/miku-nyan>
 *     
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.nttec.everychan.cache;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.Executor;

import com.nttec.everychan.api.ChanModule;
import com.nttec.everychan.api.interfaces.CancellableTask;
import com.nttec.everychan.common.IOUtils;
import com.nttec.everychan.common.Logger;
import com.nttec.everychan.common.MainApplication;
import com.nttec.everychan.containers.ReadableContainer;
import com.nttec.everychan.lib.base64.Base64;
import com.nttec.everychan.ui.downloading.DownloadingService;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.support.v4.util.LruCache;
import android.widget.ImageView;

/**
 *  ( -> ? ?? -> ? ?) ? ?  (Bitmap Cache)
 * @author miku-nyan
 *
 */
public class BitmapCache {

    private static final String TAG = "BitmapCache";

    private final LruCache<String, Bitmap> lru;
    private final FileCache fileCache;
    private final Set<String> currentDownloads;

    private static final Bitmap EMPTY_BMP = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);

    /**
     * ?
     * @param maxSize ?  ?  ?  
     * @param fileCache   ?
     */
    public BitmapCache(int maxSize, FileCache fileCache) {
        this.fileCache = fileCache;
        this.lru = new LruCache<String, Bitmap>(maxSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight();
            }
        };
        this.currentDownloads = new HashSet<String>();
    }

    /**
     * ??    ?  ?
     * @param hash ? ( ? )
     * @return Bitmap ? ,  null, ? ??  ?
     */
    public Bitmap getFromMemory(String hash) {
        return lru.get(hash);
    }

    /**
     * ? LRU-?  ? (  ?  ?)
     */
    public void clearLru() {
        lru.evictAll();
    }

    /**
     * , ??    ?
     * @param hash ? ( ? )
     * @return true, ?   
     */
    public boolean isInCache(String hash) {
        if (getFromMemory(hash) != null)
            return true;
        File file = fileCache.get(FileCache.PREFIX_BITMAPS + hash);
        if (file != null && file.exists())
            return true;
        return false;
    }

    /**
     * ??    ? (? ??  ?,  ? ??? -   ?) 
     * @param hash ? ( ? )
     * @return Bitmap ? ,  null, ? ??   ?,    ?
     */
    public Bitmap getFromCache(String hash) {
        Bitmap fromLru = getFromMemory(hash);
        if (fromLru != null)
            return fromLru;

        synchronized (currentDownloads) {
            if (currentDownloads.contains(hash))
                return null;
        }

        InputStream fileStream = null;
        Bitmap bmp = null;
        try {
            File file = fileCache.get(FileCache.PREFIX_BITMAPS + hash);
            if (file == null || !file.exists()) {
                return null;
            }
            fileStream = new FileInputStream(file);
            bmp = BitmapFactory.decodeStream(fileStream);
        } catch (Exception e) {
            Logger.e(TAG, e);
        } catch (OutOfMemoryError oom) {
            MainApplication.freeMemory();
            Logger.e(TAG, oom);
        } finally {
            IOUtils.closeQuietly(fileStream);
        }
        if (bmp != null)
            lru.put(hash, bmp);
        return bmp;
    }

    /**
     * ??     - (? )
     * @param hash ? ( ? )
     * @param container - - ? 
     * @return Bitmap ? ,  null, ? ??  
     */
    public Bitmap getFromContainer(String hash, ReadableContainer container) {
        Bitmap bmp = getFromMemory(hash);
        if (bmp != null)
            return bmp;
        if (container == null)
            return null;

        for (String fileFormatInContainer : new String[] { DownloadingService.THUMBNAIL_FILE_FORMAT,
                DownloadingService.ICON_FILE_FORMAT }) {
            String filenameInContainer = String.format(Locale.US, fileFormatInContainer, hash);
            if (bmp == null && container.hasFile(filenameInContainer)) {
                InputStream is = null;
                try {
                    is = container.openStream(filenameInContainer);
                    bmp = BitmapFactory.decodeStream(is);
                    if (bmp != null)
                        lru.put(hash, bmp);
                } catch (Exception e) {
                    Logger.e(TAG, e);
                    bmp = null;
                } catch (OutOfMemoryError oom) {
                    MainApplication.freeMemory();
                    Logger.e(TAG, oom);
                    bmp = null;
                } finally {
                    IOUtils.closeQuietly(is);
                }
            }
        }
        return bmp;
    }

    /**
     *   ?  ?   
     * @param hash ? ( ? )
     * @param url ? URL (?  ? )
     * @param maxSize ?   ??,     ,  0, ? ?? ?  ?
     * @param chan   ? 
     * @param task ?? 
     * @return Bitmap ? ,  null, ?   ?
     */
    public Bitmap download(String hash, String url, int maxSize, ChanModule chan, CancellableTask task) {
        if (hash == null) {
            Logger.e(TAG, "received null hash; url: " + url);
            return null;
        }

        synchronized (currentDownloads) {
            while (currentDownloads.contains(hash)) {
                try {
                    currentDownloads.wait();
                } catch (Exception e) {
                    Logger.e(TAG, e);
                }
            }
            currentDownloads.add(hash);
        }

        try {
            Bitmap bmp = getFromCache(hash);
            if (bmp != null)
                return bmp;
            try {
                class BufOutputStream extends ByteArrayOutputStream {
                    public BufOutputStream() {
                        super(1024);
                    }

                    public InputStream toInputStream() {
                        return new ByteArrayInputStream(buf, 0, count);
                    }
                }
                BufOutputStream data = new BufOutputStream();
                chan.downloadFile(url, data, null, task);
                bmp = BitmapFactory.decodeStream(data.toInputStream());
            } catch (Exception e) {
                Logger.e(TAG, e);
                if (url.startsWith("data:image")) {
                    try {
                        byte[] data = Base64.decode(url.substring(url.indexOf("base64,") + 7), Base64.DEFAULT);
                        bmp = BitmapFactory.decodeByteArray(data, 0, data.length);
                    } catch (Exception e1) {
                        Logger.e(TAG, e1);
                    }
                }
            }
            if (bmp == null || (task != null && task.isCancelled())) {
                return null;
            }

            if (maxSize > 0) { // 
                double scale = (double) maxSize / Math.max(bmp.getWidth(), bmp.getHeight());
                if (scale < 1.0) {
                    int width = (int) (bmp.getWidth() * scale);
                    int height = (int) (bmp.getHeight() * scale);
                    if (Math.min(width, height) > 0) {
                        Bitmap scaled = Bitmap.createScaledBitmap(bmp, width, height, true);
                        bmp.recycle();
                        bmp = scaled;
                    }
                }
            }

            OutputStream fileStream = null;
            File file = null;
            boolean success = true;
            try {
                lru.put(hash, bmp);
                file = fileCache.create(FileCache.PREFIX_BITMAPS + hash);
                fileStream = new FileOutputStream(file);
                if (!bmp.compress(Bitmap.CompressFormat.PNG, 100, fileStream)) {
                    throw new Exception();
                }
                fileStream.close();
                fileCache.put(file);
                fileStream = null;
            } catch (Exception e) {
                success = false;
                Logger.e(TAG, e);
            } finally {
                IOUtils.closeQuietly(fileStream);
                if (!success && file != null)
                    fileCache.abort(file);
            }
            return bmp;

        } catch (OutOfMemoryError oom) {
            MainApplication.freeMemory();
            Logger.e(TAG, oom);
            return null;

        } finally {
            synchronized (currentDownloads) {
                currentDownloads.remove(hash);
                currentDownloads.notifyAll();
            }
        }
    }

    /**
     *   ?  ?  ImageView.<br>
     * ? ImageView ??? ?:<ul>
     * <li>? ? ? ,  ?  (? ??,   )</li>
     * <li> {@link Boolean#TRUE},    ?</li>
     * <li> {@link Boolean#FALSE},      ( ?   downloadFromInternet == false)</li></ul>
     * @param hash ? ( ? )
     * @param url ? URL (?  ?)
     * @param maxSize ?   ??,     ,  0, ? ?? ?  ?
     * @param chan   ? ??   
     * @param zipFile - - ?  ? ?  (  null)
     * @param task ?? 
     * @param imageView  {@link ImageView},    
     * @param executor ? ?
     * @param handler Handler UI 
     * @param downloadFromInternet     
     * @param defaultResId ID ?? ?  , ?    ( ?  downloadFromInternet == false),
     *  0 - ?    
     */
    public void asyncGet(String hash, String url, int maxSize, ChanModule chan, ReadableContainer zipFile,
            CancellableTask task, ImageView imageView, Executor executor, Handler handler,
            boolean downloadFromInternet, int defaultResId) {
        if (hash == null) {
            Logger.e(TAG, "received null hash for url: " + url);
            imageView.setTag(Boolean.FALSE);
            imageView.setImageResource(defaultResId);
            return;
        }
        Bitmap fromLru = getFromMemory(hash);
        if (fromLru != null) {
            imageView.setTag(Boolean.TRUE);
            imageView.setImageBitmap(fromLru);
            return;
        } else {
            imageView.setImageBitmap(EMPTY_BMP);
        }
        class ImageDownloader implements Runnable {
            private final String hash;
            private final String url;
            private final int maxSize;
            private final ChanModule chan;
            private final ReadableContainer zipFile;
            private final CancellableTask task;
            private final ImageView imageView;
            private final Handler handler;
            private final boolean downloadFromInternet;
            private final int defaultResId;

            public ImageDownloader(String hash, String url, int maxSize, ChanModule chan, ReadableContainer zipFile,
                    CancellableTask task, ImageView imageView, Handler handler, boolean downloadFromInternet,
                    int defaultResId) {
                this.hash = hash;
                this.url = url;
                this.maxSize = maxSize;
                this.chan = chan;
                this.zipFile = zipFile;
                this.task = task;
                this.imageView = imageView;
                this.handler = handler;
                this.downloadFromInternet = downloadFromInternet;
                this.defaultResId = defaultResId;
            }

            private Bitmap bmp;

            @Override
            public void run() {
                bmp = getFromCache(hash);
                if (bmp == null && zipFile != null)
                    bmp = getFromContainer(hash, zipFile);
                if (bmp == null && downloadFromInternet) {
                    bmp = download(hash, url, maxSize, chan, task);
                }
                if (task != null && task.isCancelled())
                    return;
                if (imageView.getTag() == null || !imageView.getTag().equals(hash))
                    return;
                if (bmp == null) {
                    if (defaultResId == 0) {
                        imageView.setTag(Boolean.FALSE);
                        return;
                    }
                }
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            if (imageView.getTag() == null || !imageView.getTag().equals(hash))
                                return;
                            if (bmp != null) {
                                imageView.setTag(Boolean.TRUE);
                                imageView.setImageBitmap(bmp);
                            } else {
                                imageView.setTag(Boolean.FALSE);
                                imageView.setImageResource(defaultResId);
                            }
                        } catch (OutOfMemoryError oom) {
                            MainApplication.freeMemory();
                            Logger.e(TAG, oom);
                        }
                    }
                });
            }
        }
        if (task != null && task.isCancelled())
            return;
        imageView.setTag(hash);
        executor.execute(new ImageDownloader(hash, url, maxSize, chan, zipFile, task, imageView, handler,
                downloadFromInternet, defaultResId));
    }

}