1. 화면이 회전하면 액티비티의 생명주기는?
  2. 펜딩인텐트란?
  3. 안드로이드에서 Task와 Process의 차이점
  4. A 앱에서 B 앱의 액티비티를 부를 경우
  5. 화면 렌더링 속도를 개선하는 방법
  6. Thred간 통신 방법
  7. ANR

화면이 회전하면 액티비티의 생명주기는?

안드로이드 애플리케이션을 사용하다 보면 화면이 가로 모드를 지원하는 애플리케이션들이 많다. 이런 경우 우리가 사용하는 액티비티의 생명주기는 어떻게 되는 것일까???

일단, 화면이 회전하면 뷰의 크기를 재측정하고 다시 달라진 크기대로 새롭게 그려야 된다. 먼저, onPause가 호출되고 onStop, onDestroy가 진행된 후 다시 onCreate, onStart, onResume 순서로 진행된다. 따라서 현재 액티비티를 destroy 시키고 새로 만들기 때문에 기존의 데이터는 사라지게 된다.

그런데 우리가 사용하는 애플리케이션에서는 지워지지 않는데?

onDestroy가 호출되기 전에 액티비티는 onSaveInstanceState를 호출하게 되는데 이때 저장되어야 할 데이터를 Bundle 객체에 저장해두었다가 다시 onCreate가 호출되면 파라미터로 날아온 Bundle에서 이전 데이터를 받아 복구할 수 있다. 이 과정을 통해 화면이 회전되어도 데이터를 유지할 수 있는 것이다.

펜딩인텐트란?

펜딩 인텐트(Pending Intent)는 인텐트의 일종이다.

컴포넌트에서 다른 컴포넌트에게 작업을 요청하는 인텐트를 사전에 생성시키고 만든다는 점과 특정 시점에 자신이 아닌 다른 컴포넌트들이 펜딩 인텐트를 사용하여 다른 컴포넌트에게 작업을 요청시키는데 사용된다는 점이 일반적인 인텐트와의 차이점이다.

수행시킬 작업 및 인텐트와 그것을 수행하는 주체를 지정하기 위한 정보를 명시할 수 있는 기능의 클래스라고 보면 된다.

쉬운 예제는 아래와 같다.

A한테 이 B인텐트를 C 시점에 실행하라고 해. 지금은 실행하지 말고.

이 클래스의 인스턴스는 getActivity (Context, int, Intent, int), getActivities (Context, int, Intent [], int), getBroadcast (Context, int, Intent, int) 및 getService(Context, int, Intent, int) 가 반환 하는 객체를 다른 응용 프로그램으로 전달 할 수 있으므로 앱 개발자가 명시하는 작업을 수행 할 수 있다.

Pending Intent를 다른 응용 프로그램에 제공하면 다른 응용 프로그램이 자신과 동일한 권한과 ID로 지정된 것처럼 작업을 수행할 수 있는 권한이 부여된다. 따라서 Pending Intent를 작성하는 방법에 주의해야 한다. 예를 들어, 제공하는 기본적인 인텐트는 컴포넌트 이름이 자신이 가진 컴포넌트들 중 하나를 명시적으로 지정해야 하며, 궁극적으로 그곳으로 보내지는 것을 보장해야 한다.

사용되는 몇가지 사례

  • 사용자가 Notification을 통해 특정한 동작을 할 때, 실행되는 인텐트를 생성함(NotificationManager가 인텐트를 실행)
  • 사용자가 AppWidget을 통해 특정한 동작을 할 때, 실행되는 인텐트를 생성함(홈 스크린이 인텐트를 실행)
  • 미래의 특정 시점에 실행되는 인텐트를 선언함(안드로이드의 AlarmManager가 인텐트를 실행)

안드로이드 앱을 구현할 때, 인터넷으로부터 파일을 다운로드 하는 로직은 대부분 서비스에서 이루어지도록 구성한다. 그런데 서비스는 액티비티와 달리 화면에 나타나지 않는다. 따라서 서비스는 다운로드의 진행중이라는 사실 및 진행 정도를 화면 상단에 위치한 노티피케이션의 상태바(Status bar)를 통해서 표현한다. 다운로드가 현재 진행 중이라는 상황을 표시하는 아이콘 등으로 말이다. 그리고 다운로드가 완료된 후에는 아이콘으로 다운로드 완료의 상태를 보여주게 된다.

사용자가 상태바의 아이콘을 확인하고 안드로이드 화면의 상태바를 누르면서 나타나는 바를 잡아 아래로 끌어당기면 나타나는 화면을 노티피케이션 리스트 또는 확장 메시지라 한다. 그리고 만약 서비스가 이 Notification List에 다운로드 완료를 표시를 추가해놓았고, 사용자가 이것을 클릭하면 노티피케이션은 사전에 서비스에서 작성한 펜딩 인텐트를 사용하여 다운로드된 파일을 읽을 수 있는 앱을 호출하고 다운로드 완료된 파일을 호출된 앱에서 재생 혹은 보여주게 된다.

