쓰레드 (Thread)


Thread 생성 기법

Thread를 생성하는 방법은 아래 2가지 이다.

Thread()
Thread(Runnable runnable)

첫 번째 방법인 Thread의 경우 run에 해당 기능을 구현한다.

// Activity

BackThread thread = new BackThread():
thread.setDaemon(true);
thread.start();

class BackThread extends Thread {
	public void run(){
		while(true){
			mValue++;
			try {Thread.sleep(1000);} catch {InterruptedException e){;}
		}
	}
}

두 번째 방법인 Runnable은 interface 내부에 run을 구현 하는 방식이다.

Back Runnable runnable = new BackRunnable();
Thread thread = new Thread(runnable);
thread.setDeamon(true); // main thread가 종료 될 때만 종료된다 라는 의미이다.
thread.start();

class BackRunnable implements Runnable {
	public void run(){
		while(true){
			mBackValue ++;
			try {Thread.sleep(1000);} catch (interruptedException e) {;}
		}
	}
}

구지 복잡하게 자바에서 Runnable Interface를 지원하는 이유는 다중상속을 지원하지 않는 특성상
Thread만 구현 하고 싶을 때 Runnable을 이용하라는 의미이다.

Thread간의 통신 Handler

Handler 클래스로 표현되는 핸들러는 Tread간에 메시지 Runnable 객체를 통해 메시지를 주고 받는 장치이다.

  • 핸들러는 생성하는 Thread에 부착 된다. 부착된 Thread의 메시지큐를 통해서 다른 Thread와 통신 한다.
  • Handler는 Message를 MessageQueue에 넣는 기능과 MessageQueue에서 꺼내 처리하는 기능을 함께 제공한다.

Handler 생성자 이해

  • Handler()
  • Handler(Handler.Callback callback)
  • Handler(Looper looper)
  • Handler(Looper looper, Handler.Callback callback)

메시지 수신에 사용되는 메서드

  • void handleMessage (Message msg)
    인수 Message는 아래와 같은 추가 필드를 가진다.
  • int what: 메시지의 의미를 설명한다. 의미가 정해져 있지는 않으며 핸들러별로 지역적이므로 다른 핸들러와 충돌할 위험은 없다.
  • int arg1: 메시지의 추가 정보이다.
  • int arg2: 메시지의 추가 정보이다.
  • Object obj: 정수만으로 메시지를 기술할 수 없을 때 임의의 객체를 보낸다.
  • Messenger replyTo: 메시지에 대한 응답을 받을 객체를 지정한다.

메시지를 보내는 쪽에서는 전달하고자 하는 내용을 Message 객체에 저장하여 핸들러로 전송한다.
사용되는 메서드는 다음과 같다.

  • boolean Handler.sendEmptyMessage (int what)
  • boolean Handler.sendMessage (Message msg)
  • boolean Handler.sendEmptyMessageAtTime(int what, long uptimeMillis)
  • boolean Handler.sendMessageAtTime(Message msg, long uptimeMillis)
  • boolean sendMessageAtFrontOfQueue (Message msg)

실행 코드인 Runnable object를 전송하는 메서드는 다음과 같다.

  • boolean Handler.post(Runnable r)
  • boolean Handler.postDelayed(Runnable r, long delayMillis)
  • boolean Handler.postAtTime(Runnable r, Object token, long uptimeMillis)
  • boolean Handler.postAtTime(Runnable r, long uptimeMillis)
  • boolean Handler.postAtFrontofQueue(Runnable r)

메시지 전송 예제

public class HandlerTest extends Activity {
	int mMainValue = 0;
	int mBackValue = 0;
	TextView mMainText;
	TextView mBackText;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.threadtest);

		mMainText = (TextView)findViewById(R.id.mainvalue);
		mBackText = (TextView)findViewById(R.id.backvalue);

		BackThread thread = new BackThread();
		thread.setDaemon(true);
		thread.start();
	}

	public void mOnClick(View v) {
		mMainValue++;
		mMainText.setText("MainValue : " + mMainValue);
	}

	class BackThread extends Thread {
		public void run() {
			while (true) {
				mBackValue++;
				mHandler.sendEmptyMessage(0);
				try { Thread.sleep(1000); } catch (InterruptedException e) {;}
			}
		}
	}

	Handler mHandler = new Handler() {
		public void handleMessage(Message msg) {
			if (msg.what == 0) {
				mBackText.setText("BackValue : " + mBackValue);
			}
		}
	};
}

