15. 안드로이드 개발하면서 이것만은 피하자

de_sj_awa·2021년 9월 4일
0

15. 안드로이드 개발하면서 이것만은 피하자

요즘 출시되는 안드로이드 장비들은 대부분 CPU Core가 2개 이상이고 화면도 크다. 이러한 안드로이드 개발을 할 때는 성능을 고려하지 않아도 될까?

1. 일반적인 서버 프로그램 개발과 안드로이드 개발은 다르다

서버 프로그램이 구동되는 환경과 안드로이드 프로그램이 구동되는 환경은 다르다.

안드로이드는 오라클이나 IBM에서 만든 JVM을 사용하지 않고, Dalvik VM이라는 것을 사용한다. 자바 코드가 안드로이드에서 수행 가능한 상태가 되려면 다음 절차로 코드가 수행된다.

여기서 첫번째 컴파일은 javac을 통해서 수행되며, 두번째 컴파일은 dex라는 구글에서 제공하는 컴파일러에서 수행된다. 결론적으로 지금까지 알고있는 자바와 문법은 같지만 컴파일러와 가상 머신(VM)은 다르다는 말이다.

그리고 또 다른 점이 있다. 윈도 PC, 맥, 리눅스 장비는 모두 SWAP이라는 메모리 영역을 사용할 수 있다. 다시 말해서 요즘은 보통 4GB ~ 8GB를 사용하는 물리적인 RAM이 부족할 경우 디스크를 메모리처럼 사용하는 SWAP이 발생한다. 이렇게 사용할 경우 메모리를 많이 사용할 수 있다는 장점이 있지만, 아무리 SSD를 사용할지라도 메모리에 접근하는 작업의 성능이 매우 느려진다는 단점이 있다. 그래도 메모리를 풍족하게는 사용할 수 있다.

그런데, 안드로이드의 경우는 이러한 SWAP이 존재하지 않는다. 그러므로, 개발할 때는 작은 부분이라도 메모리를 생각하면서 개발해야 한다.

2. 구글에서 이야기하는 안드로이드 성능 개선

