Java tutorial
/* * Copyright (C) 2012 - 2013 Ohso Ltd * * 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 com.ohso.util; import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.net.MalformedURLException; import java.net.URL; import java.util.Locale; import android.app.Activity; import android.app.ActivityManager; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.TransitionDrawable; import android.support.v4.util.LruCache; import android.util.Log; import android.util.TypedValue; import android.widget.ImageView; import android.widget.RelativeLayout; import com.jakewharton.DiskLruCache; import com.jakewharton.DiskLruCache.Editor; import com.jakewharton.DiskLruCache.Snapshot; import com.ohso.omgubuntu.OMGUbuntuApplication; /* * Some bits taken from the Android BitmapFun project found here: * http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html * Notably the pausing/cancelling functionality. */ public class ImageHandler { private static final boolean DEBUG_LOG = false; private final int MEMORY_CLASS; private final int MEMORY_CACHE_SIZE; private final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10 MB private final CompressFormat IMAGE_COMPRESS_FORMAT = Bitmap.CompressFormat.JPEG; private final int IMAGE_COMPRESS_QUALITY = 75; private final int IMAGE_DIMENSIONS = 75; private static Resources mResources = OMGUbuntuApplication.getContext().getResources(); // Pausing, cancelling, and locking private final Object mPauseWorkLock = new Object(); private boolean mPauseWork = false; private boolean mExitTaskEarly = false; private float mPixelSize; private RelativeLayout.LayoutParams mPlaceholderParams; private DiskLruCache mDiskCache; private LruCache<String, Bitmap> mMemoryCache; public ImageHandler(Activity activity) { MEMORY_CLASS = ((ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass(); MEMORY_CACHE_SIZE = 1024 * 1024 * MEMORY_CLASS / 8; // Use 1/8 the available memory mMemoryCache = new LruCache<String, Bitmap>(MEMORY_CACHE_SIZE) { protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getRowBytes() * bitmap.getHeight(); } }; File cacheDir = new File(activity.getCacheDir(), "thumbnails"); try { mDiskCache = DiskLruCache.open(cacheDir, 1, 1, DISK_CACHE_SIZE); } catch (IOException e) { e.printStackTrace(); } mPixelSize = scaledDIP(IMAGE_DIMENSIONS); mPlaceholderParams = new RelativeLayout.LayoutParams((int) mPixelSize, (int) mPixelSize); mPlaceholderParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); mPlaceholderParams.topMargin = 0; } public static int scaledDIP(int dip) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, mResources.getDisplayMetrics()); } public void closeCache() { try { mDiskCache.close(); } catch (IOException e) { e.printStackTrace(); } } public void getImage(String url, ImageView imageView, Bitmap placeholder) { if (url == null) { imageView.setLayoutParams(mPlaceholderParams); imageView.setImageBitmap(placeholder); return; } Bitmap bitmap = null; bitmap = mMemoryCache.get(getKey(url)); if (bitmap != null) { imageView.setImageBitmap(bitmap); } else if (cancelPotentialWork(getKey(url), imageView)) { final ImageGetter imageGetter = new ImageGetter(imageView, url); final AsyncDrawable drawable = new AsyncDrawable(mResources, placeholder, imageGetter); imageView.setImageDrawable(drawable); imageGetter.executeOnExecutor(AsyncTask.DUAL_THREAD_EXECUTOR); } } private String getKey(String url) { try { String key = new URL(url).getPath().toLowerCase(Locale.ENGLISH).replaceAll("[^a-z^0-9^_]", "_"); if (key.length() > 64) return key.substring(0, 63); return key; } catch (MalformedURLException e) { e.printStackTrace(); return null; } } private class ImageGetter extends AsyncTask<Void, Void, Bitmap> { private final WeakReference<ImageView> mImageView; private String mUrl; private final String mFullUrl; private ImageGetter(ImageView imageView, String url) { mImageView = new WeakReference<ImageView>(imageView); mFullUrl = url; } @Override protected Bitmap doInBackground(Void... params) { if (DEBUG_LOG) Log.d("OMG!", "Starting background work"); Bitmap imageBitmap = null; mUrl = getKey(mFullUrl); synchronized (mPauseWorkLock) { while (mPauseWork && !isCancelled()) { try { mPauseWorkLock.wait(); } catch (InterruptedException e) { } } } // From disk if (!isCancelled() && getAttachedImageView() != null && !mExitTaskEarly) { imageBitmap = getBitmapFromDisk(); } // From url if (imageBitmap == null && !isCancelled() && getAttachedImageView() != null && !mExitTaskEarly) { imageBitmap = getBitmapFromUrl(); } // Put into memory cache if (imageBitmap != null) mMemoryCache.put(mUrl, imageBitmap); return imageBitmap; } @Override protected void onPostExecute(Bitmap result) { if (isCancelled() || mExitTaskEarly) { result = null; } final ImageView finalView = getAttachedImageView(); if (finalView != null && result != null) { final TransitionDrawable trans = new TransitionDrawable(new Drawable[] { new ColorDrawable(android.R.color.transparent), new BitmapDrawable(mResources, result) }); finalView.setImageDrawable(trans); trans.startTransition(250); } } @Override protected void onCancelled() { super.onCancelled(); synchronized (mPauseWorkLock) { mPauseWorkLock.notifyAll(); } } private Bitmap getBitmapFromUrl() { InputStream is = null; try { URL url = new URL(mFullUrl); is = url.openConnection().getInputStream(); final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; Bitmap bitmap = BitmapFactory.decodeStream(is, null, options); options.inSampleSize = calculateInSampleSize(options, (int) mPixelSize, (int) mPixelSize); // reset() isn't always supported. if (is.markSupported()) is.reset(); else is = url.openConnection().getInputStream(); options.inJustDecodeBounds = false; bitmap = BitmapFactory.decodeStream(is, null, options); is.close(); if (isCancelled() || getAttachedImageView() == null || mExitTaskEarly) { return null; } try { Editor editor = mDiskCache.edit(mUrl); BufferedOutputStream stream = new BufferedOutputStream(editor.newOutputStream(0)); if (isCancelled() || getAttachedImageView() == null || mExitTaskEarly || bitmap == null) { return null; } if (bitmap.compress(IMAGE_COMPRESS_FORMAT, IMAGE_COMPRESS_QUALITY, stream)) { if (DEBUG_LOG) Log.d("OMG!", "Adding image to disk"); editor.commit(); mDiskCache.flush(); stream.close(); } else { if (DEBUG_LOG) Log.d("OMG!", "ABORTING"); editor.abort(); stream.close(); } } catch (IOException e) { e.printStackTrace(); } catch (NullPointerException e) { Log.d("OMG!", "NullPointerException: " + e); } if (DEBUG_LOG) Log.d("OMG!", "Wrote image for " + mUrl); return bitmap; } catch (IOException e) { e.printStackTrace(); return null; } } private Bitmap getBitmapFromDisk() { InputStream inputStream = null; try { final Snapshot diskCachedSnapshot = mDiskCache.get(mUrl); if (diskCachedSnapshot != null) { inputStream = diskCachedSnapshot.getInputStream(0); if (inputStream != null) { final Bitmap bitmap = BitmapFactory.decodeStream(inputStream); diskCachedSnapshot.close(); return bitmap; } } } catch (IOException e) { e.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { } } } return null; } private ImageView getAttachedImageView() { final ImageView imageView = mImageView.get(); final ImageGetter imageGetter = getImageGetter(imageView); if (this == imageGetter) { return imageView; } return null; } } public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { if (width > height) { inSampleSize = Math.round((float) height / (float) reqHeight); } else { inSampleSize = Math.round((float) width / (float) reqWidth); } } return inSampleSize; } public void setExitTasksEarly(boolean exit) { mExitTaskEarly = exit; } public void setPauseWork(boolean pause) { synchronized (mPauseWorkLock) { mPauseWork = pause; if (!mPauseWork) { if (DEBUG_LOG) Log.i("OMG!", "Resuming work"); mPauseWorkLock.notifyAll(); } } } public static void cancelWork(ImageView imageView) { final ImageGetter imageGetter = getImageGetter(imageView); if (imageGetter != null) { imageGetter.cancel(true); if (DEBUG_LOG) Log.i("OMG!", "Cancelled work!"); } } private static ImageGetter getImageGetter(ImageView imageView) { if (imageView != null) { final Drawable drawable = imageView.getDrawable(); if (drawable instanceof AsyncDrawable) { final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; return asyncDrawable.getImageGetter(); } } return null; } public static boolean cancelPotentialWork(String url, ImageView imageView) { final ImageGetter imageGetter = getImageGetter(imageView); if (imageGetter != null) { final String fullUrl = imageGetter.mFullUrl; if (fullUrl == null || !fullUrl.equals(url)) { imageGetter.cancel(true); if (DEBUG_LOG) Log.i("OMG!", "Cancelled potential work"); } else { // Not potential, but already occurring. if (DEBUG_LOG) Log.i("OMG!", "Work in progress!"); return false; } } return true; } private static class AsyncDrawable extends BitmapDrawable { private final WeakReference<ImageGetter> imageGetterReference; public AsyncDrawable(Resources res, Bitmap bitmap, ImageGetter imageGetter) { super(res, bitmap); imageGetterReference = new WeakReference<ImageGetter>(imageGetter); } public ImageGetter getImageGetter() { return imageGetterReference.get(); } } public void flush() { try { mDiskCache.flush(); } catch (IOException e) { } } }