안드로이드에서 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 통신을 하는 것이 좋을 것이다.

참조

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

안드로이드의 앱을 구성하는 일명 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() 메소드가 호출되지 않고도, 자원 부족으로 인해 컴포넌트가 사라지기도 한다.

참고

안드로이드 잡스케줄러 분석

지난 글에서 안드로이드 잡스케줄러를 이용해 백그라운드 작업을 처리하는 잡서비스 생성 시 주의할 점을 살펴보았다.

잡스케줄러 기능은 L(API21)부터 추가되었지만, M(API23)까지는 거의 사용되지 않았다. 안드로이드에서 백그라운드 작업에 애용되던 CONNECTIVITY_ACTION 수신 브로드캐스트를 targetSdk 24(N)부터 사용할 수 없도록 제한하고, 대안으로 잡스케줄을 제안하면서 많이 사용되고 있다.

이번 글에서는 잡스케줄러가 단말에서 어떻게 동작하는지 O OS 기반 테스트를 통해 살펴보자.

O OS를 선택한 이유는?

안드로이드 개발 문서는 잡스케줄의 동작에 대해 상당히 두루뭉술하게 설명하고 있다. 이를 요약하면,

  • 최대한 주기에 맞춰 실행시켜 준다.
  • 최대한 실행 시간을 보장한다.

이다. 내 생각에 이는 잡스케줄 서비스에 대한 설명이 맞지만, OS 간 동작에 있어 차이가 커 개발자에게는 충분치 않다. 그럼, M OS와 O OS 사이의 차이점들을 살펴보자.

잡서비스에 잡을 등록하면,

  • M OS에서는 보통 잡서비스가 곧(몇 분 이내) 실행된다. O OS에서는 보통 즉시(수 초 내) 실행된다.
  • 앱을 강제종료(am force-stop)하거나 초기화(pm clear)하면 M OS에서는 잡이 유지되지만, O OS에서는 잡이 사라진다.

위와 같은 OS간 동작의 차이로 인해, 모든 OS(L ~ O)의 공통된 특징을 추리기가 어렵다. 그러므로, 여기서는 앞으로 대세가 될 O OS 대상으로 확인하겠다.

얼마나 자주 그리고 오래 실행되나?

안정적인 백그라운드 작업을 위해서는 자주, 오랫동안 실행하는 것이 중요하다. 확인을 위해 총 4가지의 실행 조건(condition) 만들고 비교 테스트 보자.

대부분의 백그라운드 작업이 긴 시간을 필요하므로, 1회 실행되는 잡을 1개, (각각 다른 방법으로 구현된) 반복 실행 잡을 3개 넣었다.

  • C1 = 1회 실행
    setOverrideDeadline()을 0ms로 설정
  • C2 = 반복 실행
    setPeriodic()을 15분으로 설정
  • C3 = 반복 실행
    최초 setOverrideDeadline()을 0ms로 설정, 잡 서비스의 onStopJob()에서 다음 작업을 반복적으로 등록
  • C4 = 반복 실행
    최초 setOverrideDeadline()을 0ms로 설정, onStopJob()에서 true를 반환하여 back-off 전략에  따라(기본값 30s) 계속 재 실행

각각의 조건을 O OS 단말에서 실행하면 아래와 같은 그래프가 나온다. (실제 테스트 결과를 기반으로 그린 그래프지만, 수 많은 실행 횟수를 모두 그래프로 옮기기 어려워 임의대로 횟수를 솎아낸 후 표현했다. 실행이 집중되는 시기와 주기 변동같은 특징은 최대한 그대로 옮겼으니 참고 바란다.)

  • C1 : 1회성 작업이므로 1회 종료 후 더이상 실행되지 않는다.
  • C2 : setPeriodic()에 설정한 값에 따라 주기적으로 실행된다.
  • C3 : onStopJob()에서 setOverrideDeadline(0) 설정으로 후속 작업을 등록한다. 그 결과, 작업이 계속 실행되는 것처럼 보이는 경우도 있다. 하지만, 수면 시간이나 다른 작업으로 바쁠 때는 C2와 마찬가지로 주기가 길어진다.
  • C4 : back-off 정책에 따라 계속 잡서비스가 계속 재 실행된다. 물론, back-off 정책에 따라 주기가 점점 길어진다.