구글에서 제공하는 개발자 사이트에는 성능과 관련된 내용들을 정리한 페이지(http://developer.android.com/traning/articles/perf-tips.html)가 있다. 다루고 있는 주제들은 다음과 같다.

  • Avoid Creating Unneccessary Objects
  • Prefer Static Over Virtual
  • User Static Final for Constants
  • Avoid Internal Getters/Setters
  • Use Enhanced For Loop Syntax
  • Consider Package Instead of Private Access with Private Inner Classes
  • Avoid Using Floating-Point
  • Know and Use the Libraries
  • Use Native Methods Carefully

이 내용들을 간단히 정리해보자.

  • Avoid Creating Unnecessary Objects : 필요 없는 객체 생성을 피하자.
    - String 대신 StringBuffer 사용
    - Integer 배열 대신 int 배열 사용
    - 다차원 배열 대신 1차원 배열 사용
    이렇게 작은 것부터 신경 쓰면서 개발해여 객체가 적게 생성되고, GC도 적게 발생한다.
  • Prefer Static Over Virtual : static을 적절히 사용하자.
    - 만약 인스턴스 변수에 접근할 일이 없을 경우엔 static 메서드를 선언하여 호출하는 것은 15~20%의 성능 개선이 발생할 수 있다.

  • Use Static Final for Constants : 상수에는 static final을 사용하자.
    - 변하지 않는 상수를 선언할 때 static final로 선언할 경우와 static으로 선할 때 저장되고 참조되는 위치가 달라진다. static final이 접근 속도가 훨씬 빠르다.

  • Avoid Internal Getters/Setters : 내부에서는 getter와 setter의 사용을 피하자. 인스턴스에 직접 접근하는 것이 getter나 setter 메서드를 사용하여 접근하는 것보다 빠르다. JIT 컴파일러가 적용되지 않을 경우 3배, 적용될 경우 7배 정도 빨라진다.

  • Use Enhanced For Loop Syntax : 개선된 for 루프를 사용하자.
    - Iterable 인터페이스를 사용하는 대부분의 Collection에서 제공하는 클래스들은 전통적인 for 루프를 사용하는 것보다는 for-each 루프를 사용하는 방법이 더 성능상 유리하다. 하지만, ArrayList는 전통적인 for 루프가 3배 빠르다.

  • Consider Package Instead of Private Access with Private Inner Classes : private한 Inner 클래스의 private 접근을 피하자.
    - 자바에서 Inner 클래스는 감싸고 있는 클래스의 private 변수를 접근할 수 있다. 그런데, VM에서는 내부 클래스와 감싸고 있는 클래스는 다른 클래스로 인식한다. 그래서, 컴파일러는 감싸고 있는 클래스의 private 변수에 접근할 수 있는 메서드를 자동으로 생성해 준다. 따라서, 변수에 직접 접근이 불가하므로 getter나 setter를 사용하는 것처럼 성능이 저하된다.

  • Avoid Using Floating-Point : 소수점 연산을 피하자.
    - 안드로이드 기기에서는 정수 연산보다 소수점 연산이 2배 느리다. 그리고, double이 float보다 2배의 저장 공간을 사용하므로, 가능하다면 float을 사용하는 것을 권장한다.

  • Know and Use the Libraries : 라이브러리를 알고 사용하자.
    - 내가 만든 코드가 최적화된 것일 수도 있겠지만, API에서 제공하는 클래스와 메서드가 훨씬 더 빠를 수 있다. 예를 들면 배열을 복사할 때 Sytem.arraycopy() 메서드를 사용하면 루프를 사용하는 것보다 9배 이상 빠르다.

  • Use Native Methods Carefully : Native 메서드는 유의해서 사용하자.
    - 안드로이드 NDK를 사용한 Native 코드 호출을 할 때는 신중하게 접근해야만 한다. 예를 들면 JIT 컴파일러가 최적화도 못하고, 게다가 각각의 상이한 아키텍처에 따라서 컴파일을 별도로 진행해야 하는 경우도 발생한다.

3. 안드로이드 분석에 도움이 되는 기본적인 툴들

안드로이드로 개발할 때 사용되는 툴의 종류는 매우 많은데, 일반적이고 가장 좋은 툴은 안드로이드 개발 툴킷에 포함된 툴들이다. 먼저 가장 기본적인 디버깅과 관련된 툴에는 어떤 것들이 있는지 한 번 살펴보자.

  • adbd : 안드로이드 기기와 개발 도구(PC)간의 연동을 돕는 툴이다.
  • DDMS : Dalvik Debug Monitor Server의 약자로 adb를 통해서 안드로이드 기기와 상호작용하는 정보를 그래픽 UI로 제공한다.
  • LogCat : 안드로이드 개발할 때도 System.out.println() 메서드로 출력해서 보는 습관은 좋지 않다. LogCat 이라는 로그 확인 툴을 사용하는 것을 권장한다. LogCat은 원하는 로그만 쉽게 볼 수 있도록 필터링하는 기능도 제공한다.

이번에는 분석을 하는 데 도움이 되는 툴을 살펴보자.

  • lint : 정적으로 코드를 분석하는 툴이다. 개발된 앱에 lint를 돌려 한 번에 확인할 수도 있지만, 개발하는 중에 lint 점검을 켜도 확인할 수도 있다. 다시 말해 실시간 코드 분석이 가능하다.
  • Hierarchy Viewer : UI의 레이어 처리 시에 발생하는 성능 저하를 확인할 수 있는 툴이다.
  • Traceview : 앱의 성능상 저하가 발생하는 부분이 어디인지를 확인할 수 있는 프로파일링 툴이다. 많은 정보를 제공하지만, 성능 저하가 심각하다는 단점이 있다. 예를 들어 3초 소요되는 앱이 있을 때 이 툴로 분석할 경우 8초 정도 소요되기도 한다. 즉, 프로파일링으로 인해서 성능 저하가 발생하게 되기 때문에 결과를 믿기 어려운 상황이 된다.
  • dmtracedump : 이 툴은 호출 관계를 그림으로 보여 주기는 하지만, 처음에 사용하기도 어렵고, 제공되는 내용이 그다지 큰 도움이 되지는 않는다.
  • Systrace : 지금까지 살펴본 툴 중에서 가장 쓸 만한 툴이지만, 단점은 최신 기기(android 4.1 이상)에서만 사용할 수 있다는 점이다. 게다가 UI는 사용하기 쉽게 되어 있지만, 수집된 결과를 분석하려면 아주 전문적인 지식이 있어야만 가능하다.
  • Tracer for OpenGL ES : ES(Embedded System)을 위한 OpenGL(그래픽 라이브러리)을 프로파일링하는 툴이다. 만약 OpenGL을 사용한 게임이나 앱을 개발한 경우에 이 툴을 사용하여 분석하면 많은 도움이 될 것이다.

분석하는 툴 이외에 앱을 배포하기 전에 실행해보면 좋은 툴이 있다. 바로 각종 최적화 툴이다.

  • Monkey : 클릭, 터치 등과 같은 사용자의 시스템 레벨 이벤트를 발생시켜 앱의 스트레스 테스트를 수행할 수 있는 툴이다.
  • uiautomator : UI 자동화 테스트를 쉽게 수행할 수 있도록 도와주는 툴이다.
  • ProGuard : 이 툴은 앱을 최적화하고, 사용하지 않는 클래스들, 변수, 메서드들을 제거하는 기능을 제공한다. 게다가 최적화를 위해 코드 수정도 하기 때문에 역 컴파일이 어렵도록 만든다.
  • zipalign : APK 파일의 크기를 효과적으로 줄여 주고, 압축되지 않은 이미지 및 원본 파일들을 최적화한다.

이밖에 ARO라는 툴도 있다. 이 툴은 AT&T라는 미국 통신 회사에서 제공하고 있다. 오픈소스이기 때문에 무료로 사용할 수 있고, 툴을 원핟는 대로 수정해서 사용할 수도 있다. 구글에서 'ARO android'로 검색하면 쉽게 홈페이지를 찾을 수 있다(http://developer.att.com/ARO).

이 툴이 동작하는 원리를 간단히 설명하면 다음과 같다.

1) ARO 수집 에이전트 앱을 안드로이드에 설치한다.
2) ARO에서 제공하는 분석 툴을 PC에서 수행한 후 프로파일링을 시작한다.
3) 분석을 원하는 앱을 실행한다. 그러면 수집된 내용은 모두 안드로이드 기기에 젖아되며, 분석을 종료시키면 해당 데이터들은 분석 PC에 저장된다.
4) 각종 데이터를 확인한다.

