'안드로이드/문서번역'에 해당되는 글 5건

  1. 재사용 가능한 UI 컴포넌트로 레이아웃 구성하기 (8)
  2. 윈도우 백그라운드 & UI 속도 (4)
  3. 효율적인 레이아웃 구성하는 기법 (51)
  4. 리스트뷰 백그라운드의 최적화 (5)
  5. 메모리 누출 피하기 (4)
재사용 가능한 UI 컴포넌트로 레이아웃 구성하기

안드로이드 플랫폼은 아주 다양한 UI 위젯을 제공합니다. 작은 위젯 조각을 합쳐서 복잡하고 정교한 인터페이스를 사용자에게 보여줄 수 있습니다.
하지만 애플리케이션을 개발 할 때 더 수준높은 인터페이스가 필요 할 때가 있습니다. 이 인터페이스를 완성하면서 그것도 효율적으로 만들기 위해서는 수 개의 표준위젯을 하나이면서 
재사용이 가능한 컴포넌트로 합쳐야 합니다.

예를 들어 프로그레스바와 취소버튼이 있고 아이콘과 제목, 설명이 있고 Positive 와 Negative 액션과 등등이 포함된 패널을 만듭니다.
이런 UI 컴포넌트를 만들기 위해서는 커스텀 뷰를 만드는 방법도 있지만 XML을 통해서 더욱 쉽게 할 수 있습니다.

안드로이드의 XML 레이아웃 파일에서 각각의 태그는 클래스 인스턴스에 맵핑(상호 연결)되어 있습니다.
클래스는 항상 뷰의 서브클래스이고 UI 툴은 뷰의 인스턴스에 맵핑되어있지 않은 세가지 태그를 지원합니다. <requestFocus />, <merge >, <include /> 입니다.
이 기술문서는 <include /> 태그를 어떻게 사용해서 순수하게 XML 로만 작성한 컴포넌트를 만드는지 알아보겠습니다.
<include /> 문과 같이써서 강력한 효과를 내는 <merge /> 태그의 사용법은 Merging Layouts 문서를 참고하세요.

<include /> 의 하는 일은 이름 그대로입니다.
즉, 다른 XML 레이아웃을 포함(include)하는 것입니다. 이 태그를 사용하는 것은 아래의 예제에서 보이는 것과 같이 아주 직관적입니다.
아래 예제는 안드로이드 내장 애플리케이션인 the source code of the Home application 에서 직접 가져왔습니다.

<com.android.launcher.Workspace
    android:id="@+id/workspace"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"

    launcher:defaultScreen="1">
</com.android.launcher.Workspace>


<include /> 태그안에서는 layout 속성만 써야합니다. 이 속성은 android 접두어가 붙지 않으면 포함 시키고자 하는 레이아웃 파일을 참조합니다. (사용한다는 말)
이 예제에서는 똑같은 레이아웃이 3번 연속으로 적용되었습니다. 이 태그는 또한 포함시키고자 하는 레이아웃의 몇가지 속성들을 오버라이드 할 수도 있습니다.
위의 예제에서는 android:id 를 사용해서 포함된 레이아웃의 루트 뷰(최상단 뷰)에 id를 부여 할 수 있습니다. id가 부여 되면 포함된 레이아웃의 id 도 오버라이드 됩니다.
또한 모든 레이아웃의 속성값들을 오버라이드 하는것도 가능합니다. 즉, 어떠한 android:layout_* 속성이라도 <include /> 태그안에서 쓰일 수 있다는 말입니다.
아래 예제에서는 똑같은 레이아웃이 두번 포함되었는데 첫번째 것만 레이아웃 속성들을 오버라이드 했습니다.
살펴보죠.

<!-- override the layout height and width -->
<include layout="@layout/image_holder"
    android:layout_height="fill_parent"
    android:layout_width="fill_parent" />
<!-- do not override layout dimensions; inherit them from image_holder -->
<include layout="@layout/image_holder" />


주의
안드로이드 위젯의 dimension(폭과 높이)를 오버리아드 하고자 한다면 android:layout_height와 android:layout_width 속성을 다 오버라이드해야 합니다.
어느 한 가지 속성만 오버라이드 해서는 안됩니다. 한 가지만 오버라이드 하게 되면 아무 효과도 나타나지 않습니다. (weight 같은 이외의 속성들은 소스레이아웃에서 상속됩니다.)


