2016년 10월 12일 수요일

[안드로이드] Doze 모드

Doze 모드 최적화

안드로이드 6.0(API 23)부터 배터리를 절약하기 위해 Doze 모드가 추가되었다. 사용자가 휴대폰을 사용하고 있지 않다고 판단되면, 시스템은 모든 앱의 배터리를 많이 잡아먹는 백그라운드 작업과 네트워킹을 일시 중단시킨다. Doze 모드는 타겟 API가 23 이하의 앱이더라도 동일하게 적용되므로, Doze 모드에서의 앱 동작을 확인해봐야 한다.

Doze 모드 이해하기

디바이스에 전원공급이 없고, 스크린이 꺼진 채로 아무 움직임 없이 일정 시간이 지나면, 디바이스는 Doze 모드로 진입하게 된다. Doze 모드에 진입하게 되면, 시스템은 배터리를 절약하기 위해 모든 앱의 네트워크 사용과 CPU를 과도하게 이용하는 백그라운드 서비스, Sync, 기본 알람까지 제한한다.
주기적으로, 시스템은 Doze모드에서 깨어나, 모든 앱이 미뤄뒀던 작업을 완료할 수 있는 Maintenance Window라 불리는 짧은 시간을 가진다. 이 짧은 시간동안, 시스템은 Doze 모드에 진입하면서 멈췄던 작업들을 다시 시작하도록 수행한다.
디바이스의 화면이 켜지거나, 전원공급이 시작되면 앱은 Doze 모드에서 벗어나 원래대로 모든 작업을 수행하게 된다.
6.0 Doze
최근, 7.0(API 24)에서는 디바이스에 전원공급이 없고 화면만 꺼져있으면, 디바이스가 움직이더라도 Doze 모드로 진입한다. 그리고 Doze 모드가 2단계로 세분화되었다.
1단계에서는 네트워크 엑세스, Sync, Job 스케쥴링 정지, 2단계에서는 GPS, Wifi 스캐닝, wake lock 등이 제한된다.
7.0 Doze

Doze 모드에서 제한되는 동작

  • 네트워크 엑세스
  • Wake Lock
  • AlarmManager의 기본 알람
    • setAndAllowWhileIdle() / setExactAndAllowWhileIdle() 메서드를 이용한 알람은 Doze모드에서도 받을 수 있다.
    • setAlarmClock() 메서드를 이용한 알람은 정상적으로 동작한다. 이 알람이 울리기 전에, 시스템은 Doze 모드를 빨리 빠져나온다.
  • Wifi 스캐닝
  • Sync Adapter 동작
  • Job Scheduler 동작

Doze 모드 적용

Doze 모드에 대한 영향은 앱마다 다 다를것이다. 대부분의 앱들은 아무런 변경 없이도 Doze 모드 주기안에 잘 동작할 것이다. 하지만, 네트워크, 알람, Job, Sync 를 이용하고 있다면 Doze 모드를 확인해 볼 필요가 있다. 이 앱들은 각maintenence window 동안에 효율적으로 동작하도록 만들어야 한다.
AlarmManager의 경우, 기존에 있던 API 메서드를 이용하면, Doze 모드에서는 알람이 정확하게 울리지 않는다. 그래서 Doze 모드에서도 알람이 제대로 동작하도록 하려면 6.0(API 23)에서 추가된setAndAllowWhileIdle()setExactAndAllowWhileIdle() 메서드를 이용해야 한다.
앱이 실시간으로 연결을 유지하며 메시지를 받을 필요가 있다면, Google Cloud Messageing 을 고려해본다.

Doze 모드 감지
브로드캐스트 리시버를 등록하고, PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED 액션을 수신하도록 등록하면, Doze 모드일 때, 디바이스가 대기 상태로 변경되며, 해당 액션이 발생하게 된다.
if (Build.VERSION.SDK_INT >= VERSION_CODES.M) {
    registerReceiver(mDeviceIdleModeReceiver, new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED));
}

App Standby 이해하기

App Standby는 사용자가 앱을 이용하지 않을 때, 앱이 대기상태인지를 결정한다. 시스템은 일정시간 동안 앱에 아무런 동작이 없고, 다음의 상황이 아닐 때, 앱을 대기상태로 인지한다.
  • 사용자가 명시적으로 앱을 실행
  • 앱이 현재 포그라운드 프로세스를 가지고 있음
  • 사용자가 Notification 영역을 보고있고, 그 영역에 Notification을 출력해야 할 때