또한 ARO 툴은 세 가지 장점을 갖고 있다.

1) 어느 시점에 배터리를 많이 사용하는지를 확인할 수 있다.
2) 네트워크로 주고 받는 내용들을 볼 수 있다.
3) 분석하는 앱을 실행한 내용을 동영상으로 캡쳐해 줘서, 동영상과 데이터를 주고 받는 부분을 sync 되어 볼 수 있다.

이렇게 안드로이드 개발자를 위해 기본적으로 제공하는 많은 툴이 있는데, 여기서는 DDMS와 systrace에 대해서만 간단히 알아보자.

4. 안드로이드 앱의 상황을 확인하는 방법은?

안드로이드에서 가장 간단히 앱의 성능을 측정하는 방법은 DDMS를 활용하는 것이다. DDMS는 안드로이드 개발자 툴(ADT, Android Developer Tools)을 활용하다보면 자연스레 접하게 되는 툴이다. DDMS라는 Perspective가 따로 존재하며, ADT 설치 시 DDMS Perspective를 누르면 다음과 같은 창이 나타난다.

화면의 좌측에는 디바이스 목록이 있고, 우측에는 각종 분석 결과를 보여 주는 화면들이 탭으로 구성되어 있다. 그리고, 하단에서는 앞에서 소개한 LogCat이 위치한다. 따라서 작업 중인 개발 장비에 안드로이드 기기를 USB로 연결하면 좌측의 디바이스 목록에 나타난다.