Runnable 객체 전송 예제
위 예제와 다르게 실행 객체를 전송하므로 mHandler는 구체적으로 정의할 필요 없이 생성만 해두면
내부 Runnable Code가 실행 된다.

public class HandlerTest extends Activity {
	int mMainValue = 0;
	int mBackValue = 0;
	TextView mMainText;
	TextView mBackText;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.threadtest);

		mMainText = (TextView)findViewById(R.id.mainvalue);
		mBackText = (TextView)findViewById(R.id.backvalue);

		BackThread thread = new BackThread();
		thread.setDaemon(true);
		thread.start();
	}

	public void mOnClick(View v) {
		mMainValue++;
		mMainText.setText("MainValue : " + mMainValue);
	}

	class BackThread extends Thread {
		public void run() {
			while (true) {
				mBackValue++;
				mHandler.post(new Runnable() {
					public void run() {
						mBackText.setText("BackValue : " + mBackValue);
					}
				});
				try { Thread.sleep(1000); } catch (InterruptedException e) {;}
			}
		}
	}

	Handler mHandler = new Handler();
}

위 예제에서는 Anonymous class 기법이 사용 되었다.

// non-anonymous class
Runnable runUpdate = new Runnable(){
	public void run() {
		mBackText.setText();
	}
};
mHandler.post(runUpdate);

// anonymous class
mHandler.post(new Runnable(){
	public void run() {
		mBackText.setText();
	}
};

runOnUiThread를 통한 Main UI Thread와의 통신

  • runOnUiThread 메서드 호출 위치가 UI 스레드라면 Runnable은 즉시 실행된다.

  • 작업 스레드에서 runOnUiThread가 호출 되었다면 자동으로 UI 스레드 큐를 찾아서 Runnable을 그 뒤에 붙여 준다. 즉, RunnableHandler.post를 하지 않아도 내부적으로 이와 동일한 작업을 해주는 것이다. mHandler가 없어도 상관 없기 때문에 훨씬 간편하다.

익명 class를 runnable로 runOnUiThread로 전달하는 예제

//* runOnUiThread 메서드로 익명 임시 객체 전달
public class HandlerTest extends Activity {
	int mMainValue = 0;
	int mBackValue = 0;
	TextView mMainText;
	TextView mBackText;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.threadtest);

		mMainText = (TextView)findViewById(R.id.mainvalue);
		mBackText = (TextView)findViewById(R.id.backvalue);

		BackThread thread = new BackThread();
		thread.setDaemon(true);
		thread.start();
	}

	public void mOnClick(View v) {
		mMainValue++;
		mMainText.setText("MainValue : " + mMainValue);
	}

	class BackThread extends Thread {
		public void run() {
			while (true) {
				mBackValue++;
				runOnUiThread(new Runnable() {
					public void run() {
						mBackText.setText("BackValue : " + mBackValue);
					}
				});
				try { Thread.sleep(1000); } catch (InterruptedException e) {;}
			}
		}
	}
}

Activity 외부에 Thread가 선언된 좀 더 일반적인 경우

public class HandlerTest extends Activity {
	int mMainValue = 0;
	TextView mMainText;
	TextView mBackText;
	BackThread mThread;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.threadtest);

		mMainText = (TextView)findViewById(R.id.mainvalue);
		mBackText = (TextView)findViewById(R.id.backvalue);
		mThread = new BackThread(mHandler);
		mThread.setDaemon(true);
		mThread.start();
	}

	public void mOnClick(View v) {
		mMainValue++;
		mMainText.setText("MainValue : " + mMainValue);
	}

	Handler mHandler = new Handler() {
		public void handleMessage(Message msg) {
			if (msg.what == 0) {
				mBackText.setText("BackValue : " + msg.arg1);
			}
		}
	};
}