디바이스가 전원공급이 시작되면, 시스템은 이 앱들을 standby 상태로부터 해제하고, 앱의 네트워크 액세스, 스케쥴링 등 제한했던 동작을 수행하도록 한다.

디바이스가 대기 상태일 때, GCM을 이용해 인터렉션하기

GCM은 클라우드와 하나의 영구적인 연결을 유지하여, 실시간 메시징이 필요한 모든 앱들이 이 연결을 공유할 수 있도록 한다. 연결을 공유하는 이 기법은 앱마다 자신만의 연결을 유지하는 것보다 배터리를 훨씬 절약할 수 있도록 한다. 이러한 이유때문에, 구글은 실시간 메시징이 필요하다면, GCM을 이용하는걸 추천한다.
GCM 메시지의 우선순위를 높여주게 되면, 디바이스가 Doze 모드에 있거나, 앱이 Standby 상태 이더라도 네트워크에 연결할 수 있도록 수행한다. 동작 수행 후, 다시 대기 상태로 돌아간다. 즉, GCM 메시지는 Doze 모드나 App의 상태에 영향을 받지 않고, 효율적으로 상호작용 할 수 있다.

Doze & App Standby 모드 테스트하기

Doze 모드 테스트

1.앱을 실행하고, 아무 동작도 하지 않는다.
2.디바이스 스크린을 끈다.(앱은 그대로 Active 상태로 둔다.)
3.다음 명령어를 command에 입력하여 adb를 이용해 강제로 Doze모드 발생 상태를 만든다. 단, 디바이스의 상태가 변할 때까지 두번째 문장은 계속 실행한다.
$ adb shell dumpsys battery unplug
$ adb shell dumpsys deviceidle step
4.디바이스를 다시 활성화시킨 후에 앱이 정상동작 하는지 확인한다.

App Standby 모드 테스트

1.앱을 실행하고, 아무 동작도 하지 않는다.
2.command에 다음 명령어를 실행하여 강제로 앱을 Standby 상태로 만든다.
$ adb shell dumpsys battery unplug
$ adb shell am set-inactive <packageName> true
3.command에 다음의 명령어를 실행하여 앱을 깨운다.
$ adb shell ab set-inactive <packageName> false
$ adb shell ab get-inactive <packageName>
4.앱이 원상태로 돌아온 후에 정상동작 하는지 확인한다. 특히, Notification이나 Job 등이 생각한대로 동작하는지 확인한다.

2016년 10월 10일 월요일

[안드로이드] 클립보드 프레임워크 기초

Clipboard 프레임워크

안드로이드는 앱간의 데이터 이동을 위해 OS 차원에서 클립보드를 지원하고 있다. 클립보드를 이용해 공유할 수 있는 데이터 포멧은 다음과 같다.
  • 텍스트 : 단순 문자열.
  • URI : 데이터의 위치를 가리키는 URI. 실제 데이터를 제공하려면 ContentProvider를 구현해야 한다.
  • Intent : 앱 실행 명령과 관련된 데이터.
  • HTML 텍스트 : HTML 형태의 서식있는 문자열. 4.1(API 16)부터 추가되었다.
각 데이터 타입에 따라 MIME 타입을 지정함으로써, 클립보드로부터 데이터를 받는 애플리케이션은 이 MIME 타입을 읽어서 데이터가 무슨 타입인지 판별할 수 있다. 클립보드는 저장 공간이 한개 뿐이므로, 한번에 하나의 데이터만 교환할 수 있다. 클립보드에 새로운 데이터가 들어오면, 기존에 있던 데이터는 삭제된다.(즉, 시스템 차원에서는 히스토리를 제공하지 않는다.)
Clipboard Framework

주요 클래스

ClipboardManager

안드로이드에서 클립보드는 시스템이 관리하는 자원이므로, 애플리케이션이 마음대로 생성, 삭제할 수 없다. 대신, getSystemService(CLIPBOARD)SERVICE) 메서드를 이용해 객체를 얻을 수 있다. 이 객체를 이용해 클립보드에 데이터(ClipData)를 저장하거나, 저장된 데이터(ClipData)를 읽을 수 있다. ClipboardManager는 가장 최근의 ClipData 한개만 가지고 있다.

ClipData

실제로 클립보드에 저장되는 주체로, 한 개 이상의 실제 데이터(ClipData.Item)와, 이 데이터의 메타데이터(ClipDescription)을 포함한다.

ClipDescription