펜딩 인텐트는 안드로이드 App의 각각의 컴포넌트들이 펜딩 인텐트를 생성할 수 있도록 다음과 같은 메소드를 제공한다. 아래의 메소드를 통해 펜딩 인텐트를 사용하고자 하는 컴포넌트 유형을 지정해야 한다는 뜻이다.

  • getActivity(Context, int, Intent, int)
  • getBroadcast(Context, int, Intent, int)
  • getService(Context, int, Intent, int)

여기서 Context는 현재 App의 Context이다.

# Example
노래를 재생하는 중이고 이것이 상태바에 Notification으로 보여질 때 사용자가 이 Noti를 클릭하면 Notification은 이전에 전달받은 Pending Intent를 실행한다. Pending Intent가 getActivity()에 의해 생성된 것이며 감싸고 있는 intent 또한 특정 화면을 실행시키는 intent라면 이것이 그대로 실행되어 노래 화면이 뜨게 된다.

이때 주의할 점은 Activity는 새로운 테스크를 생성하여 그곳에 쌓이게 된다. 마치 Intent의 flag가 NEW_TASK로 설정된 것처럼 실행되기 때문에 유의해야 한다. 하나의 Activity만 생성되도록 보장하기 위해선 AndroidManifest 파일에 lanunchMode = “singleTop” 으로 설정해주어야 한다.

안드로이드에서 Task와 Process의 차이점

애플리케이션 컴포넌트가 처음 시작될 때 실행 중인 다른 컴포넌트가 없으면 안드로이드 시스템은 프로세스를 새로 생성시킨다. 기본적으로 애플리케이션의 컴포넌트들은 같은 프로세스의 기본 스레드에서 실행된다. 이후의 컴포넌트들이 시작할 때는 이미 생성된 프로세스 내에서 시작되며 컴포넌트별로 별도의 프로세스에서 실행되도록 할 수도 있고 어느 프로세스에서든 추가적인 스레드를 생성하여 작업할 수 있다.

Manifest 파일에서 android:process 특성을 설정함으로써 다른 프로세스에 해당 컴포넌트를 실행시킬 수 있다. 안드로이드 시스템에서 메모리가 부족할 경우 우선순위가 낮은 프로세스부터 종료시킨다. 우선순위는 Foregorund, Visible, Service, Background, Cached 프로세스 순이다.

눈에 보이는 프로세스는 Foreground와 Service 프로세스이며 Visible은 다이얼로그가 뜨는 경우 뒤에 액티비티가 가려지지만 사용자에게 보이는 경우에 해당한다.

Service는 사용자가 볼 수는 없지만 프로세스에 필요한 작업은 진행하기에 그 다음 우선순위를 가지며 Background는 뒤로 밀려난 프로세스이기에 종료될 수 있다. 마지막 Cached 프로세스는 컴포넌트가 없는 빈 프로세스이지만 다음에 실행할 때 로드 시간을 절약하기 위해 캐시된 상태이다.

Task는 각 애플리케이션마다 사용하는 Activity들을 Stack 구조로 저장 및 관리하는 컬렉션이다. 따라서 사용자가 화면의 전환 흐름을 자연스럽게 경험하도록 보장한다. 또한 Task는 다른 애플리케이션이나 프로세스에 속하는 Activity를 같은 Task에 저장시킴으로써 사용자로 하여금 하나의 애플리케이션에서 작동하는 듯한 경험을 하도록 한다. 단말기에서 사용중인 애플리케이션 리스트를 보는 것이 Task 단위로 보여지는 것이다. 하지만 실질적으로 Activity는 다른 프로세스 상에서 돌아가는 컴포넌트이며 프로세스간 통신을 통해 정보를 주고 받는 것이다.

A 앱에서 B 앱의 액티비티를 부를 경우

Task에는 해당 애플리케이션에 속한 컴포넌트뿐만 아니라 다른 애플리케이션 컴포넌트도 쌓일 수 있다. 다른 애플리케이션의 컴포넌트를 실행하는 방법으로 PackageManager의 getLaunchIntentForPackage()가 있고 Intent에 setComponent()를 사용하는 방법이 있는데 전자의 경우 항상 새로운 Task를 생성하여 그곳에서 컴포넌트를 실행시키며 후자의 경우 현재의 Task에서 컴포넌트를 실행시키게 된다.

이 부분은 추후 공부를 더 해볼 예정.

화면 렌더링 속도를 개선하는 방법

기본적으로 View의 움직임이 어색하거나 스크롤이 버벅거리거나 렌더링이 느린 경우는 뷰를 그리는 속도가 16ms 보다 오래걸리는 현상이다. 초당 60 프레임의 속도로 화면을 그려주어야 사람의 시각에 어색함이 없이 보이는데 그리는 시간이 이보다 오래 걸릴 경우 버벅이는 문제가 발생할 수 있다. 따라서 렌더링이 느리다면 2가지를 의심해 봐야 한다.

  1. View Hierarchy가 너무 많은지 의심해 볼 것.

