: 대부분의 메모리 누수는 필요 이상으로 객체가 메모리에 오래 남아있기 때문에 발생한다. 메모리 누수를 피하거나 완화하는 기본적인 방법을 알아보자.


- 정적 내부 클래스 사용

:보통 액티비티의 내부 클래스에서 액티비티에 있는 뷰 계층의 참조로 인해서 메모리 누수가 많이 발생하다. 이를 피할 방법으로는 일반적인 내부 클래스 대신 정적 내부 클래스를 사용하는 것이다. 정적 내부 클래스는 인스턴스 객체가 아닌 오직 전역 클래스 객체만 참조 가능하기 때문에 외부 인스턴스 필드에 접근할 수 없다.


- 약한 참조 사용

: 정적 내부 클래스는 외부 인스턴스 필드에 접근할 수 없다. 따라서 작업을 수행할 때 제한사항이 많아진다. 이에 대한 대책으로 java.lang.ref.WeakReference를 사용할 수 있다. WeakReference는 약한 참조를 지원한다. 약한 참조는 일반적인 참조, 강한 참조와 달리 참조한 객체의 가비지 컬렉션이 가능하다. 다음은 약한 참조를 사용한 예이다.

public class Outer{
	private int mField;
	private static class SampleThread extends Thread{
		private final WeakReference<Outer> mOuter;
		
		SampleThread(Outer outer){
			mOuter = new WeakReference<Outer>(outer);
		}
		
		@Override
		public void run(){
			//외부 인스턴스가 가비지 컬렉션되었는지 null로 확인
			if(mOuter.get() != null){
				mOuter.get().mField = 1;
			}
		}
	}
}


- 스레드 실행 중지 및 메시지 큐 정리

: 위에서 배운 두 가지 방법으로는 메모리 누수를 막을 수 없다. 작업을 다한 스레드는 꼭 종료시켜야 한다. 핸들러를 사용한다면 메시지 큐에 보내진 메시지를 사용하지 않을 때는 removeCallbacksAndMessages 메서드로 메시지를 종료시켜야 한다. 참고로 handler.hasMessage(int what), handler.hasMessage(int what, Object tag) 메서드를 사용하면 해당 메시지가 메시지 큐에 현재 존재하는 지 알 수 있다.



- 스레드 유지

: 안드로이드에서 메모리 누수의 많은 원인 중 하나는 스레드와 안드로이드 구성요소 간 생명주기의 불일치이다. 예로 화면 회전과 같이 구성 변경이 일어나게 되면 액티비티가 새로 생기게 되는 데 기존 스레드는 기존의 액티비티를 계속 참조해 액티비티가 GC 되지 않을 수도 있다. 따라서 이런 구성 변경에 대처하기 위해서 스레드를 유지하면서 기존의 안드로이드 구성요소는 버리고 새로운 안드로이드 구성요소를 참조하도록 해야 한다. 구현 방법은 액티비티와 프래그먼트로 나뉜다.


1. 액티비티에서 스레드 유지

: 액티비티에서 스레드 유지는 onRetainNonConfigurationInstance()와 getLastNonConfigurationInstance() 메서드를 이용해 구현한다. 전자 메서드는 구성 변경 전에 콜백되고 새로운 액티비티 객체에 전달하고자 하는 객체를 반환해야 한다. 스레드 유지를 위해서는 스레드를 반환하면 된다. 구성 변경 후에 새로운 액티비티의 onCreate나 onStart 메서드에서 후자 메서드를 호출해 반환값을 전달 받은 객체로 캐스팅하면 된다. 단, 구성 변경이 아닌 액티비티 재시작에서는 null이 반환된다. 액티비티에서 스레드 유지는 현재 잘 사용되지 않는다. 대부분 애플리케이션의 액티비티가 프래그먼트로 구성되 있고 프래그먼트에서 스레드 유지 방법이 더 간단하기 때문이다. 다음은 이에 대한 예이다. 

public class ThreadRetainActivity extends Activity{
	private static class MyThread extends Thread{
		private ThreadRetainActivity mActivity;
		
		public MyThread(ThreadRetainActivity activity){
			mActivity = activity;
		}
		
		private void attach(ThreadRetainActivity activity){
			mActivity = activity;
		}
		
		@Override
		public void run(){
			final String text = getTextFromNetwork();
			mActivity.setText(text);
		}
		
		//긴 동작
		private String getTextFromNetwork(){
			// 네트워크 동작 시뮬레이션
			SystemClock.sleep(5000);
			return "Text from network";
		}
	}
	
	private static MyThread t;
	private TextView textView;
	
	@Override 
	public void onCreate(Bundle savedInstanceState){
		...
		Object retainedObject = getLastNonConfigurationInstance();
		if(retainedObject != null){
			t = (MyThread)retainedObject;
			t.attach(this);
		}
	}
	
	@Override
	public Object onRetainNonConfigurationInstance(){
		if(t!=null && t.isAlive()){
			return t;
		}
		return null;
	}
	
	.....
}


2. 프래그먼트에서 스레드 유지

: Fragment.onCreate()에서 setRetainInstance(true)를 호출하면 된다. 그러면 프래그먼트는 설정 변경동안 유지되고 프래그먼트에서 실행한 스레드는 계속 살아있게 된다. 
public class ThreadRetainActivity extends Activity{
	private ThreadFragment mThreadFragment;
	
	public void onCreate(Bundle savedInstanceState){
		...
		
		FragmentManager manager = getFragmentManager();
		mThreadFragment = (ThreadFragment)manager.findFragmentByTag("threadfragment");
		if(mThreadFragment == null){
			FragmentTransaction transaction = manager.beginTransaction();
			mThreadFragment = new ThreadFragment();
			transaction.add(mThreadFragment, "threadfragment");
			transaction.commit();
		}
		
	}
	
	publicvoid onStartThread(View v){
		mThreadFragment.execute();
	}
	
	....
}

public class ThreadFragment extends Fragment{
	private ThreadRetainActivity mActivity;
	private MyThread t;
	
	private class MyThread extends Thread{
		@Override
		public void run(){
			final String text = getTextFromNetwork();
			mActivity.setText(text);
		}
		
		//긴 동작
		private String getTextFromNetwork(){
			// 네트워크 동작 시뮬레이션
			SystemClock.sleep(5000);
			return "Text from network";
		}
	}
	
	@Override
	public void onCreate(Bundle savedInstanceState){
		super.onCreate(savedInstanceState);
		setRetainInstance(true);
	}
	
	@Override
	public void onAttach(Activity activity){
		super.onAttach(activity);
		mActivity = (ThreadRetainActivity)activity;
	}
	
	@Override
	public void onDetach(){
		super.onDetach();
		mActivity = null;
	}
	
	public void execute(){
		t = new MyThread();
		t.start();
	}

}


+ Recent posts