클립보드에 저장될 데이터의 메타데이터를 나타내는 클래스이다. 메타데이터 중에는 원본 데이터의 타입을 가리키는 MIME 타입의 배열을 지정할 수 있는데, 클립보드를 읽는 애플리케이션에서는 이 MIME타입을 읽어서, 처리할 수 있는 데이터를 식별한다.

ClipData.Item

클립보드에 저장되는 실제 데이터. 텍스트, URI, Intent, HTML 텍스트를 저장할 수 있다. ClipData객체에 한개 이상의 Item 객체를 추가할 수 있는데, 이는 다중 선택을 하나의 클립으로 취급하기 때문이다. 단, 여러 item을 ClipData에 추가하려면 item의 데이터 타입이 동일해야 한다.

Copy & Paste

Copy 구현

1.URI 형태로 임의의 데이터를 클립보드에 저장하려면, 해당 URI에 접근했을 때 실제 데이터를 얻을 수 있도록 ContentProvider를 제공한다.
2.ClipboardManager 객체를 얻는다.
3.ClipData 객체를 생성한다.
4.클립보드에 추가한다.
...
// 임의의 메서드
public void copy() {
    // 클립보드 객체 얻기
    ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
    // 클립데이터 생성
    ClipData clipData = ClipData.newPlainText("Test Clipboard", "Test");
    // 클립보드에 추가
    clipboardManager.setPrimaryClip(clipData);
    ...
}
...

Paste 구현

1.ClipboardManager 객체를 얻는다.
2.ClipDescription 객체를 얻어서 원하는 MIME 타입인지 조사한다.
3.MIME 타입에 맞게 처리한다.
...
// 임의의 메서드
public void paste() {
    // 클립보드 객체 얻기
    ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
    if(!(clipboard.hasPrimaryClip())) {
        // 클립보드 데이터가 있을 때 처리
        ClipData data = clipboard.getPrimaryClip();
        ...
        if(clipboard.getPrimaryClipDescription().hasMimeType(MIMETYPE_TEXT_PLAIN)) {
            // MIME 타입이 텍스트일 때 처리
            ...
        }
    }
    ...
}
...

Clipboard 변경시점 알기

ClipboardManager객체에 ClipboardManager.OnPrimaryClipChangedListener 리스너를 등록하면, onPrimaryClipChanged() 메서드를 구현하여 클립보드가 변경되었을 때 콜백을 받을 수 있다.
public class ClipboardService extends Service implements ClipboardManager.OnPrimaryClipChangedListener {
    ClipboardManager mManager;

    @Override
    public void onCreate() {
        super.onCreate();
        mManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
        // 리스너 등록
        mManager.addPrimaryClipChangedListener(this);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 리스너 해제
        mManager.removePrimaryClipChangedListener(this);
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onPrimaryClipChanged() {
        if (mManager != null && mManager.getPrimaryClip() != null) {
            ClipData data = mManager.getPrimaryClip();
            
            // 한번의 복사로 복수 데이터를 넣었을 수 있으므로, 모든 데이터를 가져온다.
            int dataCount = data.getItemCount();
            for (int i = 0 ; i < dataCount ; i++) {
                Log.e("Test", "clip data - item : "+data.getItemAt(i).coerceToText(this));
            }
        } else {
            Log.e("Test", "No Manager or No Clip data");
        }
    }
}

2016년 10월 6일 목요일

[안드로이드] Gradle로 여러가지 버전 생성하기

Gradle로 여러가지 버전 생성하기

  • Gradle은 하나의 프로젝트에서 여러가지 버전의 APK를 생성할 수 있도록 지원한다.
  • Build Variants = BuildType + Product Flavor
  • Gradle을 이용하면 Build Variants 별로 각각 다른 APK를 한번에 생성할 수 있다.

BuildType