class BackThread extends Thread {
	int mBackValue = 0;
	Handler mHandler;

	BackThread(Handler handler) {
		mHandler = handler;
	}

	public void run() {
		while (true) {
			mBackValue++;
			Message msg = new Message();
			msg.what = 0;
			msg.arg1 = mBackValue;
			mHandler.sendMessage(msg);
			try { Thread.sleep(1000); } catch (InterruptedException e) {;}
		}
	}
}

지금 까지 예제와 다른점은 분리된 파일에 각각의 코드가 정의 되었다고 가정하므로
BackThread Class의 생성자를 통해서 main activity handler를 넘겨 주어야 한다는 점이다.
Message에도 좀 더 구체적으로 값을 넣어서 보내줘야 Main Activity Handler에서 처리가 가능하다.

성능향상을 위해서 obtain함수를 이용해서 message를 생성 할 수 있다.
Cache에 있는 이전에 생성한 Message를 재사용하는 방식이다.

class BackThread extends Thread {
	int mBackValue = 0;
	Handler mHandler;

	BackThread(Handler handler) {
		mHandler = handler;
	}

	public void run() {
		while (true) {
			mBackValue++;
			Message msg = Message.obtain(mHandler, 0, mBackValue, 0);
			mHandler.sendMessage(msg);
			try { Thread.sleep(1000); } catch (InterruptedException e) {;}
		}
	}
}

Looper (루퍼)

서로 다른 thread는 message를 이용해서 통신한다.

  • Message Queue: 메시지가 차곡차곡 쌓이는 것을 말한다.
  • Looper: 큐에서 메시지를 꺼내 핸들러로 전달하는 것을 담당한다. 단, MainThread는 기본적으로 Looper를 가지고 있기 때문에 따로 구현할 필요는 없다. WorkingThread의 경우에만 직접 구현하면 된다.
  • Handler: 메시지를 받기도하고 보내기도 한다.

응용프로그램을 시작할 때 생성되는 메인 스레드는 루퍼를 가지고 있으며 이미 동작하므로 이 경우는 루퍼나 메시지 큐의 존재에 대해 상세히 몰라도 상관없다. 핸들러를 만들어 놓고 전달되는 메시지를 처리하기만 하면 된다.

루퍼를 직접 프로그래밍해야 하는 경우는 작업 스레드가 메시지를 받아야 할 때이다.

백그라운드 스레드는 Looper가 없다. 백그라운드 스레드에서 기본 핸들러 생성자 Handler()를 사용하면 따라서 에러가 발생한다.
Can't create handler inside thread that has not called Looper.prepare라는 메시지가 나온다.

Looper가 실행하는 관련 함수는 다음과 같다.

static void prepare()
static void loop()
void quit()
  • prepare는 현재 스레드를 위한 루퍼를 준비한다. 내부적으로 메시지를 저장하는 MessageQueue를 생성하고 그외 메시지 전송에 필요한 조치를 한다.
  • loop는 큐에서 메시지를 꺼내 핸들러로 전달하는 루프를 생성 한다.
  • quit는 루프의 종료롤 나타낸다.

다음은 관련된 스레드와 루퍼를 구하는 메서드

Thread getThread()  
static Looper getMainLooper()  
static Looper myLooper() 
  • getThread: 루퍼와 연결된 스레드를 구한다.
  • getMainLooper: 응용프로그램 수준에서의 주 루퍼를 반환
  • myLooper: 현재 스레드의 루퍼를 구한다. 모든 스레드가 루퍼가 있는것은 아니므로 null이 리턴될 수도 있다.

다음 예제 코드는 메인 스레드와 작업 스레드가 메시지를 통해 양방향으로 통신하는 예를 보여준다.
메인에서 메시지를 보내 연산을 시키고 스레드가 연산 결과를 리턴할 때도 메시지를 사용한다.

Main Thread와 Working Thread 모두 Looper가 존재하는 코드