<include />태그는 기기의 설정에따라 UI의 일부 특정한 부분만 수정하고자 할 때 매우 유용합니다.
예를 들어 액티비티의 메인 레이아웃 layout/ 폴더에 넣고 다른 레이아웃은(화면 방향을 예로 들어) layout_land/ 나 layout_port/ 폴더에 따로 보관 할 수 있습니다.
이렇게 레이아웃을 따로 보관한다면 유지보수가 한 층 더 용이해지는 것은 당연하겠죠.
저작자 표시 비영리 동일 조건 변경 허락
신고
윈도우 백그라운드 & 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 혹은 커스텀 백그라운드를 사용하는 액티비티를 만들때는 윈도우의 백그라운드를 바꾸는 걸 기억하세요.
저작자 표시 비영리 동일 조건 변경 허락
신고
효율적인 레이아웃 구성하는 기법

안드로이드 UI 툴은 사용하기 쉽고 편한 관리툴을 제공합니다.
대부분의 경우에는 관리툴이 제공하는 몇몇 기본적인 것들만 이용하면 UI를 간편하게 구성할 수 있습니다.

하지만 기본적인 것들만 이용해서는 효율적인 사용자 UI를 만들지 못하겠죠.
보기 좋은 예가 LinearLayout의 남용을 들 수 있는데 이렇게 남용하게 되면 View 계층에서 View가 넘쳐나게 되는 결과를 야기합니다.
애플리케이션에서 보여지는 모든 뷰와 레이아웃 관리툴은 초기화라는 비용을 수반합니다.
이런 비용은 레이아웃과 그리기 성능을 저하시키죠. 특히 다중 LinearLayout 에서 weight 속성을 여러번 사용하면
weight 속성의 특성상 자식객체를 두번 측정해야하기 때문에 매우 값비싼 비용을 치르게 됩니다.

여기 아주 단순하고 흔한 레이아웃 예제가 있습니다. 리스트 아이템 왼쪽에 아이콘이 있고, 제목이 있고 제목 밑에 설명이 잇습니다.
어떻게 생겼는지 한 번 보죠.


위의 예제를 분석해 봅시다. 
하나의 ImageView 와 두개의 TextView가 각각의 자리를 차지하고 있습니다. HierarchyViewer 를 통해서 캡쳐한 예제의 틀을 한번 살펴봅시다.



LinearLayout을 통하면 이 레이아웃을 단번에 만들수 있습니다.
아이템 자체는 수평 LinearLayout 이고 하나의 ImageView와 두개의 TextView를 포함한 수직 LienarLayout 이 있습니다.
이 레이아웃의 코드를 살펴봅시다.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    
    android:padding="6dip">
    
    <ImageView
        android:id="@+id/icon"
        
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_marginRight="6dip"
        
        android:src="@drawable/icon" />

    <LinearLayout
        android:orientation="vertical"
    
        android:layout_width="0dip"
        android:layout_weight="1"
        android:layout_height="fill_parent">

        <TextView
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1"
                    
            android:gravity="center_vertical"
            android:text="My Application" />
            
        <TextView  
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1" 
            
            android:singleLine="true"
            android:ellipsize="marquee"
            android:text="Simple application that shows how to use RelativeLayout" />
            
    </LinearLayout>

</LinearLayout>

이렇게 만들면 동작은 하지만 ListView의 각각의 아이템을 인스턴스화 한다면 매우 소모적입니다.
같은 레이아웃을 RelativeLayout을 통해서 만들수가 있는데, 이렇게 함으로서 하나의 View를 아낄수 있고 View 계층에서 한단계 더 높습니다.
RelativeLayout을 사용한 코드를 한 번 살펴봅시다.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    
    android:padding="6dip">
    
    <ImageView
        android:id="@+id/icon"
        
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        
        android:layout_alignParentTop="true"
        android:layout_alignParentBottom="true"
        android:layout_marginRight="6dip"
        
        android:src="@drawable/icon" />

    <TextView  
        android:id="@+id/secondLine"

        android:layout_width="fill_parent"
        android:layout_height="26dip" 
        
        android:layout_toRightOf="@id/icon"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        
        android:singleLine="true"
        android:ellipsize="marquee"
        android:text="Simple application that shows how to use RelativeLayout" />

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        
        android:layout_toRightOf="@id/icon"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:layout_above="@id/secondLine"
        android:layout_alignWithParentIfMissing="true"
                
        android:gravity="center_vertical"
        android:text="My Application" />

</RelativeLayout>

