: 안드로이드는 UI 변경을 메인 스레드에서 밖에 하지 못 한다. 그래서 메인 스레드의 블록을 최소화하기 위해 파일 I/O, 네트워킹, 문자열 파싱, 복잡한 연산 등은 별도의 백그라운드 스레드를 만들어서 처리한다. 처리 결과는 사용자에게 보여주기 위해서 대부분 UI 스레드로 보내진다. 따라서 백그라운드 스레드와 UI 스레드 사이에 통신이 필요한 데 이 때 사용하는 게 핸들러이다. 핸들러는 스레드들 사이의 통신을 가능하게 해주는 메커니즘이다. 핸들러는 루퍼와 메시지 큐를 이용해서 스레드 사이에 통신을 가능하게 한다. 그렇기 때문에 핸들러는 안드로이드 개발자들이 코딩할 때 많이 사용하지만 잠재적인 메모리 누수의 원인이 될 수 있기에 조심해야 한다. 


우리는 여기서 핸들러로 인해 일어날 수 있는 메모리 누수에 대해 알아볼 것이다. 먼저 가비지 컬렉션이 되는 대상은 다른 객체에 의해 참조되지 않는 미사용 객체를 일컫는 다는 걸 참고 바란다. 해당 포스트를 읽기 전에 "(익명)내부 클래스/정적 내부 클래스 스레드에서 GC(가비지 컬렉션)" 포스트를 읽으면 도움이 될 것이다.  핸들러의 구현을 위해서는 루퍼, 메시지 큐가 필요하다. 따라서 핸들러가 구현된 스레드에는 루퍼와 메시지 큐를 참조하게 된다. 메시지 큐는 핸들러에 보낼 메시지들을 담고 있기 때문에 메시지를 참조하게 된다. 메시지 내부에는 핸들러, Object, Runnable과 참조를 유지한다. 따라서 메시지 큐에서 처리를 대기하고 있는 메시지들은 스레드가 실행되는 동안 가비지 컬렉션의 대상이 될 수 없다. 구체적인 예로 한 번 알아보자. 핸들러는 데이터 메시지 전송과 태스크 메시지 전송으로 나눌 수 있다. 각각에 대해서 잠재적인 메모리 누수 위험을 알아보자.  


※ 데이터 메시지 전송

public class Outer{
	Handler mHandler = new Handler(){
		public void handleMessage(Message msg){
			....메시지 처리
		}
	};
	
	public void doSend(){
		Message message = mHandler.obtainMessage();
		message.obj = new SampleObject();
		mHandler.sendMessageDelayed(message, 60*1000);
	}
}

위 코드에서 핸들러에 보내진 메시지가 오랫동안 메시지 큐에 남아있다면 SampleObject가 제거되야할 상황이 와도 가비지 컬렉션이 불가능하다. 잠재적인 메모리 누수 위험이 있다.


※ 태스크 메시지 전송

public class Outer{
	Handler mHandler = new Handler(){
		public void handleMessage(Message msg){
			....메시지 처리
		}
	};
	
	public void doPost(){
		mHandler.post(new Runnable(){
			public void run(){
				
			}
		})
	}
}

위 코드도 마찬가지다. Runnable에서 외부 객체를 참조하고 있다면 오랫동안 메시지 큐에 남아있을 시 잠재적인 메모리 누수 위험이 된다. 


위에서 보았듯이 대기하는 메시지가 큐에 오래있게 되면 메모리 누수 위험이 커지게 된다. 이런 메모리 누수를 방지하는 방법으로는 정적 내부 클래스를 사용, 약한 참조 사용, 메시지 큐 정리 등이 있다. 이에 대해서는 "안드로이드 기본적인 메모리 누수 방지" 포스트에서 확인 바란다.

+ Recent posts