위 테스트를 기반으로 잡서비스가 실행되는 방식을 정리하면,

  • 보통 주기에 맞춰 실행한다.
  • 보통 10분간 실행한다.
    시스템이 바쁘거나 열악하지 않다면 10분 정도 실행해준다.
  • 최대 10분을 넘지 않는다.
    시스템 상황이 좋더라도, 정확히 10분만 실행한다.
  • 잡 서비스는 언제든 종료될 수 있다.
    시스템 상황이 여의치 않으면  더 빨리(4-5분?) 종료될 수 있고, 시스템 상황이 열악하다면 컴포넌트가 생성되자마자 onStartJob ()이 호출 되기도 전에 종료되기도 한다.
  • 내가 지정한 실행 주기보다 더 띄엄띄엄 실행될 수 있다.
    작업의 재실행 주기를 최소 단위인 15분으로 한 경우에도, 핸드폰이 doze 모드에 들어간다면 더 긴 주기(몇 시간 주기)로 실행될 수 있다.

실행이 보장되나?

백그라운드 작업은 오랜 실행시간 만큼이나, 안정적인 실행도 중요하다. 안정적으로 실행되기 위해서는 내가 등록한 잡이 사라지지 않아야 할 것이다. 이를 확인하기 위해, 위에서 열거한 조건(C1~C4)에 추가적인 테스트(Test)를 해보자.

테스트는 총 7개로, 정상 종료 1개와 나름 위험한(?) 테스트 6개가 포함된다.

  • T1 = 정상종료
    안드로이드 시스템이 잡 서비스에 허락한 시간을 모두 동작한 후 종료되었다.
  • T2 = 업데이트 버전 재설치
  • T3 = 업데이트 버전 삭제
  • T4 = 강제 종료
    e.g. LMK에 의한 프로세스 종료, ‘am force-stop <패키지 이름>’, 안드로이드 스투디오 내 로그캣 패널의 ‘terminate application’ 버튼
  • T5 = 앱 초기화
    e.g. ‘pm clear <패키지 이름>’, 단말의 ‘설정-어플리케이션-내 앱-저장공간-데이터삭제’ 메뉴
  • T6 = JobInfo.setPersisted(true) 설정, 실행 중 재부팅
  • T7 = JobInfo.setPersisted(false) 설정, 실행 중 재부팅

위와 같은 테스트 항목들을 조건들(C1 ~ C4)에 실행한 결과는 아래와 같다.

잡이 유지되는 경우는 ‘O’, 제거되는 경우는 ‘GONE’로 표시했다. 잡이 사라지는(GONE) 경우가 많아 보이지만,

  • T3 : 대부분의 앱은 프리로드 앱이 아니다.
  • T2, T4~T7 : 자주 발생하는 상황이 아니다.

그러므로, 앱이 정상구동한다면 잡은 대부분 유지될 것이다. 위 테스트를 기반으로 잡이 유지되는 조건을 정리하면,

  • setPeriodic()을 설정한 잡이 정상종료 한 경우
  • onStopJob()에서 true를 반환 후 정상종료 한 경우
  • 정상 실행 중, 단말이 재부팅되는 경우

이다.  다만, 잡이 예기치 않게 제거될 수 있으니 앱이 실행될 때마다 잡이 잘 등록되어 있는지 확인이 해야할 필요가 있다.

그 밖에

  • 잡이 실행 중일 때, 같은 아이디를 가진 잡을 재등록하면 기존 잡은 종료되고 새로 등록한 잡이 바로 실행된다.
  • onStartJob()에서 false를 반환하는 경우, onStopJob()을 호출하지 않고 서비스 컴포넌트가 종료된다. onStopJob()이 호출되지 않으므로 back-off 정책에 따른 재실행 요청을 할 수 없다.
  • 단말의 여건이 좋을 때는 C2보다 C3가 더 자주 실행된다. 하지만, 잡 서비스가 살아있는 시간이 늘어나는 것이 아니라, 종료 후 바로 재실행되는 것이므로 실행 시간이 10분보다 길어지는 것은 아니다. 또한, 단말 여건이 좋지 않을 때 주기가 길어지는 것은 어느 경우든 마찬가지다.
  • 잡서비스 로그 분석은 이 글을 참고하자.

참고

안드로이드 앱과 유투브 연결하기

안드로이드 앱에 동영상을 넣고 싶을 때, 보통 유투브를 가장 먼저 떠올린다.  유투브를 내 어플리케이션에 연결하는 대표적인 방법은 아래와 같다.

구현 방법에 대한 좋은 글들이 인터넷에 많이 있으므로, 여기서는 3가지 방법의 차이점에 대해 살펴본다.

