안드로이드 개발사이트(http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html) 에 있는 내용으로,

너무나도 중요하며 안드로이드 개발자라면 반드시 숙지해야 하는 부분이다.

많은 안드로이드 개발자들이 메모리 문제로 고생하지 않길 바라는 마음에 번역하였다.


메모리 릭 피하기

안드로이드 어플리케이션들은 T-Mobile G1 모델을 포함하여 최소한 16MB 의 heap 용량을 갖는다. 이는 전화기에 사용하기에는 충분한 메모리이지만 몇몇 개발자들에게는 여전히 부족할 수도 있다. 심지어 이 메모리를 다 사용하려 하지 않는다 하더라도 한번쯤은 주의할 필요가 있다. 우리의 앱이 많은 메모리를 사용한다면 다른 앱이 실행될 때 우리의 앱을 죽일 수도 있기 때문이다. 즉, 안드로이드가 가능한 많은 앱을 메모리에 담을 수 있도록 하여, 앱의 빠른 실행전환을 돕는 것이 좋다.

내가 맡은 일의 한 부분으로, 나는 안드로이드 어플리케이션의 메모리 릭 부분에 특히 집중하였으며 대부분이 같은 실수 때문인 것이라는 것을 알게 되었다. 실수란 바로 Context  참조를 오랫동안 갖고 있는 객체를 생성하는 것을 말한다.

안드로이드에서는 대부분의 동작에 Context 를 사용하며 코드에서는 주로 리소스를 불러들이거나 접근하는 데 사용한다. 이러한 이유로 모든 안드로이드 widget(android.widget 패키지 내의 클래스들 - 역자 주) 들은 생성자에 Context 를 파라미터로 가진다. 일반적인 안드로이드 앱에서는 두 종류의 Context 를 사용하기 마련인데 바로 Activity 와 Application 이다. 개발자들은Context 가 필요한 코드에서, 전자를 사용하는 경우가 많은데 예를 들면 다음의 코드와 같다.

@Override
protected void onCreate(Bundle state) {
 
super.onCreate(state);
 
 
TextView label = new TextView(this);
  label
.setText("Leaks are bad");
 
  setContentView
(label);
}

이 코드를 살펴보자면 뷰들은 activity 전체에 대한 참조를 가진다. activity 뿐 아니라 activity 가 가진 모든 것들, 즉 View 전체의 계층구조 및 개별 View들이 사용하는 리소스들에 대한 참조를 가진단 뜻이다. 그러므로, Context 릭(leak) 이 발생한다는 것은 (릭이란 객체의 참조가 사라지지 않아 GC 대상에서 제외되는 객체를 만든다는 뜻임), 많은 메모리 낭비를 의미한다. 따라서, 조심하지 않으면 전체 Activity 에 대한 릭은 매우 빈번하게 발생한다.

간단한 예를 들어보자. 화면 방향이 바뀔 경우, 시스템은 기본적으로 현재의 Activity 를 제거한 다음 그 상태를 보존한 채로 새로운 Activity 를 만든다. 이 과정 동안 안드로이드는 앱의 UI를 리소스부터 새로 읽어들인다. 

그렇다면 매번 화면 방향을 바꿀 때 마다 그림을 읽어들이는 것을 원하지 않아, 다음과 같은 코드를 작성했다고 가정해 보자. 가장 쉬운 방법은 이미지를 static 참조로 바꾸어 그 상태를 확인함으로써 매번 불러들이지 않도록 하게 하는 것이다.

private static Drawable sBackground;

@Override
protected void onCreate(Bundle state) {
 
super.onCreate(state);
 
 
TextView label = new TextView(this);
  label
.setText("Leaks are bad");
 
 
if (sBackground == null) {
    sBackground
= getDrawable(R.drawable.large_bitmap);
 
}
  label
.setBackgroundDrawable(sBackground);
 
  setContentView
(label);
}

이 코드는 매우 빠르며 또한 매우 잘못되었다. 위 코드대로라면 최초에 화면변환이 일어날 때 최초의 Activity 참조를 놓치게 된다(leaks). Drawable 이 View 에 붙을 때(attached) View 는 drawable 을 callback 으로 갖게 된다. 코드를 다시 살펴보면, drawable 은TextView 에 대한 참조를 가지며  TextView  는 activity(Context) 에 대한 참조를 다시 갖는 구조이다. 코드에 따라 다르긴 하겠지만 이는 즉, 하나의 drawable 이 TextView 뿐 아니라 더 많은 것들에 대한 참조를 가지게 될 수도 있다는 것이다.

이 예제는 Context 릭이 발생하는 가장 간단하고 흔한 경우를 보여주며, 이를 막기 위해 안드로이드 개발팀이 홈 스크린의 소스코드 에서 어떤 노력을 하는지 확인할 수 있다. (unbindDrawables() 메소드를 살펴볼 것). 홈 스크린 소스 코드에서는 Activity 가  제거될 때, drawable 에 대한 callback 을 null 로 설정하여 이를 해결하고 있다. 흥미롭게도 놓친 context 들에 대한 참조들은 계속 이어질 수 있으며(GC되지 않았으므로 참조 가능함. 즉, 사라진 화면에 대한 참조조차도 가능하다는 뜻 - 역자 주), 또한 더욱 나쁘다. 메모리를 훨씬 빨리 고갈시킬 수 있기 때문이다.

Context 에 의한 메모리 릭을 피할 수 있는 쉬운 두 가지의 방법이 있다.

가장 명백한 첫번째 해결책은  Context 참조의 범위를 최소한으로 두라는 것이다. 위의 예제는 static 참조에 대한 예제이지만, 내부 클래스(inner class, http://docs.oracle.com/javase/tutorial/java/javaOO/innerclasses.html 참조 - 역자 주) 와 외부 클래스의 암묵적인 참조 또한 위험하다.

두 번째 해결책은 Application 의 Context를 사용하는 것이다. 이 Context는 우리의 앱이 살아있는 동안 계속 유효하며 activity 의 생명주기와는 전혀 무관하다. 만약 Context 가 필요한 장주기 객체(long-lived object)를 사용해야 할 경우  Context.getApplicationContext()또는 Activity.getApplication() 를 호출하여 획득한 Application 의 Context 사용을 고려하는 것도 좋다.

결론짓자면, Context 참조와 관련한 메모리 릭을 피하려면 다음의 내용을 숙지해야 한다.

  • 장주기 객체의 activity 참조는 가능한 피한다. (activity를 참조하는 객체는 activity 가 죽을 경우 제거되어야 함 - 선언한 activity 이외의 곳에서는 GC 대상이 되도록 설계해야 한다. - 역자 주)
  • Activity 의  Context 대신  Application 의 Context 사용한다.
  • 객체 생명주기를 직접 제어하지 않는 경우  Activity 내에서 non-static 내부 클래스의 사용을 가급적 피하고 불가피하게 사용해야 하는 경우라면 참조 타입을 약한 참조로 두도록 한다. 이 문제는 ViewRoot 구현에서 했던 것 처럼  외부 클래스를WeakReference 참조로 둔 static 내부 클래스를 활용하는 것으로 해결할 수 있다.
  • 가비지 컬렉터는 메모리 릭 방지를 보장해 주지 않는다.


'Programing > Android' 카테고리의 다른 글

Processes and Application Life Cycle  (0) 2016.05.04
Google I/O - Memory Management For Android  (0) 2016.05.03
Activity, Fragment Lifecycles  (0) 2016.05.03
Handler, Looper  (0) 2016.04.19
Parcelable vs. Serializable  (0) 2016.03.22

+ Recent posts