Android/정리2010. 4. 10. 08:09

Avoiding Memory Leaks

Android app들은 적어도 T-Mobile G1에서 16MB heap size제약이 있다. 이것은 phone을 위해서는 많은 양이면서 개발자에게는 아주 작은양이다. 대부분의 메모리를 사용할 생각이 아니더라도 다른 app들을 죽이지 않을 정도로 가능한 아주 작은 양의 메모리를 사용해야 한다. 더 많은 app들이 메모리에 유지되고 그들의 app들간에 전환이 일어날것이다. 내작업의 일부로써 메모리누수이슈가 속출할것이며 대부분의 시간을 이런 실수(하나의 Context에 오랬동안 레퍼런스를 유지하는것)때문에 보내게 될것이다.

Android에서 Context는 resource를 load하고 access하는것을 제외한 많은 작업에 이용된다. 이것은 모든 widget
들이 생성자에서 Context 파라메터를 인자로 받는 이유이기도 하다. 일반적인 Android app에서 Activity와
Application이라는 2개의 Context를 가진다. 이것은 보통 class와 method에 전달하는 첫번째 파라메터이다.

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

위코드는 view가 전체액티비티에 하나의 레퍼런스를 가지고 있고 액티비티의 모든게 그안에 유지된다는것을 의미한다(모든 뷰구조와 그 리소스들), 그러므로 만일 Context가 누수(누수는 레퍼런스를 하나 유지하여 GC를 방지하는것을 의미한다)가 발생하면 당신은 많은 양의 메모리를 잃게된다. 전체 액티비티를 읽는것은 당신이 주의하지 않으면 아주 쉽게 일어날수 있다.

디폴트로 화면이 전환되면 현재 액티비티는 제거되고 그 상태가 저장된 상태로 새로운 액티비티가 다시 생성된다. 이런 방식으로 안드로이드는 리소스들을 다시 로드하여 UI를 다시 구성한다. 당신이 로테이션시마다 다시 로드되지 않기를 원하는 아주 큰 비트맵을 사용하는 어플리케이션을 작성한다고 상상해보라.  아주 쉬운 방법은 static field를 사용하는 것이다.

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);
}

이 코드는 아주 빠르지만 또한 아주 잘못된 코드이다. 처음 액티비티가 생성되어 처음 로테이션될때 leak이 발생된다. Drawable이 뷰에 첨부될때 view는 drawable에 callback으로 설정된다. 위 코드상에서 drawable은 TextView의 참조를 가지고 있다. TextView는 액티비티의 참조를 가지고 있다.

이 예제는 Context의 누수의 아주 단순한 케이스중 하나이며 Home screen소스상에서(unbindDrawables()메소드를 찾아보라) 저장된 drawable의 callback에서 액티비티가 제거될때 null로 초기화하는 것을 볼수 있다.  충분히 흥미진진하고 누수된 context의 연결고리를 생성할수 있는 여러 군데가 있으며 그것은 좋지 않은 방법이다. 그것들은 당신을 오히려 더 빠르게 메모리부족을 야기시킨다.

2가지의 아주 쉬운 context와 연관된 메모리누수를 피하는 방법이 있다. 가장 명백한 방법의 하나는 그 Context자신의 영역의 외부로 빠져나가는것을 완전히 피하는것이다. 위 코드는 static을 사용하는 경우를 보여주었다. 하지만 내부 class나 그것의 내부참조를 외부의 클래스에 참조하도록 하는것은 똑같이 아주 위험하다.
또한가지 방법은 Application context를 사용하는것이다. 이는 액티비티 생명주기와 관계없이 당신의 application이 살아있는동안 항상 유지된다. 만일 아주 오랬동안 유지되어야 하는 객체를 사용해야 한다면 applicaiton객체를 기억하라. Application context는 Context.getApplicationContext()나 Activity.getApplication()을 통해 아주 쉽게 얻을수 있다.

Context연관 메모리누수를 피하는 방법을 정리하겠다. 아래 사항을 기억하라.

  • 오랬동안 유지되어야 하는 레퍼런스는 Activity context에 유지하지 말아라(그것들은 액티비티와 동일한 생명주기를 갖게 된다.) - Do not keep long-lived references to a context-activity (a reference to an activity should have the same life cycle as the activity itself)
  • Application context를 사용할것을 시도하라 - Try using the context-application instead of a context-activity
  • 액티비티의 생명주기를 관리하지 않는다면 액티비티내에 non-static inner class를 사용하는것을 피하라. static inner class를 사용해야 하며 액티비티내에서 weak reference를 사용하라.  Avoid non-static inner classes in an activity if you don't control their life cycle, use a static inner class and make a weak reference to the activity inside. The solution to this issue is to use a static inner class with a WeakReference to the outer class, as done in ViewRoot and its W inner class for instance
  • GC는 메모리 누수에 대해 아무런 보장을 하지 않는다. - A garbage collector is not an insurance against memory leaks

Posted by 삼스