public class LooperTest extends Activity {
	int mMainValue = 0;
	TextView mMainText;
	TextView mBackText;
	EditText mNumEdit;
	CalcThread mThread;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.loopertest);

		mMainText = (TextView)findViewById(R.id.mainvalue);
		mBackText = (TextView)findViewById(R.id.backvalue);
		mNumEdit = (EditText)findViewById(R.id.number);

		mThread = new CalcThread(mHandler);
		mThread.setDaemon(true);
		mThread.start();
	}

	public void mOnClick(View v) {
		Message msg;
		switch (v.getId()) {
		case R.id.increase:
			mMainValue++;
			mMainText.setText("MainValue : " + mMainValue);
			break;
		case R.id.square:
			msg = new Message();
			msg.what = 0;
			msg.arg1 = Integer.parseInt(mNumEdit.getText().toString());
			mThread.mBackHandler.sendMessage(msg);
			break;
		case R.id.root:
			msg = new Message();
			msg.what = 1;
			msg.arg1 = Integer.parseInt(mNumEdit.getText().toString());
			mThread.mBackHandler.sendMessage(msg);
			break;
		}
	}

	Handler mHandler = new Handler() {
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case 0:
				mBackText.setText("Square Result : " + msg.arg1);
				break;
			case 1:
				mBackText.setText("Root Result : " + ((Double)msg.obj).doubleValue());
				break;
			}
		}
	};
}

class CalcThread extends Thread {
	Handler mMainHandler;
	Handler mBackHandler;

	CalcThread(Handler handler) {
		mMainHandler = handler;
	}

	public void run() {
		Looper.prepare();
		mBackHandler = new Handler() {
			public void handleMessage(Message msg) {
				Message retmsg = new Message();
				switch (msg.what) {
				case 0:
					try { Thread.sleep(200); } catch (InterruptedException e) {;}
					retmsg.what = 0;
					retmsg.arg1 = msg.arg1 * msg.arg1;
					break;
				case 1:
					try { Thread.sleep(200); } catch (InterruptedException e) {;}
					retmsg.what = 1;
					retmsg.obj = new Double(Math.sqrt((double)msg.arg1));
					break;
				}
				mMainHandler.sendMessage(retmsg);
			}
		};
		Looper.loop();
	}
}
  • 메인스레드 작업스레드의 핸들러를 객체를 통해서 바로 호출한다.
  • 작업스레드 메인스레드의 핸들러를 객체 생성시 생성자를 통해서 받아서 그것을 통해서 호출한다.

중요한점은 이러한 메시지기반 통신 방법은 스레드 간의 통신 장치일 뿐이다. 그 이상인 응용프로그램간의 통신에는 사용할 수 없다.
공식적으로 응용프로그램 레벨에서의 통신은 Intent BR을 사용해야 한다.

작업 스케줄링

메시지큐

메시지큐에 메시지를 전달하고 원하는 시간에 꺼내서 처리 될 수 있도록 한다.

// 절대 시간
boolean sendMessageAtTime (Message msg, long uptimeMillis)
boolean postAtTime (Runnable r, long uptimeMillis)

// 상대 시간
boolean sendMessageDelayed (Message msg, long delayMillis)
boolean postDelayed (Runnable r, long delayMillis)

아래와 같은 예제를 만들 수 있다.

// 러너블 전달 
public class Post extends Activity {
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.upload);
	}

	public void mOnClick(View v) {
		new AlertDialog.Builder(this)
		.setTitle("질문")
		.setMessage("업로드 하시겠습니까?")
		.setPositiveButton("", new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int whichButton) {
				mHandler.postDelayed(new Runnable() {
					public void run() {
						doUpload();
					}
				},10);
			}
		})
		.setNegativeButton("아니오", null)
		.show();
	}

	Handler mHandler = new Handler();

	void doUpload() {
		for (int i = 0; i < 20; i++) {
			try { Thread.sleep(100); } catch (InterruptedException e) {;}
		}
		Toast.makeText(this, "업로드를 완료했습니다.", 0).show();
	}
}

위 예제는 Handler의 메시지 큐에 메시지를 전달 하는 것이다.
Runnable로 전달 하기 때문에 따로 Handler내부를 구현할 필요는 없다.

