2017년 2월 14일 화요일

[안드로이드] apk 위/변조 대비하기

APK 변조 대비하기

1.런타임에 Signing 인증서 확인하기

PackageInfo 를 이용해 런타임에 Signing 인증서를 가져올 수 있다. 이를 서버로 보내어, 현재 앱의 인증서가 제대로 된 인증서인지 변조된 인증서인지를 분별하여 앱의 실행을 제한하는 방법이다.

방법

  1. keystore파일의 SHA-1키를 확인하여 서버에 저장한다.
keytool -exportcert -alias <Alias 이름> -keystore <키스토어 파일경로> | openssl sha1 -binary | openssl base64

//예시
keytool -exportcert -alias DISC -keystore JBS.keystore  | openssl sha1 -binary | openssl base64
  1. 앱에서 현재 Signing된 인증서 정보를 가져온다.
public static void checkAppSignatureInServer(Context context, Callback callback) {
    try {
        @SuppressLint("PackageManagerGetSignatures")
        PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
        
        //안드로이드는 이론상 여러 키스토어로 Signing 가능하다고 하지만,
        //일반적으로는 하나의 키스토어로만 Signing을 하므로, signatures의 첫번째 값을 이용한다.
        Signature signature = packageInfo.signatures[0];
        final String currentSignature = obtainSignatureString(signature);
        fetchServer(currentSignature, callback);
    } catch (PackageManager.NameNotFoundException | NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
}

private static void fetchServer(String signature, Callback callback) {
    //TODO::Fill in server logic
    callback.onFinishCheck(ValidationEnum.VALID);
}

2.인스톨러 확인

모든 앱은 자신이 어떤 인스톨러로부터 인스톨되었는지를 알 수 있도록 인스톨러의 identifier를 가지고 있다. 이 값을 확인하여, 등록된 인스톨러가 아닐 경우에 앱의 사용을 제한하는 방법이 있다.

방법

  1. 인스톨러의 identifier를 확인한다.
  • 구글 : com.android.vending
  • 다른 스토어의 인스톨러 id 확인 필요.
  1. 앱의 인스톨러를 확인한다.
public static boolean verifyGoogleInstaller(Context context) {
    final String installer = context.getPackageManager().getInstallerPackageName(context.getPackageName());
    return installer != null && installer.startsWith("com.android.vending");
}

2017년 1월 24일 화요일

화면전환시 갑자기 ArrayIndexOutOfBoundsException을 내며 뻗을 때

화면전환시 갑자기 ArrayIndexOutOfBoundsException을 내며 뻗을 때

배경

안드로이드 5.0 롤리팝 이하 디바이스에서 종종 화면전환을 시도할 때, ArrayIndexOutOfBoundsException 을 뿜으며 크래쉬되는 현상을 발견했다. 그런데 로그를 보면... 어디가 원인인지 도저히 알수가 없어서 난감한 상황이 생겼다.
ava.lang.ArrayIndexOutOfBoundsException: length=125; index=-1
at android.text.StaticLayout.calculateEllipsis(StaticLayout.java:756)
at android.text.StaticLayout.out(StaticLayout.java:720)
at android.text.StaticLayout.generate(StaticLayout.java:428)
at android.text.StaticLayout.<init>(StaticLayout.java:140)
at android.widget.TextView.makeSingleLayout(TextView.java:5884)
at android.widget.TextView.makeNewLayout(TextView.java:5741)
at android.widget.TextView.onMeasure(TextView.java:6098)
at android.view.View.measure(View.java:15172)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4814)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1390)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:681)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:574)
at android.view.View.measure(View.java:15172)
at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:617)
at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:399)
at android.view.View.measure(View.java:15172)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4814)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1390)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:681)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:574)
at android.view.View.measure(View.java:15172)
at android.widget.ScrollView.measureChildWithMargins(ScrollView.java:1196)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
at android.widget.ScrollView.onMeasure(ScrollView.java:318)
at android.view.View.measure(View.java:15172)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4814)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
at android.view.View.measure(View.java:15172)
at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:617)
at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:399)
at android.view.View.measure(View.java:15172)
at android.support.v4.widget.DrawerLayout.onMeasure(DrawerLayout.java:1081)
at android.view.View.measure(View.java:15172)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4814)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
at android.support.v7.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:139)
at android.view.View.measure(View.java:15172)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4814)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1390)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:681)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:574)
at android.view.View.measure(View.java:15172)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4814)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
at android.view.View.measure(View.java:15172)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4814)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1390)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:681)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:574)
at android.view.View.measure(View.java:15172)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4814)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2148)
at android.view.View.measure(View.java:15172)
at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:1848)
at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1100)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1273)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:998)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4212)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:725)
at android.view.Choreographer.doCallbacks(Choreographer.java:555)
at android.view.Choreographer.doFrame(Choreographer.java:525)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:711)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4745)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
at dalvik.system.NativeStart.main(Native Method)

