drcarter의 DevLog

안드로이드 개발을 하다 보면 지금보다 조금 더 성능을 낼 수 있는 앱을 만들고 싶어질 때가 있습니다. 개발자 마다 코딩 스킬도 틀리고 더군다나 디바이스마다 스펙도 틀리기 때문에 같은 앱을 누가 만드느냐 어떤 디바이스에서 동작하느냐에 따라서 앱의 performance가 틀리게 나옵니다. 디바이스 스펙은 어쩔 수 없지만 개발하는 중간중간 조금의 신경을 쓴다면 성능이 좋은 앱을 만들 수 있을 거라 생각 됩니다. 안드로이드 앱을 개발하면서 경험했던 내용에 대해서 조금 정리를 해볼려고 합니다.


■ Traceview Profiling (http://developer.android.com/tools/debugging/debugging-tracing.html)

자신이 개발한 메소드 안에서 수행시간이 얼마나 걸리는지 알아보고, 수행 시간이 많이 걸렸다면 그만큼 performance에 영향을 줍니다. 그중에서 Android SDK 에 포함되어 있는 Traceview라는 것이 있습니다. 각 메소드에 대해서 프로파일링 한 결과를 그래픽으로 보여주는 좋은 툴입니다. 하지만 처음 사용하면 이게 뭐지?? 라는 생각을 가지고 있는 좀 복잡해 보이기도 합니다. 


그림과 같이 "Start Method Profiling" 버튼을 클릭 후 앱을 동작시킵니다. 그럼 동작되는 동안 수행되는 메소드에 대해서 프로파일링을 진행합니다. 그리고 나서 같은 위치에서 모양이 바뀐 "Stop Method Profiling" 버튼을 클릭 하고 나면 프로파일링이 끝난 결과를 화면에 보여 줍니다.

딱 이그림이 처음 보면... 뭐 이리 복잡해?? 라는 생각을 갖게 하는 그림 입니다. 

표의 내용은.

 - Incl Cpu Time % / Incl CPU Time : 해당 메소드 및 그것이 호출하는 자식메소드를 포함한 CPU의 수행 시간.

 - Excl Cpu Time % / Excl CPU Time : 메소드가 수행된 CPU 시간

 - Incl Real Time %/ Incl Real Time : 해당 메소드 및 그것이 호출하는 자식 메소드를 포함한 실제 시간.

 - Excl Real Time % / Excl Real Time : 메소드가 수행되는 실제 시간.

 저는 이중에서 Excl Real Time% / Excl Real Time 을 중요하게 봅니다. 해당 표의 값이 높으면 다른 Excel cpu time 이나 incl real time 도 높게 나타날 것입니다. 해당 부분을 클릭 하면 아래로 더 자세한 내용이 나옵니다. 저는 테스트하기 위해서 AsyncTask에서 String의 조합을 강제로 10000회 하도록 했습니다. 전체 프로파일링 중에 대부분 해당 부분에서 수행시간이 많이 나왔습니다.

설명은 DDMS를 이용한 방법을 설명했지만 직접 trace 파일을 생성해서 traceview로 보는 방법도 있습니다. 그건 Android 개발자 페이지에 더 자세하게 나와있으니 잠깐이라도 봐보시기 바랍니다.

■ GUI Optimization

http://developer.android.com/tools/debugging/debugging-ui.html

1. Hierarchy Viewer

Hierarchy Viewer 현재 화면에 보여지는 layout에 대하여 Tree View 형태로 보여주며 개발자가 UI 인터페이스를 디버깅 하고 분석할 수 있도록 도와 주는 도구 입니다. 각 component에 대해서 소비 시간을 분석과 다른 component와 비교도 할 수도 있고 각각의 속성도 파악 합니다. 그리고 해당 component가 어떤 화면인지에 대해서도 볼 수 있습니다.

그림과 같이 화면에 대한 분석화면을 볼 수 있고,


이와 같이 각 component에 대해서 분석한 화면도 볼 수 있습니다.맨 하단에 있는 Measure, layout, and draw performance indicators for this view에 나온 녹색, 노란색, 빨간색 원을 볼 수 있는데 녹색>노란색>빨간색 순으로 좋다고 보시면 됩니다. 그런데 해당 component에 대해서 무조건 녹색을 만들기는 어려워 보입니다. layout의 depth가 깊어지면 제일 상위에 있는 layout에 대해서는 녹색보다는 노란색과 빨간색을 더 많이 보여줍니다. 그래서 부모쪽 보다는 최 하단에 있는 자식 layout과 그에 해당하는 component에 대해서 최적화를 많이 합니다. 그러다 보면 상위로 갈 수록 빨간색과 노란색 보다는 녹색의 비율이 더 많아지더군요.


 - Lint warnings : Android resource(layout. string 등등)을 검사하여 잠재적으로 오류를 나타낼 수 있는 부분에 대하여 미리 검사를 합니다. 실행에는 전혀 문제가 없지만 경고가 잠재적인 오류를 가지고 있기 때문에 확인해 볼 필요는 있습니다.

 - layoutopt : layout의 xml 파일에 대해서 분석하여 비효율적인 부분에 대해서 알려주는 console tool.

Lint Warnings와 layoutopt에 제가 보는 것은 layout의 depth입니다. 지금 layoutopt는 android sdk tool에서 보이지 않습니다. 언제 사라진 것인지도 잘 모르겠습니다. 그래서 요즘은 Lint Warnings를 통해서 layout을 확인합니다. xml에서 layout을 만들고 lint warnings로 확인하다 보면 가끔 

이와 같이 "This RelativeLayout layout or its LinearLayout parent is useless"라는 메시지를 보이는 곳이 있습니다. RelativeLayout이나 LinearLayout은 경우에 따라 달라질 수도 있습니다. 쉽게 현재 사용중인 layout의 부모 layout은 쓸모가 없다는 내용입니다. 저는 이런게 보이면 과감하게 현재보다 바로 윗단계이 있는 layout에 대해서는 과감하게 삭제 합니다. 적어도 쓸모가 없기 때문입니다. Layout 의 Depth를 적게 하면 할수록 화면을 그리는 UI Thread의 작업이 조금이나마 줄어들것입니다. 작업이 줄어들 수록 수행 속도가 빠르다는 것도 다들 알 것이구요. 

제가 경험한 것으로는 한 화면에 너무 많은 컴포넌트들을 보여주고 싶어하는 디자이너와 기획자의 요구에 따라서 화면을 구성하다 보니 많은 layout을 사용하게 되었습니다. 단방향으로 고정된 화면에서 문제가 없었지만, 수많은 layout과 depth가 깊은 화면에 대해서 Animation을 수행할 때와 layout/layout-land 로 구성된 화면에서 screen rotaton을 하여 portrait과 landscape 화면에 대하여 서로 다른 화면을 구성할 때 문제가 되었습니다. 그래서 가급적이면 화면을 구성할 때 layout의 depth를 적게 하라고 권하고 싶습니다.


■ GC(Garbage Collection)

Android 는 디바이스마다 Heap 크기도 틀리고 하다 보니.. 메모리 관리도 중요하다고 할 수 있죠. Java의 GC(Garbage Collection)만 얘기도도 너무나 많으 내용이 될듯 싶고, 찾압면 정말 자세하게 설명해둔 자료들도 많으니 따로 찾아보셔도 될듯 합니다. 앱 개발하면서 메모리를 얼마나 많이 사용 되고 있는지 확인해볼 방법은 여러가지가 있겠지만 전 두가지를 알려드릴려고 합니다. DDMS에 있는 "Allocation Tracker"와 Heap dump 파일 분석을 통해 알아볼까 합니다.

- Allocation Tracker

DDMS에서 확인하고 싶은 앱 선택 후 Allocation Tracker에서 Start Tracking 클릭 후 GetAllocations를 누르면 현재 메모리가 할당된 Object들의 목록이 나타납니다. Allocation Size를 제일 많이 차지하고 있는 항목을 선택하고 나면 두번째 테이블에는 해당 Object의 Stack Trace 정보가 출력됩니다. 만약 해당 Allocation Size가 비정상적으로 많다면 해당 방법으로 잘못된 곳을 찾을 수도 있을것입니다.

- HPROF

힙덤프 파일을 생성해서 분석해볼 수도 있습니다. 힙덤프 생성하는 방법은 adb를 이용하거나 api를 이용. 또 DDMS를 이용해서 할 수 있습니다.

1. ADB 이용

$ adb shell

# mkdir /data/misc

# chmod 777 /data/misc (/data/misc에 쓰기권한 확인)

# ps (애플리케이션 프로세스 ID 확인)

# kill -10 <pid> (SIGUSR1 시그널 전송)

(잠시후 /data/misc 에 heap-dump-*.hprof 파일 생성됨)

$ adb pull <dump-file> <as-file> (로컬로 파일 추출)

ADB shell을 이용할려면 root 권한이 있어야 하고.. 매번 이런식으로 shell 접속하기 귀찮아서 잘 사용 안합니다. ㅡㅡ.

2. API 이요

android.os.Debug.dumpHprofData(fileName);

- fileName의 위치는 쓰기 권한이 있는 곳이어야 합니다. 외장메모리 영역이면 괜찮겠죠? 대신 WRITE_EXTERNAL_STORAGE permission 지정을 잊지 않으시면 됩니다.

3. DDMS 이용.

가장 현실적인 이용방법입니다. adb shell을 일일이 드어가기 귀찮고. 그렇다고 api를 이용하면 앱 release 시에는 해당 부분을 삭제 해 줘야 하기도 하고. 그리고 해당 heap dump 파일을 분석해주는 Eclipse MAT라는 plugin도 있습니다.


Dump HPROF file 버튼을 클릭 합니다. 저가은 경우는 Eclipse MAT플러그인을 상용하는 관계로 바로 아래와 같은 그래픽컬한 화면이 나옵니다. 바로 dump된 파일을 분석해서 그래프로 보여주는 것이죠. 

Shallow Heap과 Retained Heap을 통해서 어디서 메모리 누수가 발생하는지 확인을 할 수 있습니다.

Shallow Heap : 객체 하나당 크기.

Retained Heap : 참조가 유지하고 있는 모든 객체의 크기.

만약 상위  " Retain Heap < 하위 Retain Heap + 모든 Shallow Heap "  으로 나온다면 계산하고 있는 하위 객체에 대해서 메모리 누수가 발생하고 있다고 보면 됩니다.


이외에도 WakeLock을 어떻게 사용하느냐에 따라서 앱 전체 성능에 영향이 있을 수도 있습니다.

이 방법들 이외에도 다른 방법들도 많으 것으로 보입니다. 그런데 이런것으로 확인하는 것도 좋지만, 개발자가 개발할 당시에 조금이라도 신경써서 하면 개발한다면 좋은 성능을 낼 수 있는 앱을 만들 수 있겠죠. 개발이 완료되어 앱이 릴리즈 되었다면 개발되었던 것을 다시 돌아보면서 리펙토링 하는 시간도 갖는다면 좋을 것이구요. 하지만 현실은 그렇지 않아 보입니다. 현실은 하나 완료하기도 전에 다음 업데이트 일정들이 나오고 있으니까요.