: 앱이 실행되면, 즉 앱 프로세스가 시작되면 메인스레드가 생성된다. 메인 스레드에서는 액티비티/프래그먼트의 UI 변경만 하지 않는다. 액티비티/서비스/브로드캐스트 리시버/Application 의 생명 주기와 메서드 호출을 메인 스레드에서 실행한다. 안드로이드 애플리케이션에서 메인 스레드는 ActivityThread 클래스의 main 메서드이다. 이 메서드가 앱의 시작 지점이다. ActivityThread는 Thread를 상속하지 않은 클래스이며 모든 컴포넌트와 관련이 있다. ActivityThread의 main 메서드는 다음과 같다.

public static void main(String[] args){ .... Looper.prepareMainLooper(); // 메인 Loop를 준비한다. ActivityThread thread = new ActivityThread(); thread.attach(false); ... Looper.loop(); // loop 메서드 안에서 무한 반복문이 있어 main은 종료되지 않는다. ... }

위의 코드를 이해하기 위해 Looper에 대해서 간략히 보면(Looper에 대해서 안다고 가정한다. 모르면 이 문장의 URL 포스트를 읽는 걸 추천한다.) Looper.prepare()로 스레드 별로 다른 Looper를 생성한다. 메인 스레드의 메인 Looper의 경우 Looper.prepareMainLooper() 메서드를 통해 가져온다. 접근할 때는 Looper.getMainLooper()을 사용하면 된다. 이 메서드를 사용하면 어디서든지 메인 Looper에 접근 가능하다. Looper는 별도의 MessageQueue를 가진다. 메시지 큐는 ArrayBlockingQueue 보다는 LinkedBlockingQueue에 가깝다. 전자에 비해 후자는 개수 제한이 없고 삽입 속도가 빠르다. 대신 전자는 랜덤 접근이 가능하고 후자는 순차 접근을 해야 한다. 메시지큐는 후자에 속해 순차적으로 메시지가 실행된다. Looper.loop() 메서드 코드를 보자.

public static void loop(){
	final Looper me = myLooper();
	if(me==null){
		throw new RuntimeException("...");
	}
	final MessageQueue queue = me.Queue;
	for(;;){
		Message msg = queue.next();
		if(msg==null){
			return;
		}
		msg.target.dispatchMessage(msg);
		msg.recycle();
	}
}

public void quit(){
	mQueue.quit(false);
}

public void quitSafely(){
	mQueue.quit(true);
}

: queue.next()를 통해 메시지큐에서 메시지를 꺼낸다. 만약 메시지가 null 일시 무한루프를 멈춘다. 즉 이 때는 Looper가 종료될 때이다. msg.target.dispatchMessage를 통해서 메시지를 타겟 핸들러 인스턴스에 보낸다. quit() 메서드는 호출될 시 큐상에 아직 처리되지 않은 메시지를 다 제거한다. quitSafely()는 현재 시간보다 늦게 처리될 예정인 메시지를 제고하고 그 앞에 있는 메시지는 처리하고 종료한다. 단, API 레벨 18이상에서 사용 가능하다. 메시지 클래스는 what, arg1, arg2, obj, replyTo public 변수와 target, callback 등의 패키지 프라이빗 변수가 있다. 메시지를 생성할 때는 Message.obtain이나 Hander의 obtainMessage 메서드 사용을 추천한다. 오브젝트 풀에서 메세지 객체를 가져오기 때문이다. new Message()로 객체를 생성하면 자원이 낭비된다. 


※ Handler

: Handler는 메시지를 메시지 큐에 넣는 기능과 메시지 큐에서 전달된 메시지 객체를 처리하는 기능을 한다. Handler는 다음의 생성자들을 가진다.

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

위에서 1~3번째 생성자들은 내부에서 4번째 생성자를 호출한다. 따라서 핸들러는 무조건 Looper을 필요로 한다. 메인 스레드에서 핸들러를 생성할 시 자동으로 ActivityThread에서 생성한 메인 Looper가 연결된다. 그렇다면 백그라운드 스레드에서 핸들러를 생성한다면 Looper가 없을 시 RuntimeException이 발생한다. 이를 해결하려면 핸들러를 생성하기 전에 백그라운드 스레드에서 Looper.prepare()을 실행해서 Looper을 준비해야 한다. 

class LooperThread extends Thread{
	public Handler handler;
	
	public void run(){
		Looper.prepare();
		handler = new Handler(){
		public void HandleMessage(Message msg){
			//Message 처리
		};

		Looper.loop();
	}
}

위와 같이 백그라운드 스레드에서 핸들러를 사용하면 된다. Looper.loop() 내에는 무한 반복문이 있기에 스레드는 종료되지 않는다. 참고로 UI를 변경하기 위해서는 메인 스레드에 접근해야 하는데 이 때 메인 Looper와 연결된 핸들러에 접근해야 한다. 백그라운드에서 메인 Looper에 접근하는 좋은 방법은 Looper.getMainLooper() 메서드를 이용하는 것이다.

public void backThreadFunc(){
	new Handler(Looper.getMainLooper()).post(new Runnable(){
		public void run(){
			....
		}
	});
}

Looper.loop() 메서드 안에서 msg.target.dispatchMessage(msg)를 사용해서 메시지 처리를 하였다. dispatchMessage 메서드는 다음과 같다.

public void dispatchMessage(Message msg){
	if(msg.callback!=null){
		handleCallback(msg);
	}else{
		if(mCallback!=null){
			if(mCallback.handleMessage(msg)){
				return;
			}
		}
		handleMessage(msg);
	}
}

private static void handleCallback(Message message){
	message.callback.run();
}

callback은 Runnable 객체를 가리킨다. Runnable 객체가 없다면 handleMessage()를 호출해 메시지를 처리한다.


- Handler 용도

1. 백그라운드 스레드에서 네트워크나 DB 작업 후 UI 업데이트를 할 때 사용한다. AsyncTask의 onPostExecute 메서드도 내부적으로 Handler을 사용한다.

2. 메인 스레드 작업 예약할 때 사용된다. 간혹 Activity의 onCreate에서 하지 못하는 일이 있다. 대표적으로 소프트 키보드를 띄우는 것이다. 이 때 Handler을 사용해 Message를 보내면 된다. onCreate에서 onResume까지의 작업이 메인 Looper에서 메시지 하나를 꺼내면 이어서 실행되기 때문에 onCreate에서 보낸 메시지는 onResume 이후에 실행된다.

3. 반복해서 UI를 갱신할 때 사용한다. 핸들러의 Runnable객체 안에서 또는 handleMessage 메서드 안에서 postDelayed나 sendMessageDelayed를 호출하면 된다.

4. 시간 제한을 할 때 핸들러를 사용할 수 있다. 안드로이드 ANR을 판단할 때 핸들러를 사용한다. 다음은 몇 초 내에 백 키를 반복해서 누를 때만 앱이 종료되도록 하는 코드이다.

private boolean isBackOnce = false;

@Override
public void onBackPressed(){
	if(isBackOne){
		super.onBackPressed();
	}else{
		isBackPressedOnce = true;
		hander.postDelayed(timerTask, 5000);
	}
}	

private final Runnable timerTask = new Runnable(){
	@Override
	public void run(){
		isBackOne = false;
	}
};



+ Recent posts