우측 창에 있는 데이터가 모든 앱에 대해서 제공되는 것은 아니다. 앱을 실행하면 좌측 창의 디바이스 목록 중에서 실행 중인 앱 목록이 나타나는데, 여기에는 디버그 모드로 실행중인 앱들만 나타난다.

안드로이드 앱의 전반적인 설정을 지정하는 AndroidManifest.xml 파일의 application 태그에서 android:debuggable="true"로 지정이 되어야만 디버그가 가능하다. 다시 말해 이 설정이 되어 있지 않으면 DDMS의 좌측 디바이스 밑에 애플리케이션 목록에 나타나지 않는다.

DDMS의 우측 창에서는 어떤 기능들이 제공되는지 확인해 보자.

  • Thread
  • Allocation Tracer
  • Network Statistics
  • File Explorer
  • Emulator Control

여기서 안드로이드 앱을 개발할 때 분석을 위해 필요한 툴은 Thread, Allocation Tracer, Network Statistics이다. 각각의 툴에 대해서 간단히 살펴보자.

  • Thread : 스레드의 현재 상황을 확인하려면 다음의 절차를 따르면 된다.
    - 디바이스 목록 창에서 분석하려는 앱을 선택한다.
    - Update Thread 버튼을 누른다.
    - 스레드 탭에는 선택한 프로세스에 대한 스레드 상황을 확인할 수 있다.

  • Allocation Tracer : 힙 메모리에 어떤 데이터가 어떻게 할당되어 있는지 확인해 보려면 다음의 절차를 따르면 된다.
    - 디바이스 목록 창에서 분석하려는 앱을 선택한다.
    - Update Heap 버튼을 누른다.
    - Allocation Tracer 탭에서 Cause GC 버튼을 눌러 GC를 발생시킨다. 그러면, 힙에 있는 데이터의 목록을 확인할 수 있다. 만약 데이터를 갱신하려면 이 버튼을 다시 누르면 된다.
    - Object type을 누르면 타입별 객체의 개수를 확인할 수 있다.

  • Network Statistics : 네트워크 통계 툴은 해당 디바이스에서 사용하는 네트워크 사용량을 모니터링한다. 다른 앱에서 사용하는 네트워크 사용량과 구분하려면 앱의 소스 코드에 TrafficStats라는 클래스의 메서드를 이용하여 태그를 지정할 수도 있다.