이 구조로 된 레이아웃은 물론 이전의 레이아웃과 똑같이 동작합니다.
단, 리스트아이템을 두 줄로 하고 싶을 때를 제외합니다. 즉, 제목과 설명이 필요하다면 다른 접근이 필요합니다.
주어진 리스트 아이템에 제목말고 설명이 없다면 애플리케이션은 설명에 해당하는 TextView 의 Visibility(보임옵션)을 GONE(보이지도 않고 자리를 차지하지도 않음)으로 처리해버립니다.
이 방식은 LinearLayout에는 완벽하게 동작하지만 RelativeLayout은 동작하지 않습니다.




RelativeLayout에서는 View가 부모객체를 기준으로 정렬을 합니다. 부모가 RelativeLayout 자신이 될 수도 있고, 다른 View가 될 수도 있습니다.
예를 들어, 개발시에 설명 TextView를 RelativeLayout의 아래를 기준으로 정렬하고 제목은 설명위로 정렬하고 부모객체위에 자리를 잡게 설정했다고 한다면 이 상황에서 설명 TextView의 Visibility를 GONE으로 설정시에는 RelativeLayout은 제목의 하단을 어디를 기준으로 정렬해야 할지 모르는 상황이 발생합니다. 이 문제를 해결하기 위해서 아주 특별한 속성값을 사용할 수 있습니다.
layout_alignWithParentIfMissing 입니다.

Boolean 속성을 지니는 이 값이 하는 일은 지정된 타켓이 사라진다면 그에 맞춰 정렬된 다른 객체의 하단을 고정시키는 역할을 합니다.
예를 들어, View를 Visibility 속성이 GONE인 View 옆에 두고 alignParentIfMissing 속성을 true로 놓으면 RelativeLayout은 View를 왼쪽 가장자리에 고정시킵니다.
위의 예에 적용시키면 alignWithParentIfMisiing 속성은 RelativeLayout을 제목의 아래에 정렬시키게됩니다.
결과화면은 다음과 같습니다.





레이아웃의 출력결과는 완벽합니다. 설명이 GONE 이라도 완벽하게 동작합니다. LinearLayout의 weight 옵션도 쓰지 않기 때문에 더 효율적이고 간단합니다.
HierarchyViewer 에서 두 레이아웃의 View 계층도를 확인해보면 더 확실히 구분할 수 있습니다.




다시 말하지만 인스턴스를 위한 리스트뷰에서 각각의 아이템을 나타내기위한 두 레이아웃의 차이점은 매우 중요합니다.
바라건데 UI를 구성하고 최적화하는 방법을 배우는데에 이런 예제들이 도움이 될 수 있었으면 좋겠습니다.
저작자 표시 비영리 동일 조건 변경 허락
신고
리스트뷰 백그라운드의 최적화

리스트뷰는 안드로이드의 가장 많이 사용되는 위젯중의 하나입니다.
리스트뷰는 사용하기 쉽고 유연하며 매우 강력합니다.
가끔씩은 이해하기 어려울 때도 있죠.

리스트뷰의 가장 흔한 문제중의 하나는 리스트뷰의 백그라운드(배경)을 커스텀으로 제작할 때 발생합니다.
다른 안드로이드 위젯들처럼 리스트뷰의 백그라운드는 투명으로 아래의 그림에서 기본 배경색인 #FF191919, 
어두운 회색인 윈도우의 백그라운드를 리스트뷰를 통해 들여다 볼 수 있습니다. 추가적으로 리스트뷰는 
기본값으로 아래의 그림에서 볼 수 있듯이 가장자리에 Fading 효과를 주고 있습니다. 
아래의 그림에서 리스트뷰의 첫번째 아이템이 검은색으로 Fade 되는 것을 볼 수 있습니다.
이 기법이 사용됨으로서 리스트뷰의 내용물이 스크롤 되는 것을 보여줍니다.




위의 그림에서 보이는 Fade 효과는 Canvas.SaveLayerAlpha() 메서드와 Porter-duffDestination Out Blending mode.
메서드의 조합으로 실행됩니다.

하지만 안타깝게도 리스트뷰에 커스텀 백그라운드를 넣거나 윈도우의 백그라운드를 바꾸면 화면이 아래의 그림처럼
이상하게 변해버립니다. 아래의 두개의 스크린샷은 윈도우의 백그라운드를 바꾸면 어떤 일이 일어나는지 보여주고 있습니다.
왼쪽 스크린샷은 Default 값으로 설정했을 때 보여지는 화면이고 오른쪽 스크린샷은 리스트뷰를 스크롤했을때 보여지는 화면입니다.