  • 일반적으로 애플리케이션의 개발 과정에 따라 나뉠 수 있다.(debugalphabetarelease 등)
  • 안드로이드 프로젝트를 처음 생성하면 Gradle은 기본적으로 debugrelease의 빌드타입을 생성한다.
  • 빌드타입에 따라 키스토어 적용 여부, 디버그모드 허용 여부, 프로가드 적용 여부 등을 다르게 설정할 수 있다.
  • 빌드타입 별로 소스코드, 리소스를 다르게 적용할 수 있다.(이 후의 SourceSet 부분 참조)
  • 모듈별 build.gradle 스크립트에 buildTypes 속성을 추가하고 원하는 빌드타입을 추가한다.
  • 다음 예시는 alphabetarelease 빌드타입을 추가한 것이다.
android {
    ...
    buildTypes {
        alpha {
            applicationIdSuffix '.alpha'
            debuggable true
            minifyEnabled false
        }
        beta {
            applicationIdSuffix '.beta'
            debuggable true
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        release {
            applicationIdSuffix '.release'
            debuggable false
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    ...
}

Product Flavor

  • 동일한 앱의 서로 다른 버전을 의미(ex : free버전, paid버전 등)
  • 안드로이드 프로젝트를 처음 생성하면 Product Flavor는 존재하지 않는다.
  • 이 속성에 따라 applicationId, 버전코드, 버전명 등, 앱의 속성을 다르게 설정할 수 있다.
  • 빌드타입과 마찬가지로 소스코드, 리소스를 다르게 적용할 수 있다.
  • 모듈별 build.gradle 스크립트에 productFlavors 속성을 추가하고 원하는 flavor들을 추가한다.
  • 다음 예시에서는 freepaid 를 추가한 것이다.
android {
    ...
    productFlavors {
        free {
            applicationId 'com.test.app.free'
            versionCode 2
            versionName '1.0.1'
        }
        paid {
            applicationId 'com.test.app.paid'
            versionCode 1
            versionName '1.0.0'
        }
    }
    ...
}

Source Set

  • Gradle은 Build Variants의 소스코드, 리소스를 각각 다르게 설정할 수 있다.
  • 지정하고자 하는 디렉터리들을 미리 생성해두어야 한다.
  • BuildType과 ProductFlavor의 조합을 각각 지정할 수도 있고, 필요한 경우만 지정할 수도 있다.
    • BuildType과 ProductFlavor를 조합하여 지정할 경우 ProductFlavor+BuildType의 순서로 작성한다.
    • ex) debug & free => freeDebug
  • 다음의 예시는 위의 Build Variants를 각각 지정하여 변경한 것이다.
android {
    ...
    sourceSets {
        // main은 디폴트 위치
        main {
            manifest.srcFile 'src/main/AndroidManifest.xml'
            java.srcDirs = ['src/main/java']
            res.srcDirs = ['src/main/res']
        }
        // Product Flavor만 따로 지정하는 경우
        free {
            manifest.srcFile 'src/free/AndroidManifest.xml'
            java.srcDirs = ['src/free/java']
            res.srcDirs = ['src/free/res']
        }
        paid {
            manifest.srcFile 'src/paid/AndroidManifest.xml'
            java.srcDirs = ['src/paid/java']
            res.srcDirs = ['src/paid/res']
        }
        // Build Type만 따로 지정하는 경우
        debug {
            res.srcDirs = ['src/main/resDebug']
        }
        alpha {
            res.srcDirs = ['src/main/resAlpha']
        }
        beta {
            res.srcDirs = ['src/main/resBeta']
        }
        release {
            res.srcDirs = ['src/main/resReal']
        }
        // 둘을 조합하여 지정하는 경우
        freeDebug {
            res.srcDirs = ['src/free/resDebug']
        }
        freeAlpha {
            res.srcDirs = ['src/free/resAlpha']
        }
        freeBeta {
            res.srcDirs = ['src/free/resBeta']
        }
        freeRelease {
            res.srcDirs = ['src/free/resReal']
        }
        paidDebug {
            res.srcDirs = ['src/paid/resDebug']
        }
        paidAlpha {
            res.srcDirs = ['src/paid/resAlpha']
        }
        paidBeta {
            res.srcDirs = ['src/paid/resBeta']
        }
        paidRelease {
            res.srcDirs = ['src/paid/resReal']
        }
    }
    ...
}

변경 내용 확인하기

  • 안드로이드 스튜디오의 Project 인스펙터를 연다. (맥 단축키 cmd + 1)
  • 왼쪽 하단의 Build Variants를 선택하면 Project 인스펙터 하단에 Build Variants 인스펙터가 열린다.
  • 모듈별로 Variants를 선택할 수 있는데, variants를 선택하면 Project 인스펙터에서 선택된 폴더를 확인할 수 있다.
BuildType

2016년 10월 5일 수요일

[안드로이드] 성능향상 팁

1.불필요한 객체생성을 피하라

불필요한 객체생성은 메모리의 낭비를 가져오게 되고, 결국에는 GC작업으로 이어지게 된다. GC는 매우 비싼 연산이므로, 성능에 매우 악영향을 미치게 된다. (2.3에서 동시 가비지 컬렉터가 소개되긴 했지만, 그럼에도 비싼 연산임을 유념하자!)
다음은 가장 흔한 몇 가지의 예를 보여준다.
  • 어떤 함수가 String을 반환하고, 그 반환값이 항상 StringBuffer에 추가되는 경우가 있다면, 메서드를 변경하여 내부에서 직접 StringBuffer에 추가하도록 한다.
  • 어떤 입력된 String 데이터로부터 문자열을 추출하는 경우, 새로 문자열을 생성하는 대신 기존 데이터의 substring() 메서드를 이용하도록 한다.
  • Integer 등 Wrapper 배열을 이용중이라면, primitive 타입 배열로 변경을 고려한다.
  • (Object1, Object2) 튜플 형태의 배열 1개를 이용중이라면, Object1[], Object2[]의 배열 2개로 변경을 고려한다.(단, API 제작 등 성능보다는 디자인패턴이 중요할 경우 예외)
즉, GC 작업을 피하기 위해, 불필요한 객체생성은 가능하면 피하는 것이 좋다.

2.Virtual 메서드보다는 Static 메서드를 이용하라

만약, 메서드 내에서 객체의 멤버필드를 이용하지 않는다면, 메서드를 static으로 변경하는 것을 고려한다. 메서드 실행이 대략 15~20% 정도 빨라질 것이다. 또한, 메서드 호출이, 객체의 상태를 바꾸지 못하는 것을 메서드 원형을 통해 바로 알 수 있어서 코드 분석하기 좋다.

3.상수에는 static final 키워드를 이용하라

static int intVal = 42;
static String strVal = "Hello";
상수를 선언하기 위해 다음과 같이 final 키워드 없이 static 필드를 이용하게 되면, 컴파일러가 이 값들을 해당 변수에 할당하고, 이 변수들을 검색테이블에 저장한다. 나중에 이 변수들을 이용하려면, 컴파일러는 이 검색테이블을 통해 변수를 검색하여 값을 얻어오게 된다.
static final int intVal = 42;
static final String strVal = "hello";
다음과 같이 final 필드를 함께 이용할 경우, 이 값들은 dex 파일의 static 영역에 함꼐 저장된다. 이 경우, 변수를 이용하려면 컴파일러는 검색테이블을 거치지 않고 바로 값에 접근한다. 그렇기에 일반적으로는 static final 필드가 변수 접근속도가 빠르다.
단, 이는 primitive 타입과 String 상수에 한해 유효하다.

4.클래스 내부에서의 Getter/Setter는 피하라

일반적인 OOP 네이티브 언어(C++, C#, JAVA 등)는 멤버필드의 Getter/Setter를 지향하며, 클래스 내부에서도 사용하기를 권장한다. 이는 컴파일러가 대부분 인라인으로 엑세스하기 때문에 성능이슈가 거의 없고, 필요에 따라 변수에 제약조건을 둘 수 있기 때문이다.
하지만, 안드로이드에서는 같은 자바 언어를 사용하지만 컴파일러의 동작이 다르기 때문에 클래스 내부에서의 Getter/Setter 사용은 변수에 바로 접근하는 것보다 매우 비싼 연산이다.(심지어 변수를 검색테이블에서 찾는 것 보다도 느리다.) 그러므로, 자신의 멤버필드를 접근할 때는 가급적 Getter/Setter를 피하고 직접 이용하도록 한다.
Note : 클래스 외부에서 Getter/Setter를 이용하지 말자는 의미가 아니다. OOP를 지향하기 위해 클래스 외부에서는 Getter/Setter를 이용하는 것이 좋다.

5.향상된 for 문을 이용하라

자바 1.5부터 지원하고 있는 향상된 for문 (일명 for-each문)은 일반 배열과 Collections 객체에 대해 이용할 수 있다. Collections를 향상된 for문으로 돌리면 컴파일러가 Iterator를 이용하는 방법으로 바꿔서 컴파일한다.(즉, 명시적으로 Iterator를 이용하는 것과 동일하다.) 일반 배열의 경우에는 성능차이가 많이 날 수 있다.
static class Foo {
int mSplat;
}

Foo[] mArray = ...

public void zero() {
int sum = 0;
for (int i = 0; i < mArray.length; ++i) {
sum += mArray[i].mSplat;
}
}

public void one() {
int sum = 0;
Foo[] localArray = mArray;
int len = localArray.length;

for (int i = 0; i < len; ++i) {
sum += localArray[i].mSplat;
}
}

public void two() {
int sum = 0;
for (Foo a : mArray) {
sum += a.mSplat;
}
}
  • zero() 메서드는 매 루프마다 mArray.length를 계속 접근하기 때문에 가장 느리다.
  • one() 메서드는 멤버변수의 검색을 피하기 위해서 로컬변수에 대입해 이용하고 있고, length도 매번 불리지 않도록 메모리에 캐싱해두었기 때문에 zero() 메서드보다 빠르다.
  • two() 메서드는 JIT가 없는 디바이스에서는 앞의 두 메서드보다 빠르고, JIT가 있는 디바이스에서는 one() 메서드와 거의 같은 성능을 보인다.

6.Private Inner 클래스에서의 Private 멤버/메서드 접근은 가급적 피하라

public class Foo {
private class Inner {
void stuff() {
Foo.this.doStuff(Foo.this.mValue);
}
}

private int mValue;

public void run() {
Inner in = new Inner();
mValue = 27;
in.stuff();
}

private void doStuff(int value) {
System.out.println("Value is " + value);
}
}
다음 예시에서 private inner 클래스인 Inner는 자신의 outer 클래스인 Foo의 private 필드와 메서드를 이용하고 있다. 이는 자바의 문법으로는 가능하며, 컴파일도 잘 되고, 실행값도 우리가 예상하는 대로 "Value is 27"이 출력될 것이다.
그러나, VM은 Foo와 Foo$Inner를 전혀 별개의 클래스로 판단하고, 자바문법이 허용했지만 Inner에서의 private 필드/메서드의 접근이 잘못되었다고 인식한다. 따라서, VM은
이를 해결하기 위해 다음과 같은 브릿지 메서드를 내부적으로 생성한다.
/*package*/ static int Foo.access$100(Foo foo) {
return foo.mValue;
}
/*package*/ static void Foo.access$200(Foo foo, int value) {
foo.doStuff(value);
}
이 브릿지 메서드는 Inner에서 mValue를 접근하거나, doStuff() 메서드를 호출할 때마다 호출된다. 이는 당연히 필드를 직접 엑세스하는 것보다 느리다. 브릿지 메서드의 생성을 피하려면, Foo의 멤버/메서드를 private이 아닌 package 접근제어자로 변경해주면 된다. 하지만, 변경된 멤버/메서드는 같은 패키지 내의 다른 클래스에서 이용될 수 있기 때문에 OOP에 위배되므로 잘 고려해서 변경해야 한다.

7.부동소수점 사용을 피하라

안드로이드 디바이스에서는 부동소수점 연산은 정수 연산보다 약 2배정도 느리다. 속도 관점에서 보면, double과 float은 별 차이가 없다. 그러므로, 어차피 부동소수점 연산을 해야하고 메모리의 제약이 덜하다면, 2배 더 정밀한 표현이 가능한 double을 이용하는 것이 낫다.

8.시스템 Library를 잘 알고, 사용하자

먼저 코드를 구현하기 전에, 시스템에서 지원하는 라이브러리 중에 구현하고자 하는 동작을 지원하는지 확인하여 가급적 시스템이 제공하는 라이브러리를 이용하도록 한다. 이 라이브러리에서 제공하는 메서드들은 JIT 등, 컴파일러에 최적화되어 있어서, 대부분의 경우 유저가 직접 구현한 방법보다 빠르거나 같다.

9.주의해서 Native 메서드를 이용하라

대부분의 경우 NDK를 사용할 필요가 없을 정도로 JAVA에서 최적화를 진행할 수 있다. 그러나, 메모리, 파일 디스크립터 등의 네이티브 자원을 할당해야 하거나, 아키텍쳐별로 컴파일을 따로 해줘야 할 필요가 있는 등의 이유가 있다면, NDK를 이용한 네이티브 코드를 이용하는 것도 고려해 볼 수 있다.

성능에 대한 오해

JIT가 없는 디바이스에서는, 인터페이스 타입의 변수를 선언하고 그 인터페이스의 메서드를 이용하는 것이 실제 타입의 변수를 선언하고 메서드를 이용하는 것보다 조금 느린 것이 사실이다.(약 6%) 그러나 JIT가 있는 디바이스에서는 성능차이가 없다. 그러므로 6%의 얼마안되는 성능차이를 커버하고자 OOP의 안티패턴인 실제타입 변수를 선언하는 것은 재고해볼 필요가 있다.

참고