'기술문서'에 해당되는 글 2건

  1. 윈도우 백그라운드 & UI 속도 (4)
  2. 메모리 누출 피하기 (4)
윈도우 백그라운드 & UI 속도

안드로이드의 어떤 애플리케이션은 UI 의 속도를 최대한 끌어올릴 필요가 있습니다. 방법은 여러가지가 있습니다.
이번 문서에서는 애플리케이션의 그리기 성능과 액티비티의 지각(느낄 수 있는) 할 수 있는 스타트업 속도를 어떻게 향상시킬수 있는지 알아봅니다.
이 두가지 성능향상을 위한 기법은 한 요소에 의해 결정됩니다. 윈도우의 백그라운드 Drawable 입니다.

윈도우 백그라운드라는 용어는 약간은 오해의 소지가 있습니다. 하지만 액티비티에서 setContentView() 메서드를 이용해서 UI를 구축한다면
안드로이드는 액티비티의 윈도우에 뷰를 추가합니다. 하지만 윈도우는 단지 뷰만 추가하는 것이 아니라 몇몇 다른 요소들을 포함합니다.
가장 중요한 요소는 T-Mobile G1에서 사용되고 있는 DecorView 입니다.
아래 표에서 뷰 계층(View Hierarchy) 에서 확인해보죠.



DecorView는 윈도우의 Drawable을 실제로 담고 있는 뷰입니다.
액티비티에서 getWindow().setBackgroundDrawable() 메서드를 호출하고 DecorView의 백그라운드 Drawable을 바꾸면 윈도우의 백그라운드가 바뀝니다.
이 설정은 매우 구체적인 설정이고 차후버전이나 심지어 다른 기기들에서도 바꿀 수 있습니다.

개발시에 표준 안드로이드 테마를 사용한다면 안드로이드는 기본 백그라운드 Drawable을 액티비티의 기본 배경으로 사용합니다.
T-Mobile G1 에서 현재 사용하고 있는 표준 테마는 ColorDrawable 입니다. 대부분의 애플리케이션에서 백그라운드 Drawable은 잘 동작하며 혼자서도 동작이 가능합니다.
하지만 이 백그라운드 Drawable은 애플리케이션의 그리기 성능에 영향을 미칠 수 있습니다.
애플리케이션이 백그라운드에 항상 투명 사진을 그리는 예제를 한 번 보죠.





위의 스크린샷에서 윈도우의 백그라운드가 보이지 않는 것을 확인 할 수 있습니다.
윈도우의 백그라운드 전체가 ImageView 에 가려져 있죠. 애플리케이션이 44FPS(초당 프레임수)의 속도로 이미지를 그리고 있습니다.
이 애플리케이션이 더 빠르게 이미지를 그리게 하는 방법은 백그라운드 Drawable 을 제거하는 것입니다.
UI가 완전히 투명이기 때문에 백그라운드에 이미지를 그리는 것은 너무나 소모적이죠.
백그라운드 이미지를 제거하는 것은 성능을 끌어 올리는데 아주 좋습니다.





위의 스크린샷에서 백그라운드 이미지를 제거한 후에 FPS를 재어보니 51FPS가 나왔습니다. 초당 3밀리초의 차이는 T-Mobile G1의 메모리 버스에서 생기는 지연효과로 
풀스크린 이미지의 픽셀을 메모리 버스로 옮기는데 걸리는 시간을 의미합니다. 기본 백그라운드가 훨씬 더 화려하고 메모리 상주크기가 높다면 이 차이는 더 확연해 집니다.

윈도우의 백그라운드를 쉽게 삭제하는 방법은 커스텀 테마를 사용하는 것입니다. res/values/theme.xml 파일을 만듭니다.
그리고 아래의 코드를 넣습니다.

<resources>
    <style name="Theme.NoBackground" parent="android:Theme">
        <item name="android:windowBackground">@null</item>
    </style>
</resources>


이제 android:theme="@style/Theme.NoBackground" 속성을 <activity /> 나 <application />태그에 삽입하여 개발하고자하는 액티비티에 적용시킵니다.
MapView 나 WebView를 사용하거나 풀스크린 투명 뷰를 사용하는 애플리케이션을 개발한다면 매우 손쉽게 적용시킬 수 있습니다.

투명 뷰와 안드로이드 : 이 작업은 최적화 작업이 필요한데 안드로이드 UI 툴은 투명 자식객체로 숨겨진 뷰들을 다시 그리는 것을 방지할 만큼 똑똑하진 않기 때문입니다.
이 최적화 작업이 실행되지 않은 가장 주요한 이유는 안드로이드 애플리케이션에는 보통 투명 뷰가 없거나 아주 소수이기 때문입니다.
상황이 이렇다해도 최대한 이른 시기안에 이 최적화 작업을 진행하겠습니다. 좀 더 일찍 실행하지 못한 점이 죄송합니다.(번역자 주: 프레젠테이션에서 발표한 자료같음)

테마를 사용해서 윈도우의 백그라운드를 바꾸는 것 또한 액티비티의 스타트업 속도를 향상시킬 수 있습니다.
단, 텍스쳐나 로고를 이용한 커스텀 백그라운드를 사용하는 액티비티에만 적용됩니다.
아래 스크린샷에서 보이는 책장선반 애플리케이션이 좋은 보기입니다.






만약 이 애플리케이션의 XML이나 onCreate()메서드에서 그냥 나무 백그라운드를 지정해버린다면 사용자는 기본테마와 어두운 백그라운드를 보게 됩니다.
나무 텍스쳐(질감)은 context 뷰의 로드 후와 첫번째 레이아웃/그리기 작업이 끝난다음에만 나타나게 됩니다.
이런 작업을 보는 사용자는 애플리케이션이 불안하고 로딩하는데 시간이 오래 걸린다고 생각합니다.(실제로도 그렇습니다)
대신에 애플리케이션이 나무 백그라운드를 테마로 정의하고 애플리케이션이 시작되자마자 안드로이드 시스템이 이 설정을 적용시킨다면 사용자는 기본 테마를 보지 않게 되고
애플리케이션이 빠릿빠릿하고 바로 실행되어 빠르다는 인상을 갖게 됩니다.
메모리와 디스크 사용량을 줄이기 위해서는 res/drawable/background_shelf.xml 파일에 아래와 같이 백그라운드 타일을 정의합니다.

<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@drawable/shelf_panel"
    android:tileMode="repeat" />

이 Drawable 은 테마를 참조합니다.

<resources>
    <style name="Theme.Shelves" parent="android:Theme">
        <item name="android:windowBackground">@drawable/background_shelf</item>
        <item name="android:windowNoTitle">true</item>
    </style>
</resources>

Google Maps 애플리케이션에 위에서 보인 기법과 똑같은 기법이 사용되었습니다. 애플리케이션이 구동되면 사용자는 즉시 MapView의 타일들을 보게 됩니다.
테마는 MapView에서 타일을 로드하는 것과 똑같이 보이는 타일 백그라운드를 사용합니다.

가끔식 보면 최고의 기법들은 아주 간단합니다.
다음번에 투명 UI 혹은 커스텀 백그라운드를 사용하는 액티비티를 만들때는 윈도우의 백그라운드를 바꾸는 걸 기억하세요.
저작자 표시 비영리 동일 조건 변경 허락
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

메모리 누출 피하기

메모리 누출 피하기

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