-
Chapter 19 안드로이드 앱과 액티비티 생명주기책 내용 정리/안드로이드 스튜디오 Arctic Fox & 프로그래밍 2023. 8. 18. 09:45
날이 갈수록 모바일 장치의 메모리나 컴퓨팅 능력이 향상되고 있다. 그러나 Desktop 시스템에 비해서는 여전히 제한 되어 있어서 효율적으로 관리해야 한다 (특히 메모리!). 운영체제와 앱 모드에서 그렇다. 관리를 하면서도 항상 사용자에게 응답할 수 있어야 한다.
그래서 안드로이드는 앱이 실행되는 프로세스와 앱을 구성하는 모든 컴포넌트의 생명주기와 상태를 전적으로 통제한다. 안드로이드 개발자는 앱과 액티비티의 생명주기 관리 모델을 이해하고 상태 변경에 대처할수 있는 능력을 기르는 것이 중요하다.
19.1 안드로이드 앱과 리소스 관리
안드로이드 앱은 안드로이드 운영체제하에서 별개의 프로세스로 실행된다. 그리고 장치의 자원이 한계에 다다를 경우 시스템에서는 메모리 확보를 위해 프로세서를 중단하는 조치를 취한다.
메모리 확보를 위해 어떤 프로세스를 중단할지 결정할 때 시스템에서는 현재 실행중인 모든 프로세스의 우선순위(Priority)와 상태( state)를 모두 고려한다. 이런 요소를 결합하여 중요도 서열(importance hierarchy)라는 것을 생성한다. 낮은 우선순위의 프로세스부터 중단을 시작해서 시스템이 기능을 수행하는 데 충분한 리소스가 확보될 때까지 중요도 서열을 따라 프로세스 중단 작업을 수행한다.
19.2 안드로이드 프로세스 상태
앱은 프로세스로 실행되고 컴포넌트로 구성된다. 안드로이드 시스템에서 프로세스의 현재 상태는 앱 내부에서 가장 높은 우선순위로 실행 중인 컴포넌트에 의해 정의된다. 아래 그림처럼 프로세스는 언제든 다음 5가지중 하나의 상태가 될 수 있다.
19.2.1 Foreground Process
이 프로세스는 화면으로 볼 수 있고 사용자와 상호작용하므로 가장 높은 수준의 우선순위가 지정되며, 시스템에 의해 제일 마지막에 중단된다. 프로세스가 Foreground 상태가 되려면 다음 조건을 하나 이상 충족해야 한다.
- 사용자와 현재 상호작용 중인 Activity를 호스팅한다.
- 사용자와 현재 상호작용 중인 Activity에 연결된 서비스를 호스팅한다.
- 중단되면 사용자에게 해를 끼칠 수 있다는 것을 startForeground() 함수를 호출하여 알려 준 서비스를 호스팅한다.
- 자신의 onCreate(), onResume(), onStart() 콜백 함수 중 하나를 실행하는 서비스를 호스팅한다.
- onReceive() 함수를 현재 실행 중인 브로드캐스트 수신자를 호스팅한다.
19.2.2 Visible Process
화면으로 볼 수는 있지만 사용자와 상호작용은 하지 않는 액티비티를 포함하는 프로세스는 Visible Process로 분류된다. 예를 들어 다른 액티비티(대화상자 같은)가 포그라운드로 실행되면서 일부 화면을 가리는 경우가 그렇다. 가시적이거나 포그라운드로 실행되는 Activity와 결합된 서비스를 호스팅하는 Process도 가시적(Visible) 상태가 될 수 있다.
19.2.3 Service Process
이미 시작되어 현재 실행 중인 Service를 포함하는 프로세스다.
19.2.4 Background Process
사용자가 화면으로 현재 볼 수 없는 하나 이상의 Activity를 포함하는 프로세스다. 더 높은 우선순위의 프로세스에서 추가 메모리가 필요한 경우, 이 부류의 프로세스는 안드로이드 런타임에 의해 중단될 가능성이 크다. 안드로이드는 백그라운드 프로세스의 내역을 동적으로 유지 관리하면서 실행 순서에 따라 프로세스를 중단한다. 즉,. 포어그라운드에서 실행한 프로세스 중 가장 오래된 프로세스가 종료된다.
예제 :
더보기예제 시나리오: 백그라운드 프로세스의 중단
가정: 사용자가 다른 앱을 실행하거나 홈 버튼을 눌러 앱을 뒤로 숨겼을 때, 앱은 백그라운드 프로세스로 전환됩니다. 시스템은 자원 관리를 위해 필요한 경우 이 백그라운드 프로세스를 중단할 수 있습니다.
19.2.5 Empty process
Empty process는 실행되는 앱을 포함하지 않으며, 새로 론칭되는 앱을 호스팅 하기 위해 메모리에 남아 있는다. 문을 연 채로 엔진을 켜 놓고 승객의 탑승을 기다리는 버스와 유사하다. 이런 프로세스는 ㅚ저 우선순위를 가지며, 리소스 해체 시에 제일 먼저 중단된다.
19.3 Activity 생명주기
안드로이드 프로세스의 상태는 자신이 호스팅하는 앱의 구성하는 Activity와 Component의 상태에 의해 결정된다. 그러므로 앱이 실행되는 동안 Activity도 서로 다른 상태로 전환된다는 것을 이해하는 것이 중요하다. Activity의 현재 상태는 Activity Stack안에서의 위치에 따라 결정된다.
19.4 Activity Stack
안드로이드 런타임 시스템에서는 장치에서 실행 중인 각 앱에 대해 Activity Stack을 유지 관리한다. 앱이 론칭되면 앱은 첫 번째 시작 Activity가 스택에 쌓인다. 다른 Activity가 실행되면 아래 그림처럼 쌓이기 시작한다. 시스템 리소스가 부족한 경우에는 스택의 제일 밑에 있는 Activity부터 제거된다.
19.5 Activity 상태
앱에서 Activity가 실행되는 동안 아래와 같은 상태 중 하나가 될 수 있다.
- 실행(Active/Running) - Activity가 스택 맨위에 있고, 장치 화면에서 볼 수 있는 포그라운드 태스크이며, 사용자와 현재 상호작용하고 있다. 이런 상태의 액티비티는 시스템 리소스가 부족할 때에도 거의 중단되지 않는다.
- 일시 중지(Paused) - 사용자가 화면에서 볼 수 있지만 포커스를 갖고 있지 않는 경우다. 현재 실행 중인 다른 Activity가 이 Activity의 화면을 부분적으로 가리고 있기 때문. 일시 중지된 Activity는 메모리에 보존되어 있고, 윈도우 매니저에 연결된 채로 있으며, 자신의 모든 상태 정보를 갖고 있다. 따라서 Activity 스택 맨위로 이동하면 실행 상태로 빨리 복원될 수 있다.
- 중단(stopped) - Activity가 사용자에게 보이지 않는다. 장치화면에서 Activity 화면 전체를 다른 Activity가 가리고 있을 때다. 이 Activity는 여전히 모든 상태를 보존한다. 그러나 시스템 메모리가 부족해지면 종료될 수 있다.
- 소멸(Killed) - 메모리 확보를 위해 런타임 시스템에 의해 Activity가 종료되어 Activity Stack에 존재하지 않는다.
19.6 구성 변경
Activity의 구성변경:
- 1) 포그라운드 백그라운드 간의 Activity 이동
- 2) 메모리 확보를 위해 런타임 시스팀이 Activity 종료
- 3) Activity의 상태가 동적으로 변경되어 장치 구성 변경
3번째는 장치의 방향을 가로에서 세로로 바꾸거나 시스템 폰트를 설정하는 등의 변경을 하면 Activity 인스턴스를 소멸하고 새로 생성하게 만든다. 왜냐하면 사용자 인터페이스 레이아웃과 같은 리소스에 영향을 주는 구성 변경이 생길 때는 영향을 받는 Activity 인스턴스를 소멸 및 재생성하여 구성 변경에 응답하는 것이 가장 빠른 방법이기 때문이다. 그러나 특정 구성 변경의 경우는 시스템에서 다시 시작하지 않도록 Activity를 구성할 수 있다.
19.7 상태 변경 처리하기
상태 변경이 생길 때는 앱의 내부 데이터 구조와 사용자 인터페이스 상태 모두 저장 또는 복원하는 것이 필요하다. 그래야만 사용자가 문제없이 서로 다른 앱을 전환하면서 사용할 수 있기 때문이다. 2가지 방법이 있다.
- 1) 안드로이드 운영체제가 호출해 주는 상태 변경 함수에서 응답하는 방법이다.
- 2) 구글에서 권장하는 Jetpack 안드로이드 아키텍처 컴포넌트에 포함된 생명주리 클래스를 사용하는 것이다. (ViewModel 인듯)
Chat gpt가 만들어준 1)번 예제
Activity 부분
더보기import android.os.Bundle; import android.os.PersistableBundle; import android.view.View; import android.widget.Button; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { private int counter = 0; private TextView counterTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); counterTextView = findViewById(R.id.counterTextView); Button incrementButton = findViewById(R.id.incrementButton); Button decrementButton = findViewById(R.id.decrementButton); incrementButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { counter++; updateCounterText(); } }); decrementButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { counter--; updateCounterText(); } }); if (savedInstanceState != null) { counter = savedInstanceState.getInt("counter"); updateCounterText(); } } @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); outState.putInt("counter", counter); } @Override protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); counter = savedInstanceState.getInt("counter"); updateCounterText(); } private void updateCounterText() { counterTextView.setText(String.valueOf(counter)); } }
xml 부분
더보기<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="16dp" tools:context=".MainActivity"> <TextView android:id="@+id/counterTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="0" android:textSize="24sp" android:layout_centerInParent="true" /> <Button android:id="@+id/incrementButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Increment" android:layout_below="@id/counterTextView" android:layout_alignParentStart="true" android:layout_marginTop="16dp" /> <Button android:id="@+id/decrementButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Decrement" android:layout_below="@id/counterTextView" android:layout_alignParentEnd="true" android:layout_marginTop="16dp" /> </RelativeLayout>
'책 내용 정리 > 안드로이드 스튜디오 Arctic Fox & 프로그래밍' 카테고리의 다른 글
Chapter 9 : 안드로이드 아키텍처 개요 (0) 2023.08.14