원인

구글링을 해본 결과, 이것은 젤리빈(4.1~4.3)부터 있던 버그로, TextView에서 maxLines=1 / lines=1속성을 사용할 때, ellipsize속성값을 start로 할 경우에 해당 익셉션이 발생하여 크래쉬되는 증상이었다.
https://code.google.com/p/android/issues/detail?id=33868

해결방법

TextView에 singleLine=true속성을 추가해주면 크래쉬가 나지 않는다.
참고로, 이 버그는 6.0에서는 재현되지 않는 것으로 보아, 6.0에서 고쳐진 것 같다.

2017년 1월 13일 금요일

[안드로이드] Activity Leak 확인 툴 - LeakCanary

엑티비티 Leak

엑티비티의 참조가 남아있어서 Destroy 후에도 GC되지 않는 증상으로, 안드로이드에서 메모리 누수의 주 원인으로 꼽힌다. 엑티비티 인스턴스는 다른 인스턴스에 비해서 메모리를 많이 차지하고, 일반적으로 뷰 등 여러가지 멤버필드를 가지고있기 때문에 엑티비티 Leak이 발생하면 자칫 치명적인 성능저하를 유발할 수도 있다.

LeakCanary

Square 사(BetterKnife 만든 회사)에서 만든 무료 라이브러리로, 그나마 쉽게 엑티비티 Leak을 감지할 수 있도록 도와준다.

적용방법

  1. app의 build.gradle에 다음의 dependancy를 추가한다.
dependencies {
    //Debug용 컴파일
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
   //Release용 컴파일 - Release시에는 아무 동작도 안하도록
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
}
2.Application의 onCreate() 메서드에서 초기화한다.
public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {
      // This process is dedicated to LeakCanary for heap analysis.
      // You should not init your app in this process.
      return;
    }
    LeakCanary.install(this);
    // 앱 초기화 로직
  }
}

이용방법

앱을 테스트하다보면 다음 이미지와 같이, 추가한적 없는 아이콘이 보이는 경우가 있는데, 저 아이콘이 보이고 Notification이 존재한다면 현재 앱은 엑티비티 Leak이 존재하는 것이다.
Notification을 클릭하면, 왜 Leak이 발생했는지를 보여주는 화면이 나타난다.

장/단점

  • 장점
    • 적용이 매우 쉽다.
    • Leak이 발생하면 시각적으로 알 수 있어서 직관적이다.
  • 단점
    • Activity Leak를 확인하는 것만 가능하다. - 다른 메모리 Leak은 불가능
    • Activity의 Destroy 시점에서 검사를 진행하므로 화면 전환속도가 조금 느리다. - release 버전에서는 LeakCanary가 동작하지 않도록 빈 라이브러리를 컴파일하거나, 라이브러리를 제거해야 한다.

참고자료

2017년 1월 4일 수요일

[안드로이드] 무선 ADB 연결

선 없이 ADB 연결하기

안드로이드 개발을 하다보면, 여러 디바이스를 동시에 테스트 해야할 일이 많은데... USB포트의 개수는 한정되어 있어서 컴퓨터에 연결되는 디바이스의 개수가 제한되기 마련이다. ADB는 TCP 기반의 무선연결을 제공하므로, 이 기능을 통해서 USB포트보다 많은 디바이스를 연결할 수 있다.

준비

