package com.aincc.lib.util;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.LinkedList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.widget.AdapterView;
/**
*
* <h3><b>ABitmapLoader</b></h3></br>
*
* 비트맵 이미지 다운로드 관리
*
* @author aincc@barusoft.com
* @version 1.0.0
* @since 1.0.0
*/
@Deprecated
public class BitmapLoader
{
/**
* 스레드 풀 사이즈
*/
private static final int poolSize = 3;
/**
* 캐쉬 보관 개수
*/
private static final int MAX_CACHE_SIZE = 50;
/**
* 키/이미지 캐시 맵
*/
private final ConcurrentHashMap<String, SoftReference<Bitmap>> cache = new ConcurrentHashMap<String, SoftReference<Bitmap>>();
/**
* 다운로드한 이미지를 관리
*/
private final LinkedList<Bitmap> cacheController = new LinkedList<Bitmap>();
/**
* 작업 큐
*/
private final FixedStack<LoadPair> workQueue;
/**
* 어댑터
*/
private final AdapterView<?> adapterView;
/**
* 핸들러
*/
private final Handler handler;
/**
* 스레드풀
*/
private ExecutorService pool;
/**
* 스레드풀 동작여부
*/
private AtomicBoolean isRunning = new AtomicBoolean(true);
/**
* 스레드 우선순위
*/
private static int priority = Thread.NORM_PRIORITY / 2;
/**
* 생성자
*
* @since 1.0.0
* @param parentView
*/
public BitmapLoader(AdapterView<?> parentView)
{
handler = new Handler();
adapterView = parentView;
workQueue = new FixedStack<LoadPair>();
pool = Executors.newFixedThreadPool(poolSize, threadFactory);
for (int ii = 0; ii < poolSize; ii++)
{
pool.execute(runDownloader());
}
}
/**
* 캐쉬 초기화 및 스레드풀 초기화
*
* @since 1.0.0
*/
public void reset()
{
workQueue.clear();
cacheController.clear();
cache.clear();
Logger.i("ABitmapLoader reset");
ExecutorService oldPool = pool;
pool = Executors.newFixedThreadPool(poolSize, threadFactory);
oldPool.shutdownNow();
isRunning.set(false);
}
/**
* 스레드풀 실행
*
* @since 1.0.0
*/
public void start()
{
Logger.i("ABitmapLoader isRunning : " + isRunning.get());
if (!isRunning.get())
{
for (int ii = 0; ii < poolSize; ii++)
{
pool.execute(runDownloader());
}
}
}
/**
* 캐쉬 비트맵 가져오기
*
* @since 1.0.0
* @param key
* @return 비트맵
*/
public Bitmap getBitmap(String key)
{
if (cache.containsKey(key))
{
return cache.get(key).get();
}
return null;
}
/**
* 비트맵 다운로드 요청
*
* @since 1.0.0
* @param loadPair
*/
public void addLoadPair(LoadPair loadPair)
{
addToWorkQueue(loadPair);
}
/**
* 작업큐에 다운로드 요청 추가 및 큐 작업 실행
*
* @since 1.0.0
* @param p
*/
private void addToWorkQueue(LoadPair p)
{
workQueue.add(p);
}
/**
* 비트맵 다운로드
*
* @since 1.0.0
* @param key
* @param url
* @return 다운로드
*/
private Bitmap downloadBitmap(LoadPair pair)
{
Bitmap bitmap = null;
try
{
Logger.i("request downloadBitmap : " + pair.key + " : " + pair.url);
InputStream is = getInputStream(pair.url);
bitmap = BitmapFactory.decodeStream(is);
if (null != bitmap)
{
if (pair.resize)
{
Bitmap resize = null;
try
{
resize = Utils.resizeBitmapImage(bitmap, pair.maxres);
}
catch (OutOfMemoryError e)
{
if (null != resize)
{
resize.recycle();
resize = null;
}
e.printStackTrace();
return null;
}
finally
{
if (null != bitmap)
{
bitmap.recycle();
bitmap = null;
}
}
if (0 < pair.round)
{
resize = Utils.getRoundedCornerBitmap(resize, pair.round, pair.round);
}
putBitmapInCache(pair.key, resize);
return resize;
}
if (0 < pair.round)
{
bitmap = Utils.getRoundedCornerBitmap(bitmap, pair.round, pair.round);
}
putBitmapInCache(pair.key, bitmap);
}
return bitmap;
}
catch (OutOfMemoryError e)
{
if (null != bitmap)
{
bitmap.recycle();
bitmap = null;
}
e.printStackTrace();
}
catch (MalformedURLException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
return null;
}
/**
* 비트맵 캐쉬 저장
*
* @since 1.0.0
* @param key
* @param Bitmap
*/
private synchronized void putBitmapInCache(String key, Bitmap Bitmap)
{
int cacheControllerSize = cacheController.size();
// 캐쉬저장개수를 초과하는 경우 절반을 삭제한다.
if (cacheControllerSize > MAX_CACHE_SIZE)
{
int last = MAX_CACHE_SIZE / 2;
cacheController.subList(0, last).clear();
}
cacheController.addLast(Bitmap);
cache.put(key, new SoftReference<Bitmap>(Bitmap));
}
/**
* 입력스트림 가져오기
*
* @since 1.0.0
* @param urlString
* @return 스트림
* @throws MalformedURLException
* @throws IOException
*/
private InputStream getInputStream(String urlString) throws MalformedURLException, IOException
{
URL url = new URL(urlString);
URLConnection connection;
connection = url.openConnection();
connection.setUseCaches(true);
connection.connect();
InputStream response = connection.getInputStream();
return response;
}
/**
* 작업큐에 등록된 작업내용을 실행한다.
*
* @since 1.0.0
* @return runnable
*/
private Runnable runDownloader()
{
return new Runnable()
{
@Override
public void run()
{
while (true)
{
tryLoadingNextImage();
Thread.yield();
}
}
};
}
/**
* 작업큐의 첫번째 항목을 처리한다.
*
* @since 1.0.0
*/
private void tryLoadingNextImage()
{
final LoadPair pair;
try
{
pair = workQueue.takeFirst();
final Bitmap bmp = downloadBitmap(pair);
// 처리결과를 핸들러를 통해 UI Thread 로 콜백한다.
handler.post(new Runnable()
{
@Override
public void run()
{
callback(pair, bmp);
}
});
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
/**
*
* @since 1.0.0
* @param pair
* @param bmp
* @return boolean
*/
private boolean callback(LoadPair pair, Bitmap bmp)
{
if (null != pair.callback)
{
if (null != bmp)
{
Logger.i("key(" + pair.key + ") download complete");
pair.callback.onLoadSuccess(pair.key);
return true;
}
else
{
Logger.i("key(" + pair.key + ") download failed");
pair.callback.onLoadFailed(pair.key);
return false;
}
}
return false;
}
/**
*
* <h3><b>LoadPair</b></h3></br>
*
* @author aincc@barusoft.com
* @version 1.0.0
* @since 1.0.0
*/
public static class LoadPair
{
public String key;
public String url;
public ILoaderListener callback;
public int round = 0;
public boolean resize = false;
public int maxres = 0;
/**
* 콜백함수를 등록하는 경우
*
* @since 1.0.0
* @param key
* @param url
* @param callback
* @param round
* 라운드
* @param resize
* 리사이즈 여부
* @param maxres
* 리사이즈 시 최대 해상도
*/
public LoadPair(String key, String url, ILoaderListener callback, int round, boolean resize, int maxres)
{
this.key = key;
this.url = url;
this.callback = callback;
this.round = round;
this.resize = resize;
this.maxres = maxres;
}
}
/**
*
* <h3><b>FixedStack</b></h3></br>
*
* @author aincc@barusoft.com
* @version 1.0.0
* @since 1.0.0
* @param <T>
*/
public class FixedStack<T> extends LinkedBlockingDeque<T>
{
private static final long serialVersionUID = 1L;
private static final int KLUDGEFACTOR = 3;
private int maxEntries()
{
return (adapterView.getLastVisiblePosition() - adapterView.getFirstVisiblePosition()) + KLUDGEFACTOR;
}
public FixedStack()
{
}
public boolean add(T x)
{
super.addFirst(x);
this.trimEntries();
return true;
}
public void addFirst(T x)
{
super.addFirst(x);
this.trimEntries();
}
public void addLast(T x)
{
throw new UnsupportedOperationException("addLast not supported by this stack.");
}
/**
* Keeps the stack to a fixed size.
*/
private void trimEntries()
{
int toTrim = super.size() - maxEntries();
if (toTrim > 0)
{
for (int i = 0; i < toTrim; i++)
{
super.pollLast(); // Pop off the end (oldest) entries.
}
}
}
}
/**
* 스레드 팩토리
*/
private static ThreadFactory threadFactory = new ThreadFactory()
{
ThreadFactory tf = Executors.defaultThreadFactory();
@Override
public Thread newThread(final Runnable r)
{
final Thread out = this.tf.newThread(r);
out.setPriority(priority);
return out;
}
};
/**
*
* <h3><b>ILoaderListener</b></h3></br>
*
* @author aincc@barusoft.com
* @version 1.0.0
* @since 1.0.0
*/
public interface ILoaderListener
{
public void onLoadSuccess(String key);
public void onLoadFailed(String key);
}
}