안드로이드에서 adb shell로 apk 추출하기

adb shell을 이용한 apk 추출 가이드는 그림과 함께 친절히 작성된 글이 인터넷에 이미 많이 있습니다. 개인적 기록 차원에서 적어둡니다.

설치된 패키지를 찾습니다.

>adb shell pm list packages | find "<패키지 이름이나 키워드>"

apk의 정확한 패키지 이름 확인 후, apk 설치 위치를 확인합니다.

>adb shell dumpsys package <패키지 이름> | find "path"

apk 설치 위치를 찾은 후 apk를 명령창이 실행되고 있는 현재 경로로 꺼냅니다.

>adb pull <apk 저장 경로>

 

그 밖에

  • P OS 단말에서 테스트 되었습니다.
  • 다운로드 받아 설치한 apk는 /data/app/* 아래 경로에 위치하지만, 권한이 없어 목록을 직접 확인할 수 없습니다.
  • 프리로드 앱 등은 다른 경로에 있을  수 있습니다.

 

Cleartext HTTP traffic to not permitted

P OS부터 targetSdk를 28로 올리면 네트워크 통신 시 아래와 같은 에러를 만날 수 있다.

08-21 18:15:53.165 16809-16917/me.sunphiz.android.test W/System.err: java.io.IOException: Cleartext HTTP traffic to <your-domain> not permitted
        at com.android.okhttp.HttpHandler$CleartextURLFilter.checkURLPermitted(HttpHandler.java:115)
        at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:458)
        at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:127)
        ...

IOException가 발생한 원인은 P OS부터 앱이 서버와 통신 시 TLS 기반하도록 기본값이 변경되었기 때문이다. 이해를 돕기 위해 여기서는 가장 많이 쓰이는 HTTP에 대해서만 살펴보자면 https만을 쓰라는 뜻이다(물론, TLS 기반 통신을 하라는 말이 https를 쓰라는 뜻은 아니다).  안드로이드의 네트워크 통신 기본 설정이 바뀐 이유에 대해서는 이 글을, https와 SSL/TLS의 관계에 대해서는 이 글을 참고하자.

과거 https 통신 시 최초 핸드쉐이크(handshake) 절차가 비싸다(자원 소비가 크다)기 때문에 필요에 따라 http와 https를 적절히 사용하는 것을 옳다고 이야기 하기도 했다. 하지만, 최근 네트워크와 장비의 성능 향상과 더불어 개인 데이터 보호 측면에서 모든 통신을 https로 하는 것이 좋다는 의견이 많으며, 개인적으로도 옳다고 본다.

수정 방법은 크게 두 가지다. 하나는 시대에 흐름에 맞춰 모든 통신을 https 기반으로 고치는 것이다.  다른 하나는, http로 통신할 서버를 xml 파일에 열거하면 된다. 모든 통신을 갑자기 https로 바꾸는 것은 어려우니 여기서는 xml 파일을 통해 문제를 해결해보자.

<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">insecure.example.com</domain>
        <domain includeSubdomains="true">insecure.cdn.example.com</domain>
    </domain-config>
</network-security-config>

안드로이드 가이드에서는 위처럼, http를 이용해 통신할 서버의 도메인을 모두 열거하고 나머지는 https를 사용하도록 권장한다. 만약 통신하는 서버의 dns를 모두 열거할 수 없거나, 모든 통신을 http로 하는 경우라면 아래처럼 기본값을 바꿀 수도 있다.

<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

위와 같이 하면 모든 clearText 통신이 허가되므로, 기본값이 바뀐 셈이다. 하지만 장기적으로는 모두 https 통신을 하는 것이 좋을 것이다.

참조

안드로이드 package dump 분석

이 글은 단말 dumpstate 파일 중에서 패키지 정보에 대한 부분만 소개하는 글이다. dumpstate의 설명을 찾는면, 이 글을 살펴보자.

패키지 dump는 dumpstate에도 포함되어 있고, 아래와 같은 명령어를 통해 단말에서 패키지 dump만 뽑을 수도 있다.

 adb shell dumpsys package <패키지 이름>

패키지 dump는 아래와 같은 모양을 갖고, 앱의 정보를 총망라하여 보여준다.

Package [com.android.calendar] (4a50e0):
    userId=10118
    pkg=Package{330fe6f com.android.calendar}
...

예를 들면,

  • 단말에서 처리가능한 MINE 타입, 스키마(Scheme) , 액션 정보
  • 등록한 권한(permission)
  • 버전 정보(versionName, versionCode)
  • 설치 위치(codePath, resourcePath)
  • 시그니처 정보(apkSigningVersion, signatures)

등 이다. 이 중에서 정보의 축약이 심해 이해가 잘 안되는 것들만 살펴보자.

플래그(flags)

패키지의 속성(property)을 표시하는 플래그다. 아래와 같이 속성을 가지고 있다면 표시되고, 속성이 없다면 표시되지 않는다.

Package [com.android.phone] (5328729):
	...
	flags=[ SYSTEM HAS_CODE PERSISTENT ALLOW_CLEAR_USER_DATA ]
	...
Package [com.google.android.apps.magazines] (6a6fc41):
	...
	flags=[ HAS_CODE ALLOW_CLEAR_USER_DATA ALLOW_BACKUP KILL_AFTER_RESTORE EXTERNAL_STORAGE ]
	...

위 예제의 전화 앱(com.android.phone)은 SYSTEM HAS_CODE PERSISTENT ALLOW_CLEAR_USER_DATA 속성을 갖고 있다.

패키지가 속성들의 가지고 있는지 판단하는 기준은 com.android.server.pm.Settings 소스 코드에서 살펴볼 수 있다.

...
static final Object[] FLAG_DUMP_SPEC = new Object[] {
ApplicationInfo.FLAG_SYSTEM, "SYSTEM",
ApplicationInfo.FLAG_DEBUGGABLE, "DEBUGGABLE",
ApplicationInfo.FLAG_HAS_CODE, "HAS_CODE",
ApplicationInfo.FLAG_PERSISTENT, "PERSISTENT",
ApplicationInfo.FLAG_FACTORY_TEST, "FACTORY_TEST",
ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING, "ALLOW_TASK_REPARENTING",
ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA, "ALLOW_CLEAR_USER_DATA",
ApplicationInfo.FLAG_UPDATED_SYSTEM_APP, "UPDATED_SYSTEM_APP",
ApplicationInfo.FLAG_TEST_ONLY, "TEST_ONLY",
ApplicationInfo.FLAG_VM_SAFE_MODE, "VM_SAFE_MODE",
ApplicationInfo.FLAG_ALLOW_BACKUP, "ALLOW_BACKUP",
ApplicationInfo.FLAG_KILL_AFTER_RESTORE, "KILL_AFTER_RESTORE",
ApplicationInfo.FLAG_RESTORE_ANY_VERSION, "RESTORE_ANY_VERSION",
ApplicationInfo.FLAG_EXTERNAL_STORAGE, "EXTERNAL_STORAGE",
ApplicationInfo.FLAG_LARGE_HEAP, "LARGE_HEAP",
ApplicationInfo.FLAG_FORWARD_LOCK, "FORWARD_LOCK",
ApplicationInfo.FLAG_CANT_SAVE_STATE, "CANT_SAVE_STATE",
};
...

위 코드를 보면, ApplicationInfo.FLAG_SYSTEM가 true 이면 “SYSTEM” 속성을 가진 것으로 패키지 dump에 표시 되겠다. 그러므로, 저 코드를 참고하여 ApplicationInfo의 구글 API 가이드의 상수 설명을 보면 플래그의 의미를 알 수 있다. 그 중에 설명이 충분하지 않은 것들을 정리해보자.

  • “SYSTEM”
    프리로드 된 앱이 갖는 속성이다. 단말 제조사에서 바이너리 발행 시, /system/* 폴더에 앱을 탑재해야 한다. 그 예로, /system/priv-app/, /system/app/, /system/framework/ 이 있다.
    설치 위치가 /system/*이 아닌 /data/app/라도 시스템을 업데이트 한 경우라면 “SYSTEM” 속성을 가질 수 있다. 시스템을 업데이트한 경우에는 속성이 상속되기 때문이다.
    참고로, 이 속성은 패키지의 시그니처나 “PRIVILEGED”처럼 퍼미션 허가 여부에 영향을 미친다.
  • “UPDATED_SYSTEM_APP”
    시스템 앱을 업데이트한 앱이다. 설치 공간은 /data/app/*일 것이며, “SYSTEM” 속성도 함께 갖고 있다.
  • “PERSISTENT”
    항상 실행되는지 여부를 가리키는 플래그로 항상 동작해야하는 소수의 시스템 앱만 선언한다.
    AndroidManifest.xml 파일 내 <application/>의 파라미터로 명시적 선언해야한다.
    예) 전화, NFC 앱
  • “EXTERNAL_STORAGE”
    외부 저장공간에 저장된 경우에 표시되는 속성이다.
    앱의 제작자가 AndroidManifest.xml 파일 내 <manifest/>의 android:installLocation 파라미터로 명시적 선언되어야 이동이 가능하다.
    만약 이동 가능한 앱이라면, “Settings – Apps – <어플리케이션 이름> 선택 – Storage 선택 – 저장공간 이동”, SD카드가 있어야 메뉴가 노출된다.
    예) 구글 뉴스 앱
  • “LARGE_HEAP”
    앱 실행 시 시스템으로 부터 더 큰 Heap 메모리를 할당받는다.
    AndroidManifest.xml 파일 내 <application/>의 파라미터로 명시적 선언해야 한다.
    시스템에 따라, 앱에 할당하는 기본 Heap 메모리 크기가 다르므로, LARGE_HEAP 속성을 선언했을 때 추가할당하는 heap 크기도 단말마다 다를 수 있다.

프라이빗 플래그(privateFlags)

privateFlags라는 속성 중에서, 한 가지만 살펴보자.

Package [com.android.phone] (5328729):
    ...
    privateFlags=[ PRIVILEGED DEFAULT_TO_DEVICE_PROTECTED_STORAGE DIRECT_BOOT_AWARE PARTIALLY_DIRECT_BOOT_AWARE PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION ]
    ...
  • “PRIVILEGED”
    앱 중에서 /system/* 이하에 설치된 경우 “SYSTEM” 속성을 갖고 있다고 했다. 시스템 앱 중에서도 설치된 상세 위치가 /system/priv-app/*인 경우 추가적으로 privateFlags에 이 속성을 갖는다.
    참고로, 이 속성은 “SYSTEM” 플래그나 패키지 시그니처러럼 퍼미션 허가 여부에 영향을 미칠 수 있다.

패키지 시그니처(signatures)

패키지의 시그니처 정보를 확인할 수 있다.

Package [com.android.phone] (5328729):
	...
	signatures=PackageSignatures{15f0d38 [b378e95c]}
	...
Package [com.google.android.apps.magazines] (6a6fc41):
	...
    signatures=PackageSignatures{6261b7d [e3ca78d8]}
	...    

위 예시 속 패키지는 com.android.phone은 기본 전화 앱, com.google.android.apps.magazines는 구글 뉴스 앱이다.  signatures 파라미터의 대괄호 안에 signature가 있고, 이 값이 같은 앱들은 같은 keystore를 통해 signing이 된 것이다.

참고로, 시그니처도 “SYSTEM”이나 “PRIVILEGED” 속성과 갖이 퍼미션 허가 여부에 영향을 미친다.

패키지 상태(활성화, 강제 종료 등)

패키지 활성화(enable) 여부나 강제 종료 여부도 확인할 수 있다.

...
User 0:  installed=true hidden=false stopped=false notLaunched=false enabled=0
...

위 패키지 정보 로그 중, stopped 파라미터는 설정 > 어플리케이션 관리자 > 앱 > 강제 종료 되었는지 여부를 가리킨다.

또 enabled 파라미터는 패키지 활성화 여부를 보여준다. 0(Default), 1(Enabled), 2(Diabled), 3(Disabled by user), 4(Diabled until used) 등으로 여기에 사용되는 값은 안드로이드 버전에 따라 추가되기도 하니, PackageManager의 상수들을 참조하면 된다.

그 밖에

  • 프리로드 앱(단말에 처음부터 포함된 앱)이 업데이트 된 경우, 프리로드 앱 정보와 업데이트 된 앱 의 패키지 정보가 모두 보인다.
  • 플래그 중에는 ApplicationInfo.FLAG_IS_GAME 처럼, deprecated된 것도 있다.
  • 퍼미션 허가 여부에 대한 자세한 내용은 이 글을 살펴보자.

참고

상황 별 호출되는 액티비티의 생명주기 메소드들

안드로이드의 앱을 구성하는 일명 4대 컴포넌트는 모두 생명주기가 있다. 이 중, 가장 복잡한 생명주기를 가진 컴포넌트가 액티비티다. 액티비티의 생명주기에 대한 글은 수없이 많다. 하지만, 그런 글들을 읽고 액티비티의 생명주기를 이해하더라도, 문제 분석 시 ‘그래서 지금 사용자가 뭘 했길래 이렇게 호출된거야?’라는 생각이 드는 경우가 있다. 그러므로, 여기서는 액티비티 생명주기 소개 대신에 상황 별로 호출되는 액티비티의 생명주기 메소드들을 정리한다.

https://developer.android.com/reference/android/app/Activity

참고로, 여기서 상황이란 ‘앱 아이콘을 통해 액티비티를 최초 실행 시’ 등 액티비티에게 변화를 주는 액션을 사용자가 하는 때이며, O OS 단말 기준으로 테스트했다.

onCreate() > onStart() > onResume() 순으로 호출되는 경우

  • (액티비티 미실행 상태) – 앱아이콘 클릭 – 액티비티 최초 실행
  • (최근 앱 목록 표시 중, 액티비티는 미실행 상태) – 액티비티 선택 – 액티비티 실행

onCreate() > onStart() > onResume() > onPause() > onStop() 순으로 호출되는 경우

  • (화면 잠김 상태) – ADB 커맨드를 통한 액티비티 실행

onRestart() > onStart() > onResume() 순으로 호출되는 경우

  • (액티비티 foreground 상태에서 화면 잠김 상태) – 화면 잠금 해제(홈이나 전원 버튼, 필요하다면 비밀번호 입력 등) – 액티비티 실행
  • (액티비티를 실행한 적이 있으며 숨겨져 있는 상태) – 액티비티 실행(앱 아이콘 클릭 등) – 액티비티 실행

onPause()만 호출되는 경우

  • (액티비티 foreground 상태) – 다른 액티비티 실행

onPause() > onStop() 순으로 호출되는 경우

  • (액티비티 foreground 상태) – 전원 버튼 누르기 – 화면 잠금
  • (액티비티 foreground 상태) – 홈 버튼 누르기 – 런쳐 홈으로 이동

onPause() > onStop() > onDestroy() 순으로 호출되는 경우

  • (액티비티 foreground 상태) – 뒤로가기 버튼 눌러 액티비티 나가기

onDestroy()만 호출되는 경우

  • (최근 앱 목록 표시 중) – 액티비티 제거하기(밀어내기나 ‘X’ 버튼)

그 밖에

  • 액티비티를 실행한 적이 있다는 말의 의미는, 액티비티 컴포넌트가 소멸되지 않고 메모리에 상주하고 있어 재사용될 수 있다는 의미다.
  • 최근 목록 앱에 액티비티가 표시되더라도 액티비티는 미실행 상태일 수 있다. 최근 목록 앱은 파일 형태로 저장되어 단말 재부팅 후에도 히스토리 관리를 위해 유지되기 때문이다.
  • 액티비티 위에 다이얼로그를 표시하는 경우는, 생명주기 변화가 없다. 액티비티 위에 액티비티가 뜨는 경우에만, 변화가 있다.
  • 액티비티 컴포넌트의 onDestroy() 메소드가 호출되지 않고도, 자원 부족으로 인해 컴포넌트가 사라지기도 한다.

참고

FragmentManager와 ChildFragmentManager

허니컴(Honeycomb)부터 액티비티나 프레그먼트 안에 프레그먼트를 추가할 수 있다. 이 프레그먼트 객체를 관리하는 것이 FragmentManager다. 프레그먼트 전반에 대한 소개는 공식문서를 참고하고, 여기서는 종종 헷갈려하는 FragmentManager와 일명 ChildFragmentManager의 차이점에 대해서만 설명한다.

먼저 ChildFragmentManager라는 용어부터 짚고 넘어가자. ChildFragmentManager라는 클래스는 따로 없다.  대신, Activity.getFragmentManager()나 Fragment.getFragmentManager()에서 반환하면 FragmentManager(이하 FM), Fragment.getChildFragmentManager() 메소드에서 반환되는 FragmentManager 객체를 ChildFragmentManager(이하 CFM)라고 보통 부른다.

FM과 CFM 모두 FragmentManager 클래스의 객체이니, 동작은 유사하다. 하지만, 실제 사용 시에는 헷갈린다.

  • FM은 액티비티, 액티비티 속 프레그먼트(이하 자식 프레그먼트) 그리고 프레그먼트 속 프레그먼트(이하 손자 프레그먼트)가 모두 사용할 수 있다.
  • CFM은 프레그먼트(자식)와 프레그먼트 속 프레그먼트(손자)들만 사용할 있다.
  • FM에서 관리되는 손자 프레그먼트라면, getFragmentManager()는 액티비티의 getFragmentManager()와 동일한 객체를 반환한다.
  • CFM에서 관리되는 손자 프레그먼트라면, getFragmentManager()는 부모(액티비티의 자식) 프레그먼트의 getChildFragmentManager()와 동일한 객체를 반환한다.

위와 같이 FM과 FCM은 다른 객체이므로 FragmentManager.findFragmentBy…() 메소드의 결과도 다르다. FM이 관리하는 프레그먼트는 FM에서, FCM에서 관리하는 프레그먼트는 FCM에서 찾아야할 것이다.

번거롭게 왜 두개를 유지하는 것일까? API 11에는 getFragmentManager()만 있었다. 그 후, API 17부터 getChildFragmentManager()가 추가된다. 두 개로 유지하는 것의 장점은 remove() 메소드를 통해 프레그먼트를 제거할 때 더 잘 알 수 있다. (여기서 제거란 화면에서 보이는지 여부가 아닌, …onDestroy(), onDetach()로 이어지는 프레그먼트 객체 소멸을 말한다.)

액티비티-자식프레그먼트-손자 프레그먼트의 계층구조를 가진 액티비티가 있다. 여기서 자식 프레그먼트를 제거한다고 해보자. 손자 프레그먼트가 FM에서 관리된다면 자식 프레그먼트만 제거된다.  반면, 자식 프레그먼트의 FCM을 통해 손자 프레그먼트가 관리된다면, 손자프레그먼트가 먼저 제거된 후 자식 프레그먼트가 제거된다. 이는 프레그먼트의 계층구조로 인해 오는 관리의 어려움을 쉽게 만들 수 있다.

마지막으로, 위와 같은 구조가 startActivityForResult()의 결과에도 영향을 미칠까? 그렇지 않다. 어떤 액티비티나 프레그먼트에서 호출하든, startActivityForResult()의 결과를 onActivityResult()를 통해 받을 수 있다.  FM이나 FCM 어디에서 관리하든 상관 없이 startActivityResult()를 호출했던 프레그먼트로 결과가 반환된다. Fragment.onActivityResult()의 동작도 헷갈리는 부분이 있다면 이 글을 참고하자.

그 밖에

  • support library의 FragmentActivity를 상속받아 만든 액티비티의 경우는 getSupportFragmentManager()를 통해 FragmentManager 객체를 얻는다.
  • Activity 클래스의 getFragmentManager()는 P OS부터 deprecated 된다.

참고