연결할 디바이스와 ADB가 있는 컴퓨터는 같은 네트워크에 속해있어야 한다. (즉, 3G/LTE일 때는 무선연결을 이용할 수 없다.)

ADB 연결

  1. 연결할 스마트폰을 컴퓨터에 USB로 연결한다.
  2. Terminal / cmd 를 열고, 다음과 같이 입력한다.
    • 이 때, 입력할 명령어는 adb가 설치되어 있는 디렉터리에서 수행되거나, 그 디렉터리가 환경변수로 등록되어 있어야 한다.
    • 아래 예제에서 5555는 포트번호이다. 포트번호는 임의로 설정해준다.
    • 성공시 restarting in TCP mode port: 5555 라는 메시지가 나타난다.
    • 이미 여러개의 장치가 컴퓨터에 연결되어 있을 경우, -s 옵션을 이용하여 device id를 추가로 입력해준다. device id는 adb devices 명령을 이용해 확인할 수 있다.
// Windows
adb tcpip 5555
adb -s <device id> tcpip 5555       // 여러개의 장치가 연결되어 위의 명령어가 안될 경우
// Mac or Linux
./adb tcpip 5555
./adb -s <device id> tcpip 5555      // 여러개의 장치가 연결되어 위의 명령어가 안될 경우
  1. USB를 연결해제한 후, 스마트폰의 ip를 확인한다.
  2. 확인한 ip를 이용해 다음과 같이 adb 명령을 수행한다.
    • 아래 예제에서 192.168.0.100은 스마트폰의 ip, 5555는 위에서 설정한 포트번호이다. 각자의 상황에 맞게 바꿔적어야 한다.
    • 성공시 connect to 192.168.0.100:5555라는 메시지가 나타난다.
// Windows
adb connect 192.168.0.100:5555
// Mac or Linux
./adb connect 192.168.0.100:5555
  1. 안드로이드 스튜디오에서 로그가 제대로 보이는지 확인한다. 혹은 다음의 명령어를 이용한다.
// Windows
adb devices
// Mac or Linux
./adb devices

2016년 12월 16일 금요일

[안드로이드] Process와 Task 차이

안드로이드의 Process

타 운영체제와 마찬가지로, 실행가능한 프로그램 데이터가 메모리로 로드되어 실행된 것을 프로세스라고 한다. 안드로이드는 리눅스 기반의 OS이므로, 리눅스의 프로세스처럼 각각 ID가 부여되며, 다른 프로세스와 철저히 격리되어 실행된다.

Process의 구등

안드로이드는 앱의 컴포넌트(엑티비티, 서비스, 리시버, 프로바이더)를 실행하는 시점에서, 그 컴포넌트가 메모리로 로드되고, 컴포넌트가 속한 앱의 프로세스가 구동된다.
Note : 앱의 프로세스가 새로 구동될 때마다, 시작점은 Application의 onCreate()메서드이다.

Process 변경

컴포넌트는 기본적으로 앱의 프로세스에 속하지만, 소속 프로세스를 변경할 수도 있다.
메니페스트 파일에서 컴포넌트를 선언할 때, android:process 필드를 지정하면, 이 컴포넌트는 앱의 프로세스와는 별개로, 다른 독립된 프로세스에서 실행된다.
Note : 별도의 프로세스에서 동작하더라도, Application의 onCreate()메서드는 호출된다.(왜그럴까......??)

안드로이드의 Task

앱을 실행하게 되면, 그 앱은 필요한 컴포넌트들을 호출하게 된다. 각 앱마다 현재 사용하고 있는 컴포넌트들을 그룹화하여 관리하는 것이 Task이다. 여기서의 컴포넌트는 해당 앱에 속한 컴포넌트 일수도 있고, 다른 앱에 속한 컴포넌트 일수도 있다.

컴포넌트 교차 생성

