메모리 누출 피하기

메모리 누출 피하기

T-Mobile G1과 같은 안드로이드 애플리케이션들은 Heap 메모리 영역이
16MB 로 제한되어 있습니다. 이 메모리는 핸드폰에는 많은 양의 
메모리이지만 개발자가 필요로 하는것에는 크게 못 미칩니다.
이 메모리를 모두 다 사용할 계획이 없다고 하더라도 개발시에는
이 영역을 최소한으로 사용해서 다른 애플리케이션들이 Kill 당하지
않고 운용될 수 있도록 해야 합니다. 안드로이드가 더 많은 애플리케이션을
메모리에 상주시킴으로서 사용자는 애플리케이션간에 더 빠른 전환을
할 수 있습니다. 안드로이드를 개발 할 때에 메모리의 누출현상은 대부분의
경우에 같은 실수때문에 일어납니다.
즉, Context에 오랜시간 지속되는 참조를 하기 때문입니다.

안드로이드에서 Context는 아주 다양한 작업을 담당하는데 대부분은
리소스의 접근과 로드하는데 사용됩니다. 때문에 모든 위젯은 생성자에서
Context변수를 받습니다. 일반적인 안드로이드 애플리케이션의 경우에
Context는 두 종류로 구분됩니다. Activity와 Application이 그것인데
개발자는 보통 Activity를 클래스에 상속받고 Context가 필요한
메서드를 상속합니다. 예를 한번 보겠습니다.

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

위의 코드는 View가 전체 Activity를 참조하고 있고 따라서 Activity가 
포함하는 자료도 참조하고 있습니다. 보통 이 자료는 View의 계층과
모든 리소스를 포함합니다. 따라서 Context를 누출한다면 (여기서 
누출이라는 뜻은 Context에 계속 참조를 하기 때문에 Garbage Collector가
정리하는 것을 방지 한다는 뜻입니다.) 많은 양의 메모리를 누출하게 됩니다.
주의 하지 않으면 전체 Activity의 메모리가 누출될 수 있습니다.

스크린의 Orientation(방향 즉, Landscape와 Portrait)이 바뀌게 되면
시스템은 Default 설정에 따라 현재 Activity를 파괴하고 그 상태를 
유지하면서 새로운 Activity를 생성합니다. 그렇게 하는 동안 안드로이드는
애플리케이션의 리소스로부터 UI를 다시 로드합니다. 만약에 애플리케이션을
개발 할 때 큰 사이즈의 비트맵을 삽입하는데 Orientation(방향)에 따라
계속 로드하고 싶지 않다면 가장 쉬운 방법은 아래의 코드에서 보이는 바와 같이
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);
}

위의 코드는 실행속도는 아주 빠르지만 또한 아주 잘못된 코드입니다.
먼저 위의 코드에서 첫번째 Orientation(방향) 전환시에 만들어지는
Activity에서 메모리 누출이 발생합니다. Drawable이 View에 전개되면
View는 Drawable에 콜백함수로 설정됩니다. 즉, Drawable은 TextView에
참조하고 있고 TextView 자신은 Activity(Context)를 참조하고 있으며
Activity는 사실상 모든 것을 참조하고 있습니다. (코드에 따라 다름)

위의 예제는 Context의 메모리 누출의 아주 간단한 사례로 실제 프로젝트에서는
Home Screen's Source Code(프로젝트 내에 unbindDrawables() 메서드 참고)
에서 어떤 식으로 메모리 누출이 일어나는지 확인 할 수 있습니다.
흥미롭게도 Context에 관련한 메모리 누출 사례는 얼마든지 있고 정도에 따라
심각하기도 합니다. 이런 경우는 사용가능 메모리 영역이 비교적 빨리 소진됩니다.

Context 메모리 누출을 피하는 방법으로는 두가지 방법이 있습니다.
가장 확실한 방법은 Context 사용범위를 제한된 범위 안으로 한정하는 방법입니다.
위에서 보여드린 예제 코드에서는 static 참조를 사용했지만 이너클래스(Inner Class)
에서 바깥클래스(Outer Class)를 암묵적으로 참조하는 것이 위험한 것임을 보였습니다.
두번째 방법은 Application Context를 이용하는 것입니다.
이 방법에서 Context는 액티비티가 살아있는 동안은 지속되면서도 Activity의 생명주기
와는 별개의 수명을 갖습니다. 개발시에 오랜시간 살아있어야 할 Context가 필요한
Object가 필요하다면 Application Object를 기억하십시오.
Context.getApplicationContext() 메서드와 Activity.getApplication() 메서드를
통해서 사용할 수 있습니다.

메모리 누출 방지에 대해 요약하자면


  • Context Acitivity에 오랜시간 지속되는 참조를 피할 것 (Acivitity를 참조하는 Object는
    생명주기가 Activity 자체와 같아야 한다.)

  • Context Activity 대신에 Context Application을 사용하는 습관을 가지자


  • Activity 안의 Static 이 아닌 이너클래스(Inner Class)를 되도록 사용하지 말자
    Static 이너 클래스를 사용하고 약한 참조는 Activity 안에 두도록 하자
    이 문제에 대한 해결책은 WeakReference를 사용한 Static 이너 클래스를 이용해
    바깥클래스(Outer Class)를 참조하는 것이다.


  • Garbage Collector는 메모리 누출 방지를 보장하지 않는다.
저작자 표시 비영리 동일 조건 변경 허락
신고
크리에이티브 커먼즈 라이선스
Creative Commons License