이렇게 렌더링(화면이 출력되는 것)되는 이유는 안드로이드 프레임워크가 자동적으로 리스트뷰의 모든 인스턴스에 최적화를 실행하기 때문입니다.
앞에서 리스트뷰의 Fade 효과는 Porter-Duff blending mode를 사용해서 실행된다고 했는데 사실 이 메서드는 잘 동작하지만 실행하는데
리스트뷰가 Offscreen Bitmap(forescreen 말고 background의 비트맵 렌더링)의 일부분을 캡쳐해야하고 이 작업이 끝나면 추가적으로
Extra Blending(이 작업이 메모리로부터의 재호출을 필요로 합니다.)을 필요로 하기 때문에 비용이 높은 측면이 있습니다.

리스트뷰는 대개 단색을 배경으로 하기 때문에 이런 복잡한 과정을 거칠 이유가 없습니다.
이런 이유로 "Cache Color Hint"라는 최적화기법을 소개해 드립니다.
Cache color hint 기법은 윈도우의 배경색을 RGB컬러셋 기본값으로 정해놓은 것입니다.
안드로이드의 #191919색(어두운 회색)이 그런것입니다. RGB 컬러값이 정해지면 리스트뷰는(View 클래스의 파생)윈도우의 백그라운드를 단색으로 
설정할 것이라는 것을 알고 비용이 높은 saveLayerAlpha)()/Poretr-duff 메서드 대신에 단순한 그래디언트를 사용해 화면을 그립니다.
이 그래디언트는 완전 투명색부터 Cache color hint 값까지 지원합니다. 위의 그림에서 볼 수 있는 바와같이 리스트뷰의 마지막 리스트의
어두운 색의 그래디언트까지 지원합니다. 하지만 아직도 리스트뷰의 리스트가 스크롤이 될 때 왜 전체 리스트뷰가 검은색으로 변하는지 설명이 안됩니다.

이미 언급했듯이 리스트뷰는 다른 기본 위젯들과 마찬가지로 기본값으로 완전투명과 반투명속성을 가지고 있습니다.
이 말은 리스트뷰가 자식리스트(Children)를 다시 그릴때 자식리스트를 윈도우의 백그라운드의 색상과 혼합해야 한다는 뜻입니다.
다시 말하지만 다시 그리는 작업은 메모리로부터 재호출을 필요로 하므로 비용이 아주 높은 작업입니다.

스크롤 작업시에 그리기 성능을 끌어올리기 위해서 안드로이드 프레임워크는 Cache color hint 값을 재이용합니다.
이 값을 다시 이용함으로서 안드로이드는 각각의 자식(Child)에 hint값의 비트맵색상을 채워넣습니다. 리스트뷰는 이 비트맵이 투명하면서 혼합이 필요하지 않기 때문에
분할해서 채워넣습니다. 또한 기본값으로 백그라운드가 #191919 로 정해져 있기 때문에 리스트뷰를 스크롤 할 때 각각의 아이템의 백그라운드가 검은색으로 보이는 것입니다.

이 문제를 해결하려면 Cache color hint 최적화 옵션을 사용하지 않으면 됩니다.
백그라운드 배경색을 단색이 아닌 색을 넣고 싶다거나 hint 값을 적절한 단색값으로 넣고 싶을때 그렇게 합니다.
코드에서 할 수 있고 XML 속성으로도 설정할 수 있는데 코드로 설정하려면 setCacheColorHint(int) 메서드를 사용합니다.
XML 속성을 사용하려면 android:cacheColorHint 속성을 이용합니다. 최적화 옵션을 끄려면 투명색상인 #00000000 값을 이용하도록 합니다.
아래의 그림은 XML 레이아웃에서 android:cacheColorHint="#00000000" 속성을 이용한 화면입니다.




위 그림에서 볼 수 있듯이 윈도우의 백그라운드 색인 커스텀 나무 색상에 Fade 가 완벽하게 작동되는것을 볼 수 있습니다.
Cache color hint 기능의 특징은 이처럼 흥미로운데 왜냐하면 때때로 최적화 작업이 어떤 상황에서는 개발을 더 힘들게 할 수도 있기 때문입니다.
하지만 이번의 경우에는 기본적인 값의 수정이 복잡한 기능의 추가보다 더 낫다는 걸 보여줍니다.
저작자 표시 비영리 동일 조건 변경 허락
신고

메모리 누출 피하기

메모리 누출 피하기

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는 메모리 누출 방지를 보장하지 않는다.
저작자 표시 비영리 동일 조건 변경 허락
신고