안드로이드의 독특한 특징으로, 다른 앱에 속한 컴포넌트를 호출하여 사용할 수 있다. 이렇게 호출된 컴포넌트는 현재 실행되고 있는 앱의 Task에 속하게 된다. 하지만, 호출된 컴포넌트가 메모리에 로드되면, 그 컴포넌트의 원래 프로세스가 구동되며, 컴포넌트가 실행되면서 필요한 권한설정은 원래 앱이 획득한 권한설정을 따르게 된다.
  • ex) 메모앱에서 기본 카메라의 엑티비티를 호출하는 경우
    • 기본 카메라의 엑티비티는 메모앱의 Task로 묶이게 된다.
    • 메모앱이 기본 카메라 엑티비티를 호출하는 시점에, 기본 카메라 앱의 프로세스가 구동된다. 즉, 메모앱의 프로세스와 기본 카메라 앱의 프로세스가 동시에 실행중인 상황이 된다.
    • 기본 카메라 엑티비티가 사용하고자 하는 권한(카메라 접근, 외부저장소 저장 등)은 메모앱의 권한설정이 아닌, 기본 카메라 앱의 권한설정을 따르게 된다. 따라서, 메모앱은 사진관련 권한이 없어도, 기본 카메라 앱에 권한이 있으면 문제없이 이용할 수 있고 반대로, 메모앱이 사진관련 권한이 있어도, 기본 카메라 앱에 권한이 없으면 기능을 이용할 수 없다.

엑티비티 스택

Task는 엑티비티의 순서를 관리하기 위해 별도로 스택을 유지하여 엑티비티를 저장한다. 즉, Task가 지워지면, 엑티비티의 순서도 지워지게 된다.

Task 변경

다른 컴포넌트들에 비해, 엑티비티는 메니페스트에서 <activity>태그의 android:launchMode 필드를 주거나, 엑티비티를 실행할 인텐트에 플래그 값을 추가하여 Task를 변경할 수 있다.(변경이라고는 해도 Task간 이동은 불가능하다.)

2016년 12월 13일 화요일

[안드로이드] Task의 종료시점 확인하기

안드로이드 Task의 종료시점 확인하기

배경

안드로이드는 OS에 의해 프로세스가 강제종료 되는 시점을 알 수 없다. Application 클래스의 onTerminate()메서드조차 가상머신에서만 동작하고 실제 디바이스에서는 동작하지 않는다고 문서에 명시되어 있다.(실제로도 동작하지 않는 것을 확인했다.)
스크린샷 2016-12-01 오전 10.52.37.png

Task?

안드로이드 프로세스와 태스크의 차이

안드로이드에는 Task라는 개념이 있다. Task는 어떤 앱이 실행되면서, 관련 컴포넌트(엑티비티, 서비스, 리시버, 프로바이더)를 묶어놓은 그룹의 개념으로, 프로세스와는 약간 다르다. 그렇지만, 일반적인 앱이라면 하나의 앱을 실행할 때, 하나의 프로세스가 생기며, 그 프로세스에서 실행되는 모든 컴포넌트들은 하나의 Task로 묶이게 된다. 사용자가 확인할 수 있는 최근에 실행된 앱 보기에 나오는 하나의 단위가 바로 Task 이다.
스크린샷 2016-12-01 오전 11.05.02.png

Task 종료시점 알기

OS가 프로세스를 강제종료 시키는 시점은 알 수 없지만, Task가 종료되는 시점은 알 수 있다. 즉, 사용자가 최근에 실행된 앱 보기 화면에서 태스크를 지웠을 때의 시점을 감지할 수 있는 것이다. 한개의 프로세스와 한개의 Task로 구성되어 있는 앱이라면, 이 방법을 통해 사용자가 앱을 강제종료 시키는 시점 정도는 핸들링 할 수 있다.(이것은 프로세스가 종료되는 것과는 엄밀히 다르다. 그러므로, 프로세스의 종료시점과 혼동하여 사용하면 부작용을 초래할 수 있다.)
Service 클래스에는 Task가 종료되었을 때 콜백을 받는 onTaskRemoved()메서드가 존재한다. 이 메서드를 이용해, Task의 종료시점을 잡을 수 있다.

적용방법

1.Manifest에 서비스를 등록한다. 이 때, android:stopWithTask 속성을 반드시 false로 설정해야 한다. true로 설정하면 onTaskRemoved()메서드가 호출되지 않는다.
<application
    ...>
    ...
    <service android:name=".TestService"
                android:stopWithTask="false" />
    ...
