Java tutorial
/* * 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)); } }