View는 그려지기 전에 Measure, Layout, Draw의 3단계를 계층적으로 실행한다. 만약 계층이 매우 길고 복잡하다면 당연히 View가 그려지는 시간 또한 오래 걸릴 것이기 때문이다. 계층을 줄이려 ConstraintLayoutFlexboxLayout를 적극 사용한다면 줄일 수 있을 것이다.

  1. onDraw()에서 오버드로우 현상이 일어나는지 확인할 것.

onDraw() 함수 안에서 객체 생성을 하였는지, 오래 걸리는 작업을 실행하지 않는지 확인하여 문제가 되는 로직을 수정하거나 제거할 것이다.

# ConstrainLayout?
ConstrainLayout은 뷰의 상하좌우를 주변 또는 부모 뷰와 연관을 지어 위치시킬 수 있다. bias를 통해 비율적으로 배치시킬 수도 있고, chain을 사용하여 마치 그룹화한 것처럼 사용도 가능하다. 또한 ratio를 사용하여 너비와 높이를 비율대로 설정할 수도 있는 등 매우 유연한 배치가 가능하다. ConstrainLayout의 가장 큰 장점은 View Hierarchy수평적으로 평평하게 만든다. 최대 8 계층으로 구성되어 있는 RelativeLayout 구조를 하나의 계층으로 줄일 수 있는 효과를 볼 수 있다.

# FlexBoxLayout?
태그와 같은 항목이 하나의 뷰 안에 일렬로 즐비할 경우 한 줄이 가득 차면 다음 줄로 View를 정렬시키는 기능이 필요할 때 사용할 수 있는 Layout이다. FlexBoxLayout은 부모 레이아웃의 너비에 따라 자식 뷰를 여러 행에 걸쳐 동적으로 맞추는 기능을 제공한다. 기존에는 LinearLayout을 통해 구현 가능하긴 하지만 ScrollView와 같이 사용하면서 View의 너비를 일일이 계산하여 다음 행으로 배치시키는 코드를 직접 구현하는 방식으로 커스텀을 해야했다. FlexBoxLayout을 사용하면 이러한 작업을 자동으로 처리해주기에 잘못된 구현으로 인해 오버헤드가 발생하는 문제를 방지할 수 있다.

Thred간 통신 방법

Android의 UI를 담당하는 쓰레드는 메인 쓰레드(UI 쓰레드)인데, 오랜 시간이 걸리는 작업을 메인 쓰레드에서 수행할 경우 앱의 성능이 저하된다. 따라서 여분의 쓰레드를 사용하여 작업을 수행해야 하고, 이 결과를 반영하기 위해 메인 쓰레드와의 통신이 필요하게 된다. 그래서 메인 쓰레드에 접근하는 방법은 LooperHandler를 이용하면 된다. 그리고 UI 작업을 메인 쓰레드에서만 담당하는 이유는 두 개 이상의 쓰레드를 사용할 때의 동기화 이슈를 차단하기 위함이다.

Android는 Java의 쓰레드를 좀 더 쉽게 사용할 수 있도록 래핑한 HandlerThread, AsyncTask를 제공한다.

1. Looper
메인 쓰레드는 내부적으로 Looper를 가지며 그 안에는 Message Queue가 포함된다. Message Queue는 쓰레드가 다른 쓰레드나 혹은 자기 자신으로부터 전달받은 Message를 보관하는 Queue(FIFO)이다. Looper는 무한 루프를 돌며 자신이 속한 쓰레드의 Message Queue에서 Message 객체를 차례로 꺼내서 Handler가 처리하도록 전달한다.

메인 쓰레드는 기본적으로 Looper가 생성되어 있으나, 새로 생성한 쓰레드는 새로운 Looper를 생성해주어야 한다. Android는 Looper가 기본적으로 생성되어 있는 HandlerThred를 제공한다.

  • Message Queue에 저장되는 객체
    • 문자와 필드로 구성된 Message 객체
    • Runnable 객체

2. Handler
Looper로부터 받은 Message를 실행, 처리하거나 다른 쓰레드로부터 메시지를 받아서 Message Queue에 넣는 역할을 하는 쓰레드 간의 통신 장치이다. 일반적으로 UI 갱신을 위해 사용된다. (뷰나 뷰 그룹에서 제공하는 메소드는 단일 스레드 모델(Thread-Unsafe)

ANR

ANR은 Android Not Responding의 약자로 오랜 시간이 걸리는 작업을 메인 쓰레드에서 담당하면 앱의 반응성이 낮아질 수 있고, 사용자의 불편함을 방지하고자 시스템이 ANR 상태로 전환시킬 수 있다. 따라서 시간이 걸리는 작업은 여분의 쓰레드를 사용해야 하고, 이 작업 결과를 반영하기 위해 메인 쓰레드와 통신하는 방법이 필요하다.

# ANR상태 예시
-> input 이벤트에 5초 안에 반응하지 않을 경우(Activity)
-> BroadcastRecevier가 10초 이내로 실행하지 않을 경우(UI가 없는 브로드캐스트 리시버와 서비스도 실행 주체가 메인 쓰레드이기 때문에)

# ANR대처 방법
-> 시간이 걸리는 긴 작업은 쓰레드로 처리
-> Progress bar로 진행 상황을 보여주어 사용자를 기다리게 한다.

참고