</application>
2.Manifest에 선언한 Service를 선언하고, onTaskRemoved()메서드를 오버라이드하고 필요한 내용을 구현한다.
Note : super.onTaskRemoved()를 호출하게 되면, Task가 종료되는 시점에서 프로세스는 재시작된다.(Task는 안보이고 프로세스만 살아있는 형태) Task가 종료되는 시점에서 서비스도 같이 종료시키려면 stopSelf() 메서드를 호출해준다.
public class TestService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    
    @Override
    public void onTaskRemoved(Intent rootIntent) {
        Logger.logWarn("onTaskRemoved - " + rootIntent);
        // 여기에 필요한 코드를 추가한다.,
        
        stopSelf();
    }
}
3.해당 Service를 앱의 시작점에서 시작한다.일반적으로 앱의 시작점은 Application의 onCreate()나 스플래쉬 엑티비티의 onCreate() 메서드일 것이다. Manifest에 등록된 리시버가 없다면, 두가지 방법은 차이가 없지만, 외부 액션을 받는 리시버가 등록되어 있다면, 그 외부액션을 받았을 때 Application의 onCreate()가 실행되기 때문에, 오동작할 우려가 있으므로, 그럴때는 가급적 엑티비티의 onCreate()에서 시작하도록 한다.
public class SplashActivity extends Activity {
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        startService(new Intent(this, TestService.class));
    }
    ...
}

2016년 12월 12일 월요일

[안드로이드] 안드로이드의 특징과 역사

안드로이드

  • 구글의 주도하에 여러 회사들이 Open Handset Aliance 컨소시엄으로 진행하고 있는 모바일 OS
  • 2008년 모바일 첫 장비가 출시된 이후, 현재 모바일, TV, wearable, Auto 등으로 범위가 확장되고 있다.

특징

  • 운영체제 커널은 리눅스 커널을 기반으로 한다. 오픈소스이기 때문에 여러 제조사에서 커널을 변경하여 배포하기도 한다.
  • 공식 개발언어는 자바를 이용하며, 추가적으로 C를 이용해 개발할 수 있는 NDK를 지원한다.
  • OpenGL, SQLite 등 검증된 주요 라이브러리를 모두 포함하고 있다.
  • 플랫폼에 내장된 빌트인 애플리케이션과 일반 사용자가 만든 프로그램이 동일한 레벨로 취급된다.
  • 다양한 제조사, 다양한 기기에 이용되다 보니 파편화 이슈가 심하다.

아키텍쳐

안드로이드_아키텍쳐.PNG
  1. 리눅스 커널을 이용하여 하드웨어 등 저수준 관리 기능을 담당한다.
  2. 라이브러리들과 런타임 머신이 커널위의 계층에 존재한다.
    • 라이브러리 : OpenGL, SQLite, WEBKit 등
    • 런타임 : Dalvic, JAVA 코어 라이브러리, ART(5.0 이후)
  3. 안드로이드에서 자바 라이브러리 형태로 제공하는 API를 프레임워크 계층에 구현했다.
  4. 빌트인 프로그램 및 사용자가 만든 프로그램이 응용프로그램 계층에 존재한다.

안드로이드 History

  • 네이밍규칙 : 알파벳 순서의 디저트 이름

2.1~2.3

  • 버전명
    • 2.1 : 이클레어 (API 7)
    • 2.2 : 프로요 (API 8)
    • 2.3 : 진저브레드 (API 9~10)
  • 주요 특징
    • WallPapersd카드에 앱 설치, 플래시 지원, NFC 지원xhdpi 해상도 지원
    • 태블릿을 지원하기 직전 OS
    • 지금은 사용하는 사람이 없다.(간혹 중소업체의 IoT 디바이스에서 사용)

3.0~3.2

  • 버전명 : 허니컴 (API 11~13)
  • 주요 특징
    • FragmentActionBar등 태블릿 전용 UI 지원, 시스템 Clipboard 지원
    • 드래그 & 드롭 기능 지원
    • Animator 지원(더 유연한 애니메이션 가능)
    • 유일하게 휴대폰과 태블릿으로 쪼개진 버전. (버림받았다.)
    • 지금은 사용하는 사람이 없다.

