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); } }