날짜별 글 목록: 2012년 9월 8일

LIFO 구조의 AsyncTask 디스패칭 구현하기..

안드로이드에서 우측의 GridView의 썸네일들을 API 서버에서 받아온다고 가정을 합니다. API 서버를 통해서 각 썸네일을 가져오려면, 보통은 Adapter의 getView()메쏘드에서 썸네일을 가져오는 AsyncTask를 순서대로 실행을 할 것입니다. 순서대로 FIFO의 구조로.. 그렇지만, 터치를 해서 두번 정도만 아래로 내려도, FIFO 구조가 좋지 않은 것은 자명한 일리겠죠.. 화면에 보이지도 않는 썸네일을 가져오는 쓰레드가 Background로 돌고 있겠죠.. 그래서, LIFO 구조로 AsyncTask를 디스패칭하는 넘을 만들어 봤습니다.. 

많은 썸네일을 보여주기 위해서는, 보통 페이징을 합니다. 썸네일을 보려고, 빠르게 터치해서 이동하다 보면, 쓰레드가 많이 생기게 마련이고, 이런경우에 쓰레드 응답문제로 앱이 죽을 수 있습니다.. 물론, THREAD_POOLING을 이용하는 EXECUTOR를 사용한다고 해도, AsyncTask의 과도한 큐잉으로 인해서 죽을 수도 있습니다.. 

그래서, AsyncTask를 중간에 큐잉(화면에 나오지 않을 넘들까지 큐잉할 필요는 없다고 생각함)을 하고, AsyncTask를 디스패칭(엄밀히 말하면, Executor에 넘겨주는..)을 담당하는 매니저를 두는 것으로 간단하게 해결을 합니다. 디스패칭을 담당하는 클래스는 기본적으로 DEQUE를 사용해서 LIFO구조의 QUEUE를 사용합니다.. 

참고로, 구현된 코드의 일부만 발췌를 했기 때문에, 아래의 코드는 컴파일이 안됩니다.. 필요한 부분은 적당한 수정과 추가가 필요합니다..

1. AsyncTaskVO.java

public class AsyncTaskVO {

public Context c;

public AsyncTask<String, Integer, Bitmap> task;

public static AsyncTaskVO newInstance(Context c, AsyncTask<String, Integer, Bitmap> task) {

AsyncTaskVO vo = new AsyncTaskVO();

vo.c = c;

vo.task = task;

return vo;

}

}

2. AsyncTaskDispatcher.java

import java.util.ArrayList;

import java.util.concurrent.LinkedBlockingDeque;

import java.util.concurrent.atomic.AtomicInteger;

public class AsyncTaskDispatcher {

static String CNAME = AsyncTaskDispatcher.class.getSimpleName();

public static AtomicInteger runningTask = new AtomicInteger(0);

static int MAX_SIZE = 72;

static int HALF_MAX_SIZE = 36;

static LinkedBlockingDeque<AsyncTaskVO> queue = new LinkedBlockingDeque<AsyncTaskVO>(MAX_SIZE); 

static ArrayList<Thread> consumers = new ArrayList<Thread>(3);

static {

consumers.add(new Thread(new Consumer(“1”)));

consumers.add(new Thread(new Consumer(“2”)));

consumers.add(new Thread(new Consumer(“3”)));

consumers.get(0).start();

consumers.get(1).start();

consumers.get(2).start();

}

public static void put(AsyncTaskVO e) {

if(e == null)

return;

try {

queue.addFirst(e);

if(queue.size() > HALF_MAX_SIZE) {

AsyncTaskVO taskVo =queue.takeLast();

if(DEBUG)

Logger.w(CNAME, “task deleted ~~” + taskVo);

taskVo = null;

return;

}

if(DEBUG)

Logger.w(CNAME, “task added~~”);

} catch (InterruptedException e1) {

if(DEBUG)

Logger.e(CNAME, ExceptionUtil.getException(e1));

}

}

public static class Consumer implements Runnable {

private String name = “”;

final Object lock = new Object();

public Consumer(String name) {

this.name = name;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

@Override

public void run() {

while(true) {

synchronized (lock) {

try {

// 실행중인 Task가 20개가 넘으면 wait을 한다. 

if(runningTask.intValue() > 20)

lock.wait(200);

if(DEBUG)

Logger.e(CNAME, “running task size = ” + runningTask.intValue());

AsyncTaskVO taskVo = queue.takeFirst();

if(taskVo == null || taskVo.c == null || taskVo.task == null) {

if(DEBUG)

Logger.e(CNAME, “TASK : NULL”);

return;

}

if(!NetworkUtil.isAvailable(taskVo.c)) {

queue.clear();

return;

}

taskVo.task.executeOnExecutor(EXECUTOR, “”);

} catch(Exception e) {

if(DEBUG)

Logger.e(CNAME, ExceptionUtil.getException(e));

}

}

}

}

}

3. GetThumbnailTask.java

public class GetThumbnailTask extends AsyncTask<String, Integer, Bitmap> {

static final String CNAME = GetThumbnailTask.class.getSimpleName();

@Override

public void onPreExecute() {

AsyncTaskDispatcher.runningTask.incrementAndGet();

}

@Override

protected Bitmap doInBackground(String… params) {

// 여기에서 Bitmap 이미지를 가져온다. 

}

@Override

protected void onPostExecute(Bitmap result) {

AsyncTaskDispatcher.runningTask.decrementAndGet();

}

}

LIFO 구조의 AsyncTask 디스패칭을 실행하는 방법은, 위의 GetThumbnailTask를 Adapter에서 생성해서 AsyncTaskDispatcher에 추가만 하면 됩니다.