//* 뷰의 postDelayed 호출
public class Post extends Activity {
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.upload);
	}

	public void mOnClick(View v) {
		new AlertDialog.Builder(this)
		.setTitle("질문")
		.setMessage("업로드 하시겠습니까?")
		.setPositiveButton("", new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int whichButton) {
				Button btnUpload = (Button)findViewById(R.id.upload);
				btnUpload.postDelayed(new Runnable() {
					public void run() {
						doUpload();
					}
				},10);
			}
		})
		.setNegativeButton("아니오", null)
		.show();
	}

	void doUpload() {
		for (int i = 0; i < 20; i++) {
			try { Thread.sleep(100); } catch (InterruptedException e) {;}
		}
		Toast.makeText(this, "업로드를 완료했습니다.", 0).show();
	}
}

위 예제는 더 재밌는 것으로 View에 있는 스레드 큐를 사용 한다.
메인 View에 메시지큐가 있다는 것이다.

ANR (Application Not Responding)

Application Not Reponsding (ANR)이 발생하는 조건은 아래 두 가지이다.

  • 응용프로그램이 5초 이상 사용자의 입력에 반응하지 않을 때
  • 브로드캐스트 리시버 (BR)가 10초 내로 리턴하지 않을 때
public class ANR2 extends Activity {
	boolean bUploading = false;
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.anr);
	}
	
	public void mOnClick(View v) {
		switch (v.getId()) {
		case R.id.btnincrease:
			TextView textCounter=(TextView)findViewById(R.id.txtcounter);
			int count = Integer.parseInt(textCounter.getText().toString());
			textCounter.setText(Integer.toString(count + 1));
			break;
		case R.id.btnupload:
			if (bUploading) return;
			Thread uploadThread = new Thread() {
				public void run() {
					doUpload();
					mCompleteHandler.sendEmptyMessage(0);
				}
			};
			bUploading = true;
			uploadThread.start();
			break;
		}
	}

	public Handler mCompleteHandler = new Handler() {
		public void handleMessage(Message msg) {
			bUploading = false;
			Toast.makeText(ANR2.this, "업로드를 완료했습니다.", 0).show();
		}
	};
	
	void doUpload() {
		for (int i = 0; i < 100; i++) {
			try { Thread.sleep(100); } catch (InterruptedException e) {;}
		}
	}
}

위 코드는 두 가지로 나눠 지는데
첫 번째는 장기간 걸리는 작업을 Thread를 생성해서 doUpload()를 호출 했다는 점이다.
두 번째는 최종적인 결과 메시지 반환을 main thread의 handler호출로 처리 했다는 점이다.

StricMode

ANR 디버깅을 위해서 사용 하는 것으로 재미있는 기능 같다.

LogTime working을 Progress bar로 표현 하기

public class LongTime4 extends Activity {
	int mValue;
	TextView mText;
	ProgressDialog mProgress;
	boolean mQuit;
	UpdateThread mThread;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.longtime);

		mText=(TextView)findViewById(R.id.text);
	}
	
	@SuppressWarnings("deprecation")
	public void mOnClick(View v) {
		mValue = 0;
		showDialog(0);
		mQuit = false;
		mThread = new UpdateThread();
		mThread.start();
	}

	@SuppressWarnings("deprecation")
	protected Dialog onCreateDialog(int id) {
		switch (id) {
		case 0:
			mProgress = new ProgressDialog(this);
			mProgress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
			mProgress.setTitle("Updating");
			mProgress.setMessage("Wait...");
			mProgress.setCancelable(false);
			mProgress.setButton("Cancel", new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog, int whichButton) {
					mQuit = true;
					dismissDialog(0);
				}
			});
			return mProgress;
		}
		return null;
	}

	Handler mHandler = new Handler() {
		@SuppressWarnings("deprecation")
		public void handleMessage(Message msg) {
			mValue = msg.arg1;
			mText.setText(Integer.toString(mValue));
			if (mValue < 100) {
				mProgress.setProgress(mValue);
			} else {
				mQuit = true;
				dismissDialog(0);
			}
		}
	};

	class UpdateThread extends Thread {
		public void run() {
			while (mQuit == false) {
				mValue++;
				Message msg = mHandler.obtainMessage();
				msg.arg1 = mValue;
				mHandler.sendMessage(msg);
				try { Thread.sleep(50); } catch (InterruptedException e) {;}
			}
		}
	}
}