자세한 내용은 이 절에서 다루는 사항들을 상세하게 다루고 있는 안드로이드 개발자 사이트(http://developer.android.com/tools/debugging/ddms.html)를 참고하면 된다.

그렇다면, 안드로이드 앱의 메서드를 프로파일링하는 방법은 없을까? 안드로이드 개발 툴에는 메서드를 프로파일링하는 기능도 포함되어 있다. 바로 TraceView라는 툴인데 DDMS에서 Update Thread 버튼 옆에 있는 Start Method Profiling 버튼을 누르면 프로파일링을 시작한다. 프로팡리링 시작 후 앱의 기능을 이것 저것 수행한 후 Stop Method Profiling 버튼을 누르면 프로파일링이 종료되고, DDMS에서 자동으로 TraceView 화면을 띄운다.

그런데 이 TraceView는 분석하려는 앱의 성능 정보만 제공하는 것이 아니라, 안드로이드 OS에서 점유한 성능 정보도 함께 제공한다. 따라서 성능 저하가 매우 심하다. 만약 3초 소요되는 앱이 있다면 프로파일링을 할 경우에는 5초~8초가 소요될 수 있다. 그 결과로 분석을 하면 성능 저하가 발생하는 지점만 파악할 수 있다고 보면 된다. 프로파일링한 결과로 제공되는 시간은 그냥 참고용으로만 생각하면 된다.

5. systrace를 활용하자

앞서 살펴본 프로파일링 기능은 성능 저하가 심하지만, systrace라는 툴은 성능 저하가 거의 없다. 하지만, 안드로이드 4.1 이상부터 사용할 수 잇고, python이 설치되어 있어야 하며 (Mac OS와 Linux에서는 큰 무리 없이 실행 가능), 분석할 때 참고할 만한 자료가 거의 없다는 것이 단점이다.

이 툴은 기본적으로 UI 성능 분석을 위해서 사용한다. 다음은 Systrace에 대한 가이드가 있는 페이지이다.

이 사이트의 내용을 보면서 systrace의 사용법을 익히면 된다. 간단하게 정리하면 다음과 같다.

1) 안드로이드 장비를 디버그 모드로 설정한다.
2) 분석하고자 하는 대상을 선정한다. Settings > Developer options > Monitoring > Enable traces에서 지정하면 된다. 선택 가능한 대상은 다음과 같다.
- 태그 지정 : gfx(Graphics), input, view, webview, wm(Window Manager), am(Activity Manager), sync(Sync Manager), audio, video, camera
- OS 모니터링 옵션 지정 : PC, 디스크, 작업 큐

3) 옵션을 지정한 후에 adbd shell을 중지하고, 다시 시작한다.

$ adbd shell stop
$ adbd shell start

4) 안드로이드 장비를 USB로 연결한 개발 PC에서 systrace 스크립트로 실행한다.

$ python systrace --cpu-freq --cpu-load --time=10 -o mytracefile.html

이렇게 하면 다음과 같이 수행된다.
- CPU frequency(--cpu-freq)와 CPU 부하(--cpu-load)를 모니터링하고,
- 10초간 데이터를 수집(--time=10)하며
- mytracefile.html이라는 파일에 결과를 저장(-o mytracefile.html)한다.

이 명령을 실행하기 위해 엔터를 치자마자, 분석하고자 하는 앱을 실행한다. 앱이 10초 이내에 원하는 작업을 마치면 정상적으로 로그가 저장된다. 앞서 이야기한대로 해당 스크립트가 수행되자마자 adbd로 연결되어 있는 디바이스의 atrace라는 프로그램이 실행되면서 정보를 수집하기 때문에 이러한 작업이 가능해진 것이다.

atrace는 안드로이드에서 제공하는 커널의 데이터를 수집하는 툴이다. systrace라는 파이썬 언어로 만들어진 스크립트에서는 안드로이드 디바이스에 있는 atrace 명령을 수행하도록 되어 있다. atrace에서는 /sys/kernel/debug/tracing/으로 시작하는 디렉터리에 필요한 파일을 저장하기 때문에 만약 systrace를 수행했을 때 이 디렉터리에 접근 권한이 없다는 등의 오류 메시지가 나타나면 안드로이드 디바이스가 atrace를 사용할 수 있는 상태로 컴파일이 되지 않은 것이다. 이 atrace 명령이 어떻게 되어 있는지 확인하려면 'android atrace source'로 검색하면 atrace.c 파일의 소스를 확인할 수 있다.

정상적으로 분석이 완료되면 해당 파일은 python 스크립트를 수행한 디렉터리에 생성되어 있을 것이다. 이제 저장된 html 파일을 열자. 그러면 다음과 같은 화면이 브라우저에 나타난다.

이 화면에서는 다음과 같은 단축키를 기억해 둬야 분석이 편하다. 단축키를 사용하면 내가 실행한 앱과 안드로이드 OS에서 어떤 작업이 수행되었는지를 확인할 수 있다. 아쉬운 점은 여기서 제공하는 각 이벤트들이 어떤 의미를 가지는지에 대한 상세한 문서가 존재하지 않는다는 것이다.