바로가기

영상을 내 앱에서 재생하지 않고, 유투브 공식 앱이나 사이트로 뛰는 방법이다.  대부분의 안드로이드 단말에는 유투브 앱이 기본 앱으로 설치되어 있어 있으므로, 쉽게 쓸 수 있다. 보통  Intent를 이용해 유투브 앱의 영상을 호출하거나 유투브 라이브러리를 이용한다.

먼저, Intent를 이용해 호출 하는 방법을 살펴보자. 코드는 아주 간단하다. 한국이나 미국, 유럽 등에서 판매되는 안드로이드 단말에서 잘 실행된다. 하지만, 구글 인증을 받지 않은 단말은 유투브 앱이 없을 수 있고, 중국처럼 유투브 서비스 자체가 막힌 곳도 있으므로 이 때를 위한 예외처리를 직접해야 한다.

다음으로, 유투브 라이브러리를 이용하는 방법이 있다. 유투브 라이브러리 내의 YouTubeIntents라는 클래스에 관련 API가 모두 모여있다.  API를 이용하면, 유투브 앱이 설치되어 있는지, 영상이 재생 가능한 상태인지 등을 확인 후 유투브 앱 내의 영상으로 이동한다. 그러므로, 예외처리 없이 안전하게 사용할 수 있다.

유투브 앱이 없는 경우, 웹 브라우저를 통해 유투브 사이트의 영상 페이지로 이동할 수도 있다. 아래는 간략한 코드다.

Intent intent = new Intent(
                    Intent.ACTION_VIEW,
                    Uri.parse( "http://youtu.be/" + <비디오 아이디> ));

startActivity( intent );
유투브 IFrame Player

유투브는 IFrame을 이용해 웹 페이지에 동영상을 연결하는 기능을 공식적으로 지원한다.

이 IFrame Player를 조절하기 위한 다양한 파라미터들을 정의하고 있으므로, 이를 통해 유투브 영상이 보여지는 방식을 조절할 수 있다.

간혹 “YouTube에서 동영상을 시청하세요. 동영상 소유자가 다른 웹사이트에서 재생할 수 없도록 설정하였습니다.” 메시지가 나오는 영상이 있다. 동영상 퍼가기를 허용하지 않은 경우 발생하는 것으로, ‘동영상 퍼가기’에 대한 설명은 여기를 보자. 당장 문제를 해결하고 싶다면 아래를 참고하자.

  • 유투브 사이트 > 로그인 > 우측 상단 메뉴 > 크리에이터 스투디오 > 위 메시지가 나오는 동영상의 수정 버튼 클릭 > 고급 설정 탭 선택 > ‘퍼가기 허용’ 체크박스 채우기

개인적으로 IFrame 플레이어 파라미터가 일부(rel, showinfo 파라미터) 일시적으로 동작하지 않는 경험이 있었다. 몇일 내에 다시 고쳤는지 재현되지 않았다.

유투브 Android Player

안드로이드 용 유투브 플레이어를 라이브러리로 제공한다. 권장하는 비디오 재생 모양과 샘플들도 함께 제공하므로, 쉽게 적용할 수 있다.

문서화가 아주 잘되어 있으니, 여기를 참고하자.

주의할 점은 앱에 안드로이드 Player를 사용하기 위해서는 API 키가 필요하고, 이 키는 구글 개발자 등록(최초 1회 유료)을 해야 발급 받을 수 있다. 이에 대한 절차는 여기에 소개되어 있으니 참고하자.

그 밖에

  • 3가지 방법 모두 쉽게 적용할 수 있으므로 현재 상황에 가장 적합한 것을 적용하면 된다. 링크 정도면 충분하다면 바로가기를, 웹 페이지의 일부로 보이고 싶다면 IFrame Player를, 네이티브 앱으로 보여주고 싶다면 Android Player를 써보자.
  • 유투브를 비디오 스트리밍 프로바이더처럼 쓰기는 매우 어렵다. 예를 들어, 유투브 로고를 지운다거나,  영상 없이 소리만 재생되게 하거나, 앱이 포그라운드로 실행되지 않을 때에도 재생되게 하는 식이다. 유투브는 제한 된 형태로만 영상을 제공하고 있다.
  • 유튜브 동영상의 퀄리티에 대해서는 이 글을 참고하자. 경험상 최근 영상들은 대부분 최고 퀄리티로 업로드 되는 듯 하다.

참조