AsyncTask

백그라운드 작업을 하기 위해서는 스레드, 핸들러 등을 각각 만들어야 하고 작업 중에 핸들러를 주기적으로
호출해야 하는 번거로움이 있다.

이것을 간편하게 해주는 Helper 함수 AsyncTask이다.
AsyncTask는 내부적으로 작업 스레드를 생성하며 필요할 때마다 UI 스레드에서 실행되는 콜백 메서드를 호출한다.
콜백 메서드에서 정해진 작업만 처리하면 간단하게 백그라운드 작업을 수행할 수 있다.

AsyncTask자체는 추상 클래스이므로 파생 클래스를 생성하고 콜백 메서드를 구현해야 한다.

Generic parameters를 사용한다.

  • Params: 실행할 때 전달할 인수의 타입이다. 즉 배경 작업거리이다.
  • Progress: 매 작업 단계마다 진행 상태를 표기하는 타입이다.
  • Result: 작업의 결과로 리턴될 타입이다.

콜백 함수들은 아래와 같다.
doInBackground 메서드만 wroking thread에서 수행된다.
나머지 모든 메서드들은 Main UI 스레드에서 실행 된다.

  • void onPreExecute()
    • 작업이 시작되기 전에 호출되며 UI 스레드에서 실행 된다.
    • 계산을 위한 초기화나 프로그래스 대화상자를 준비하는 등의 작업을 수행한다.
  • Result doInBackground(Params... params)
    • 배경 작업을 수행하며 분리된 스레드에서 실행된다.
    • execute 메서드로 전달한 작업거리가 params 인수로 전달되는데 여러 개의 인수를 전달할 수 있으므로 배열이다.
    • 하나의 인수만 필요하다면 params[0]만 사용하면 된다.
    • 작업중에 publishProgress 메서드를 호출하여 작업 경과를 UI 스레드로 보고한다.
    • 최종적으로 작업된 결과를 Result 타입으로 리턴한다.
  • void onProgressUpdate(Progress... values)
    • doInBackground에서 publishProgress 메서드를 호출할 때 작업 경과 표시를 위해 호출되며 UI 스레드에서 실행된다. 프로그래스바에 진행 상태를 표시하는 역할을 한다.
  • void onPostExecute(Result result)
    • 백그라운드 작업이 끝난 후 UI 스레드에서 실행된다. 인수로 작업의 결과가 전달되는데 취소되었거나 예외가 발생했으면 null을 리턴 한다.
  • void onCancelled()
    • cancel 메서드로 작업을 취소했을 때 호출되며 UI 스레드에서 실행 된다.

아래의 메서드들은 만드시 UI 스레드에서 호출해야 하는 것들이다.

  • AsyncTask <Params, Progress, Result> execute (Params... params)
    • 인수로 작업거리에 해당하는 값을 넘겨주되 같은 타입의 인수 여러 개를 동시에 넘길 수 있다.
    • 이 호출에 의해 AsyncTask의 각 메서드가 순서대로 호출되어 배경 작업을 수행한다.
    • 스레드 내에서 적당한 때에 콜백 메서드를 호출하므로 UI 스레드에서 콜백 메서드를 직접 호출해서는 안 된다.
    • 리턴되는 AsyncTask 객체는 메인 스레드에서 참조 가능하며 다음 메서드로 작업을 관리한다.
  • boolean cancel (boolean mayIntteruptIfRunning)
  • boolean isCancelled()
    • 작업을 취소하라고 지시한다.
    • 작업 취소가 항상 성공적이지 않으며 실패할 수도 있는데 이미 완료되었거나 취소되었을 때 또는 기타의 이유로 취소할 수 없는 경우도 있다. 만약 작업을 아직 시작하기 전이라면 취소는 성공한 것으로 평가된다.
    • 인수는 작업을 실행중인 스레드를 중지할 것인가 지정한다.
    • isCancelled 메서드는 정상 종료되지 않고 취소되었는지 조사한다.
  • Result get ([long timeout, TimeUnit unit])
    • 작업이 완료되기까지 대기하며 작업 결과를 돌려 받는다.
    • 필요할 경우 대기할 타임아웃값을 지정한다.
  • AsyncTask.Status getStatus()
    • 작업의 현재 상태를 조사한다.
    • 아직 시작하지 않은 상태이면 PENDING이 리턴되며 이미 실행중이면 RUNNING, 작업이 완료되었으면 FINISHED가 리턴된다.