설명
w 줌 인(가로축 타임라인을 더 짧은 시간으로 확대)>
s 줌 아웃(가로축 타임라인을 더 긴 시간으로 축소
a 좌측 시간으로 이동
d 우측 시간으로 이동
e 현재 마우스 위치의 타임라인을 중앙으로 이동
g 현재 선택한 작업의 시작 그리드 표시
Shift + g 현재 선택한 작업의 끝 그리드 표시
우측 방향키 선택한 타임라인의 다음 이벤트 선택
좌측 방향키 선택한 타입라인의 이전 이벤트 선택
더블 클릭 현재 타임라인 줌 인
Shift + 더블 클릭 현재 타임라인 줌 아웃

추가로 Open GL 처리를 하는 앱을 만든다면 'Tracer for OpenGL ES'라는 툴도 있으니 성능 분석에 사용할 수 있다.

6. 안드로이드에서는 이미지 처리만 잘해도 성능이 좋아진다.

안드로이드 앱 중에서 많은 앱들이 이미지 목록을 처리한다. 이미지 목록은 왼쪽에 이미지가 있고, 우측에 해당 항목에 대한 설명이 있는 화면을 말한다. Google Play나 TStore, Hoppin 등의 앱 및 영화 목록을 제공하는 앱이나, 국민 메신저인 카카오톡의 친구 목록이 여기에 속한다. 그런데, 여기서 이미지에 대한 최적화를 하지 않으면 화면을 아래로 스크롤 햇을 때 사용자가 느끼기에 매우 버벅거리거나 느리다는 느낌을 받게 된다.

이미지가 많을 경우 버벅거리는 현상을 없애는 것은 생각보다 쉽지는 않지만, 다음의 규칙만 잘 따라도 의외로 쉽게 해결할 수 있다.

1) 이미지는 크기가 얼마나 되나 확인해 보자.
2) ImageView의 setImageResource() 메서드 사용을 자제하자.

각각의 사항을 조금 더 자세히 살펴보자.

이미지는 크기가 얼마나 되자 확인해 보자

목록에서 보이는 이미지의 크기가 얼마나 되는지 생각해 보자.

혹시 목록의 크기가 영화 포스터인데, PC에서 해당 이미지를 다운로드해서 보면 영화배우의 잔주름까지 확인할 수 있을 정도로 해상도가 높은가? 그렇다면 이미지를 최적화하는 프로그램을 찾아봐야 한다. 앱에서 사용자에게 보여주는 썸네일 이미지 크기는 정말 대부분은 손톱만한 크기다. 이렇게 보여주는 이미지는 해상도가 좋을 필요가 없다. 그러므로, 필요한 크기로 미리 서버에서 크기 조정 및 압축률을 변경하면 서버에서 안드로이드 디바이스로 전달되는 파일의 크기도 작아지고, 앱에서 처리하는 이미지의 크기도 작아 사용자가 느끼는 체감 속도는 당연히 빨라질 것이다.

ImageView의 setImageResource() 메서드 사용을 자제하자

해당 메서드에 대한 안드로이드 API를 보면, 이 메서드를 사용하면 Bitmap 이미지를 읽고 디코딩하는 작업을 UI 스레드에서 수행하기 때문에 응답 시간이 저하된다고 되어 있다. 따라서, setImageDrawable(android.graphics.drawable.Drawable)나 setImageBitmap(android.graphics.Bitmap) 메서드를 사용하고, BitmapFactory 사용을 권장하고 있다. 추가로 ImageView를 사용하는 것보다 WebView를 사용할 경우에도 큰 효과를 볼 수 있다. 그러므로, 안드로이드 앱의 상황에 맞는 처리 방식을 찾아서 비교해야 한다.

이렇게 보면 안드로이드 앱에서 이미지 처리 부분의 성능 개선 작업이 별 것 아닌 것처럼 느껴질 수 있지만, 실제 사용자들이 체감하는 속도는 많은 차이가 발생한다.

참고

  • 자바 성능 튜닝 이야기
profile
이것저것 관심많은 개발자.

0개의 댓글