package com.aviary.android.feather.async_tasks;
import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Message;
import android.widget.ImageView;
import com.aviary.android.feather.library.log.LoggerFactory;
import com.aviary.android.feather.library.log.LoggerFactory.Logger;
import com.aviary.android.feather.library.log.LoggerFactory.LoggerType;
import com.aviary.android.feather.utils.SimpleBitmapCache;
/**
* Load an internal asset asynchronous.
*
* @author alessandro
*/
public class AsyncImageManager {
public static interface OnImageLoadListener {
public void onLoadComplete( ImageView view, Bitmap bitmap, int tag );
}
private static final int THUMBNAIL_LOADED = 1;
private volatile Boolean mStopped = false;
private final int nThreads;
/** thread pool */
private final PoolWorker[] threads;
/** The current runnable queue. */
private final LinkedList<MyRunnable> mQueue;
private SimpleBitmapCache mBitmapCache;
private OnImageLoadListener mListener;
private static Handler mHandler;
private Logger logger = LoggerFactory.getLogger( "AsyncImageManager", LoggerType.ConsoleLoggerType );
public AsyncImageManager() {
this( 2 );
}
public AsyncImageManager( int nthreads ) {
mBitmapCache = new SimpleBitmapCache();
nThreads = Math.min( 5, Math.max( 1, nthreads ) );
mQueue = new LinkedList<MyRunnable>();
threads = new PoolWorker[nThreads];
mHandler = new MyHandler( this );
for ( int i = 0; i < nThreads; i++ ) {
threads[i] = new PoolWorker();
threads[i].start();
}
mListener = null;
}
public void setOnLoadCompleteListener( OnImageLoadListener listener ) {
mListener = listener;
}
private static class MyHandler extends Handler {
WeakReference<AsyncImageManager> mParent;
public MyHandler( AsyncImageManager parent ) {
mParent = new WeakReference<AsyncImageManager>( parent );
}
@Override
public void handleMessage( Message msg ) {
switch ( msg.what ) {
case AsyncImageManager.THUMBNAIL_LOADED:
Thumb thumb = (Thumb) msg.obj;
AsyncImageManager parent = mParent.get();
ImageView view = thumb.image.get();
Bitmap bitmap = thumb.bitmap != null ? thumb.bitmap.get() : null;
if ( null != parent ) {
if ( parent.mListener != null ) {
if ( null != view ) {
parent.mListener.onLoadComplete( view, bitmap, thumb.tag );
}
return;
}
}
if ( view != null && bitmap != null ) {
view.setImageBitmap( bitmap );
}
break;
}
}
}
/**
* Shut down now.
*/
public void shutDownNow() {
logger.info( "shutDownNow" );
mStopped = true;
mHandler = null;
synchronized ( mQueue ) {
mQueue.clear();
mQueue.notify();
}
clearCache();
for ( int i = 0; i < nThreads; i++ ) {
threads[i] = null;
}
}
/**
* The Class MyRunnable.
*/
private abstract class MyRunnable implements Runnable {
/** The view. */
public WeakReference<ImageView> view;
/**
* Instantiates a new my runnable.
*
* @param image
* the image
*/
public MyRunnable( ImageView image ) {
this.view = new WeakReference<ImageView>( image );
}
};
public void execute( final Callable<Bitmap> executor, final String hash, final ImageView view ) {
execute( executor, hash, view, -1 );
}
/**
* Retrive the bitmap either using the internal cache or executing the passed {@link Callable} instance.
*
* @param executor
* - the executor
* @param hash
* - the unique hash used to store/retrieve the bitmap from the cache
* @param view
* - the final {@link ImageView} where the bitmap will be shown
* @param tag
* - a custom tag
*/
public void execute( final Callable<Bitmap> executor, final String hash, final ImageView view, final int tag ) {
if ( mStopped ) return;
mBitmapCache.resetPurgeTimer();
runTask( new MyRunnable( view ) {
@Override
public void run() {
if ( mStopped ) return;
Message message = Message.obtain();
Bitmap bitmap = mBitmapCache.getBitmapFromCache( hash );
if ( bitmap != null ) {
message.what = THUMBNAIL_LOADED;
message.obj = new Thumb( bitmap, view.get(), tag );
} else {
try {
bitmap = executor.call();
} catch ( Exception e ) {
e.printStackTrace();
// TODO: a fallback here???
return;
}
if ( bitmap != null ) mBitmapCache.addBitmapToCache( hash, bitmap );
ImageView imageView = view.get();
if ( imageView != null ) {
MyRunnable bitmapTask = getBitmapTask( imageView );
if ( this == bitmapTask ) {
imageView.setTag( null );
message.what = THUMBNAIL_LOADED;
message.obj = new Thumb( bitmap, imageView, tag );
} else {
logger.error( "image tag is different than current task!" );
}
}
}
if ( message.what == THUMBNAIL_LOADED ) mHandler.sendMessage( message );
}
} );
}
/**
* Run task.
*
* @param task
* the task
*/
private void runTask( MyRunnable task ) {
synchronized ( mQueue ) {
Iterator<MyRunnable> iterator = mQueue.iterator();
while ( iterator.hasNext() ) {
MyRunnable current = iterator.next();
ImageView image = current.view.get();
if ( image == null ) {
iterator.remove();
} else {
if ( image.equals( task.view.get() ) ) {
current.view.get().setTag( null );
iterator.remove();
break;
}
}
}
task.view.get().setTag( new CustomTag( task ) );
mQueue.add( task );
mQueue.notify();
}
}
/**
* The Class PoolWorker.
*/
private class PoolWorker extends Thread {
@Override
public void run() {
Runnable r;
while ( mStopped != true ) {
synchronized ( mQueue ) {
while ( mQueue.isEmpty() ) {
if ( mStopped ) break;
try {
mQueue.wait();
} catch ( InterruptedException ignored ) {}
}
try {
r = (Runnable) mQueue.removeFirst();
} catch ( NoSuchElementException e ) {
// queue is empty
break;
}
}
try {
r.run();
} catch ( RuntimeException e ) {
logger.error( e.getMessage() );
}
}
}
}
/**
* The Class CustomTag.
*/
static class CustomTag {
/** The task reference. */
private final WeakReference<MyRunnable> taskReference;
/**
* Instantiates a new custom tag.
*
* @param task
* the task
*/
public CustomTag( MyRunnable task ) {
super();
taskReference = new WeakReference<MyRunnable>( task );
}
/**
* Gets the downloader task.
*
* @return the downloader task
*/
public MyRunnable getDownloaderTask() {
return taskReference.get();
}
}
/**
* Gets the bitmap task.
*
* @param imageView
* the image view
* @return the bitmap task
*/
private static MyRunnable getBitmapTask( ImageView imageView ) {
if ( imageView != null ) {
Object tag = imageView.getTag();
if ( tag instanceof CustomTag ) {
CustomTag runnableTag = (CustomTag) tag;
return runnableTag.getDownloaderTask();
}
}
return null;
}
/**
* Clears the image cache used internally to improve performance. Note that for memory efficiency reasons, the cache will
* automatically be cleared after a certain inactivity delay.
*/
public void clearCache() {
mBitmapCache.clearCache();
}
/**
* The Class Thumb.
*/
static class Thumb {
public WeakReference<Bitmap> bitmap;
public WeakReference<ImageView> image;
public final int tag;
public Thumb( Bitmap bmp, ImageView img ) {
this( bmp, img, -1 );
}
public Thumb( Bitmap bmp, ImageView img, int ntag ) {
image = new WeakReference<ImageView>( img );
bitmap = new WeakReference<Bitmap>( bmp );
tag = ntag;
}
}
}