4.0~4.04

  • 버전명 : 아이스크림 샌드위치 (API 14~15)
  • 주요 특징
    • 휴대폰 & 태블릿 통합 API 제공 - 휴대폰에서도 ActionBar, Fragment 등 3.0에서 추가된 기능 이용가능
    • 안드로이드 OS중 하위 버전과 가장 많은 차이를 보인 OS
    • WifiDirect 지원, 온도계, 습도계 센서 추가, GridLayout 추가
    • 하드웨어 가속 지원(화면 렌더링에 그래픽카드를 이용)

4.1~4.3

  • 버전명 : 젤리빈 (API 16~18)
  • 주요 특징
    • 현재 (${today}) 기준으로 시즌 off된 휴대폰의 최소 버전이다.
    • 4.1(API 16)
      • 메모리 관리방식
      • Camera, Audio 라이브러리 개선
      • Android Beam 지원
      • Notification 스타일 변경
    • 4.2(API 17)
      • DayDream 추가
      • Second Display 지원
      • 잠금화면 위젯
      • 멀티 유저(다중 계정)
      • RTL layout,
    • 4.3(API 18)
      • Bluetooth Low Energy(BLE) 지원
      • Wi-Fi Scan only Mode 추가(WiFi로 유저의 위치정보를 받기 위해)
      • Multimedia 라이브러리 개선
      • OpenGL 3.0 지원, Mipmap 지원, optical bound layout 추가
      • 주소록 API 추가(Contacts Provider)

4.4

  • 버전명 : 킷캣 (API 19, 20)
  • 주요 특징
    • Print Framework추가(클라우드 프린팅 가능)
    • 오디오 터널링 지원
    • Storage Access Framework 추가(저장소 접근 방법 변경)
    • reusable bitmap, Animator pause 추가
    • Immersive Full Screen(전체화면 상태인데 사용자의 터치동작에 따라 상태바가 보여짐)

5.0~5.1

  • 버전명 : 롤리팝 (API 21, 22)
  • 주요 특징
    • 달빅 가상머신을 ART 런타임 으로 대체
    • 64비트 지원
    • 머티리얼 디자인
    • Heads-Up 알림(기존의 작업을 방해하지 않고 다이얼로그 처럼 사용자의 액션을 받는 노티피케이션)
    • 알림 기능 개선(잠금상태에서 알림 받을 수 있음)
    • 카메라 성능 개선
    • Chromium 엔진을 이용하도록 WebView변경
    • 다중 SIM카드 지원
    • 아파치 HTTP 클래스 지원 중단

6.0

  • 버전명 : 마시멜로 (API 23)
  • 주요 특징
    • Runtime 퍼미션(앱을 실행할 때 필요한 퍼미션을 요청)
    • 배터리 절약 모드(Doze, App Standby 모드) - Doze모드에서 알람, 비동기 네트워크 처리 등이 제한되므로 신경써야함
    • 텍스트 선택 UI변경
    • 지문인식 지원
    • 앱링크
    • 구글 드라이브에 자동으로 백업 - 하위 버전에서는 수동으로 구현해야 함
    • 블루투스 스타일러스 펜 지원
    • 4K 디스플레이 모드 추가

7.0

  • 버전명 : 누가 (API 24)
  • 주요 특징
    • 향상된 Doze모드 - Doze 동작이 2단계로 변경
    • 백그라운드 최적화
      • CONNECTIVITY_CHANGE 액션을 메니페스트에 등록하여 사용불가(앱 내에서 등록 및 사용은 가능)
      • ACTION_NEW_PICTUREACTION_NEW_VIDEO 사용불가
    • 설정에서 스크린 크기 변경 가능 - 모든 크기의 스크린 사이즈에서 제대로 UI가 구성되는지 확인할 필요 있음
    • 멀티윈도우 지원
    • JIT/AOT 동시 지원 컴파일러 사용
    • Vulkan API 사용가능
    • Quick Setting 영역의 타일 API 사용가능