전체적인 메서드 실행 흐름은 아래와 같다.

AsyncTask의 작성 코드는 아래와 같다.

package andexam.ver6.c19_thread;

import andexam.ver6.*;
import andexam.ver6.c19_thread.LongTime4.*;
import android.app.*;
import android.content.*;
import android.os.*;
import android.view.*;
import android.widget.*;

public class LongTime5 extends Activity {
	int mValue;
	TextView mText;
	ProgressDialog mProgress;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.longtime);

		mText = (TextView)findViewById(R.id.text);
	}
	
	public void mOnClick(View v) {
		new AccumulateTask().execute(100);
	}

	class AccumulateTask extends AsyncTask<Integer, Integer, Integer> {
		@SuppressWarnings("deprecation")
		protected void onPreExecute() {
			mValue = 0;
			mProgress = new ProgressDialog(LongTime5.this);
			mProgress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
			mProgress.setTitle("Updating");
			mProgress.setMessage("Wait...");
			mProgress.setCancelable(false);
			mProgress.setProgress(0);
			mProgress.setButton("Cancel", new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog, int whichButton) {
					cancel(true);
				}
			});
			mProgress.show();
		}
		
		protected Integer doInBackground(Integer... arg0) {
			while (isCancelled() == false) { 
				mValue++;
				if (mValue <= 100) {
					publishProgress(mValue);
				} else {
					break;
				}
				try { Thread.sleep(50); } catch (InterruptedException e) {;}
			}
			return mValue;
		}
		
		protected void onProgressUpdate(Integer... progress) {		 
			mProgress.setProgress(progress[0]);	 
			mText.setText(Integer.toString(progress[0]));	 
		}
		
		protected void onPostExecute(Integer result) { 
			mProgress.dismiss();
		}
		
		protected void onCancelled() {
			mProgress.dismiss();
		}
	}
}

결국 실행 흐름과 코드를 가지고 이해해 보면,
메인 스레드와 작업 스레드를 알아서 안드로이드 시스템이 스위칭하면서 백그라운드 작업과 UI 갱신을 순서대로 실행하는 구조이다.

결국 기존 방식과 유사하되 각 단계에서 해당하는 부분이 AsyncTask의 콜백 메서드로 정형화되어 있다는 점만 다르다.
doInBackground메서드는 작업 스레드에 해당하며 onProgressUpdate는 핸들러에 해당하는 셈이다.
doInBackground내부에서 publishProgress를 실행함으로써 Progress Bar Update한다.

대화상자를 클래스 내부에서 Cancel버튼을 추가하여 생성 한다.

스레드와 핸들러를 직접 만들지 않아도 된다는 점에서 구조가 깔끔해 보이지만 약간 이해하는데 어려움이 있을 수도 있다.
전달 가능한 인수의 개수는 제한이 없지만 타입이 하나로 고정되어 범용성에 약간 제약이 있다.

하지만 많은 Open app code에서 해당 기능으로 구현을 많이 하므로 남의 코드를 이해하기 위해서는 익숙해질 필요가 있다.

참고문헌

안드로이드 정복 4판, 김상


'Computer Science > Android Application' 카테고리의 다른 글

Service  (0) 2017.08.16
Activity  (0) 2017.08.16
쓰레드 (Thread)  (0) 2017.08.04
이벤트 (Event)  (0) 2017.08.04
Gradle  (0) 2016.06.03
Android Plot Libraries  (0) 2016.01.22

+ Recent posts