- Activity 는 애플리케이션의 화면을 구성하는 컴포넌트로 하나의 Activity는 하나의 화면을 나타내며, Activity 에서 출력한 내용이 안드로이드 디바이스의 화면에 나타낸다.
- 사용자는 액티비티가 생성한 UI 를 통해 애플리케이션과 상호 작용할 수 있다.
- 각 액티비티는 독립적이어서 앱이 사용자와 상호작용하기 위한 진입점 역할을 하기도 한다.
- 액티비티는 하나 이상의 뷰(View)나 뷰 그룹(View Group)을 가지고 있어야 하며, 이를 통해 UI를 표현한다.
Activity Lifecycle
- Activity 클래스는 수명 주기 상태가 변경되었음을 Activity 에 알리는 주요한 메서드들을 제공한다.
- 핵심 콜백 메소드로는 onCreate(), onStart(), onResume(), onPause(), onStop(), onDestroy() 이렇게 6가지가 존재한다.
- 액티비티가 새로운 상태게 들어가게 되면, 시스템은 각 콜백 메소드를 호출하게 된다.
- 해당 메서드들은 아래 그림에 표시된 생명 주기 순서에 따라 호출된다.
- 생명 주기 콜백 메서드를 사용하여 Activity 가 동작하는 방식을 정의하고, 리소스를 효율적으로 관리할 수 있다.
1. onCreate()
- 이 콜백은 시스템이 Activity 를 생성할 때 호출된다.
- Activity 의 생명 주기에서 단 한 번만 발생해야 하는 대부분의 초기화 로직은 여기에서 처리되어야 한다. (예: View 초기화 및 데이터 바인딩)
- 반드시 오버라이딩하여 구현해야 한다.
- 액티비티 전체 수명 주기 동안 딱 한 번만 동작되는 초기화 및 시작 로직을 실행할 수 있다.
- 예: RecyclerView 에 데이터를 바인딩하기, 버튼에 View.OnClickListener 를 설정하는 등의 동작
2. onStart()
- 이 콜백은 onCreate() 메서드를 호출한 후, Activity 가 사용자에게 보여질 때 호출된다.
- 여러 개의 Activity 또는 애플리케이션 간에 화면을 전환하는 경우 이 콜백 함수는 두 번 이상 호출될 수 있다.
- 이 메소드가 호출되면 액티비티가 사용자에게 보여지고, 포그라운드 태스크로써 사용자와 상호작용할 수 있도록 준비한다.
- 액티비티가 'STARTED' 상태에 진입하게 되어 시스템은 onStart() 와 onResume() 을 연달아 호출하게 된다.
3. onResume()
- Activity 가 foreground 로 노출되어 사용자와 상호 작용할 준비가 되었음을 의미한다.
- onStart() 는 매우 빠른 속도로 실행되고, 액티비티가 'RESUMED' 상태에 진입함과 동시에 onResume() 메소드를 호출하게 된다.
- 액티비티가 'RESUMED' 상태에 진입하게 되면 포그라운드에 액티비티가 표시되고 앱이 사용자와 상호작용을 할 수 있는 상태가 된다.
- 포그라운드에서 사용자에게 액티비티가 보여지는 동안 실행해야 하는 모든 기능을 활성화 할 수 있게 된다.
4. onPuase()
- Activity 가 더 이상 사용자에게 보여지고 있지 않지만, 여전히 부분적으로 보여질 수 있음을 의미한다. (예: 사용자가 멀티 윈도우 모드에 있는 경우).
- 대부분의 경우는 사용자가 Activity 를 떠나고 생명 주기가 다음 상태로 전환됨을 나타낸다.
- 사용자가 잠시 액티비티를 떠났을 때 (다른 액티비티에 포커스를 뒀을 때) 호출되는 콜백 메소드이다.
- 즉, 해당 액티비티가 포그라운드에 있지 않게 되었다는 것을 의미한다.
- 액티비티가 포그라운드에 없을 동안 계속 실행되어서는 안 되지만 언젠가 다시 시작할 작업을 일시중지하는 작업을 수행한다.
5. onStop()
- 이 콜백은 Activity 가 사용자에게 더 이상 보여지지 않을 때 호출된다.
- 여러 Activity 또는 애플리케이션 간에 전환하는 경우 이 콜백 함수는 두 번 이상 호출될 수 있다.
- 액티비티가 사용자에게 더 이상 표시되지 않으면 'STOPPED' 상태에 진입하고 시스템이 onStop() 콜백 메소드를 호출한다.
- 즉, 새로 시작된 액티비티가 화면 전체를 차지할 경우에 해당된다. 혹은 액티비티의 실행이 완료되어 종료될 시점에 onStop() 를 호출할 수도 있다.
- 이 메소드에서는 필요하지 않은 리소스를 해제하거나 조정해야 한다. 예를 들어 애니메이션을 일시중지하거나, GPS 사용 시 배터리를 아끼기 위해 위치 인식 정확도를 '세밀한 위치' 에서 '대략적인 위치' 로 전환할 수 있다.
6. onDestroy()
- 이 콜백은 Activity 가 완전히 파괴되기 전에 호출된다.
- 시스템은 Activity 가 종료되거나 화면전환으로 인해 시스템이 Activity 를 일시적으로 파괴할 때 이 콜백을 호출한다.
- 이 콜백은 남은 모든 리소스를 해제하고 Garbage Collector 가 할당된 모든 메모리를 회수하도록 할 때 사용할 수 있다.
- 액티비티가 완전히 소멸되기 전에 이 콜백 메소드가 호출된다. 아래와 같은 경우 액티비티가 완전히 소멸된다.
- finish() 가 호출되거나 사용자가 앱을 종료하여 액티비티가 종료되는 경우
- *화면 구성이 변경되어 일시적으로 액티비티를 소멸시키는 경우
- 화면모드(가로모드, 세로모드)의 전환, 입력기기(키보드) 변경, 지역 및 언어의 변경, 다크 모드 등
- 구성 변경 : 기기 상태가 매우 급격하게 변경되어 액티비티가 완전히 종료되고 다시 빌드하는 것
- 화면모드(가로모드, 세로모드)의 전환, 입력기기(키보드) 변경, 지역 및 언어의 변경, 다크 모드 등
Activity 상태 변화
1. 앱을 최초로 실행했을 경우
- onCreate → onStart → onResumse
2. 앱을 실행하고 종료했을 경우
- onCreate → onStart → onResume → onPause → onStop → onDestroy
3. 앱을 실행하고 홈 버튼을 눌러 백그라운드로 보낸 후 다시 실행한 경우
- onCreate → onStart → onResume → onPause → onStop → onRestart → onStart → onResume
4. 화면을 회전한 경우
- (onResume →) onPuase → onStop → onDestroy → onCreate → onStart → onResume
5. 다이얼로그(Dialog) 실행하고 다시 홈으로 돌아온 경우
- 새로운 액티비티가 최상단으로 온 상황이 아니기 때문에 아무런 함수가 호출되지 않는다.
6. (공유 버튼으로) 화면의 일부만 가려진 경우
- (onResume →) onPase → onResume
7. 첫 번째 액티비티에서 두 번째 액티비티로 화면을 전환할 경우
- 첫번째 액티비티에서 onCreate, onStart, onResume, onPause 까지 호출되고, 두번째 액티비티에서 onCreate, onStart, onResume 을 호출한 이후에 다시 첫번째 액티비티에서 onStop을 호출된다.
8. 두 번째 액티비티에서 화면을 종료하고 첫 번째 액티비티로 다시 돌아갈 때
- 두번째 액티비티에서 onPause를 호출한 다음, 첫번째 액티비티에서 onRestart, onStart, onResume 이 호출되고 두번째 액티비티에서 onStop, onDestroy가 호출된다.
Activity 상태 유지
- Configuration Changed 로 인해 원하지 않는 경우에도 onDestroy 가 호출되기 때문에, 기존에 Activity 가 가지고 있던 UI 상태들을 일게 된다.
- 비정상적인 종료에도 상태를 저장하기 위해 onSaveInstanceState() 를 사용할 수 있다.
- onSaveInstanceState() : 액티비티가 가지는 콜백 중 하나로, 화면 재생성에 대비하여 액티비티의 상태를 *Bundle 로 저장하는 함수
- Bundle : Map형태로 구현된 데이터의 묶음(Bundle)이다. Map 형태라 key 값이 String 이며, value 값에는 Int, String 과 같은 간단한 타입부터 Serializable, Parcelable 같은 복잡한 타입이 들어올 수 있다.
- 데이터 저장 객체로 상태 저장 및 복구에 사용된다. Activity가 onStop() 되기 전에 onSavedInstanceState() 에서 저장할 데이터를 저장시키며, onStart() 이후에 onRestoreInstanceState() 에서 복구시킨다.
- Bundle : Map형태로 구현된 데이터의 묶음(Bundle)이다. Map 형태라 key 값이 String 이며, value 값에는 Int, String 과 같은 간단한 타입부터 Serializable, Parcelable 같은 복잡한 타입이 들어올 수 있다.
- onSaveInstanceState() : 액티비티가 가지는 콜백 중 하나로, 화면 재생성에 대비하여 액티비티의 상태를 *Bundle 로 저장하는 함수
// Android P (API 28) 이후 부터는 onStop 이후에 onSaveInstanceState 를 발생시킨다.
@CallSuper
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
Lifecycle lifecycle = getLifecycle();
if (lifecycle instanceof LifecycleRegistry) {
// 해당 시점의 Lifecycle의 상태를 CREATED 로 설정하고 super 를 호출한다.
((LifecycleRegistry) lifecycle).setCurrentState(Lifecycle.State.CREATED);
}
super.onSaveInstanceState(outState);
mSavedStateRegistryController.performSave(outState);
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString("KEY", "VALUE")
}
상태 복원
1. onCreate() 는 Nullable 한 Bundle 객체를 통해 이전 상태를 복원할 수 있다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState != null) {
// restore data
}
}
2. onRestoreInstanceState()
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
if (mWindow != null) {
Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
if (windowState != null) {
mWindow.restoreHierarchyState(windowState);
}
}
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
val state = savedInstanceState.getString("KEY")
}
- onRestoreInstanceState 는 저장된 상태가 있고, Activity 가 다시 초기화되었을 때 onStart 이후에 호출되는 콜백 메소드이다.
- 마찬가지로 Bundle 을 통해 상태를 복원할 수 있고, onCreate 와는 다르게 Non-null 한 Bundle 을 사용한다.
- 항상 호출되는 Lifecycle 콜백이 아닌 반드시 비정상적인 종료에 의해 호출되는 콜백이기 때문에 어떤 데이터든 무조건 포함되어 있다.
화면을 회전한 경우
- (onResume →) onPuase → onStop → onSaveInstanceState → onDestroy → onCreate → onStart → onRestoreInstanceState → onResume
반전
1. onStop() 이 불렸다고 해서 onSaveInstanceState() 가 항상 불리는 것은 아니다.
- 액티비티가 올라온 상태에서 백버튼을 누르면 onSaveInstanceState() 가 호출되지 않는다.
- 상태를 보존할 필요가 없기 때문이다. onSaveInstanceState() 는 액티비티의 재생성에 대비해서 현재 상태를 저장한다.
- finish() 함수를 호출하면 onSaveInstanceState() 가 호출되지 않는다.
2. onSaveInstanceState() 가 onStop() 보다 먼저 호출될 수 있다.
- HoneyComb 미만 : onPause() 직전
- HoneyComb 이상, Pie 미만 : onStop() 직전
- Pie 이상 : onStop() 직후
- WHY) 버전에 따라 process kill 당할 수 있는 기준이 다르기 때문이다. (자세히)
생명주기를 활용하여 에러 방지하기
1. 사용자가 앱을 사용하는 도중에 전화가 걸려오거나 다른 앱으로 전환할 때 비정상 종료되는 문제
- 활동이 전화나 다른 앱으로 가려질 때에는 onStop() 이, 재개될 때에는 onResume() 이 실행된다.
- 따라서, 이 두 콜백 메서드를 잘 활용해 종료를 방지할 수 있다.
2. 사용자가 앱을 활발하게 사용하지 않는 경우 귀중한 시스템 리소스가 소비되는 문제
- 액티비티를 떠났는데 백그라운드 작업이 종료되지 않는 경우
- onDestroy() 또는 onStop() 에서 문제가 되는 작업을 종료시키면, 액티비티가 종료되거나 잠시 떠나 있을 때 해당 작업도 자동으로 중지된다.
3. 사용자가 앱에서 나갔다가 나중에 돌아왔을 때 사용자의 진행 상태가 저장되지 않는 문제
- 상태를 임시 저장해야 할 경우에는, onStop() 이후에 onSavedInstanceState() 라는 메서드가 자동으로 실행된다.
- (안드로이드 P 이후 기준) 또한 활동을 복구해야 할 때에는, onRestoreInstanceState() 메서드가 자동으로 실행된다.
- 따라서, onSavedInstanceState() 와 onRestoreInstanceState() 수명 주기 콜백을 활용해 방지할 수 있다.
4. 화면이 가로 방향과 세로 방향 간에 회전할 경우, 비정상 종료되거나 사용자의 진행 상태가 저장되지 않는 문제
- 이 문제 역시 3번의 onSavedInstanceState() 로 어느 정도는 해결할 수 있을 것이다.
- 다만, 회전의 경우에는 configChanges 를 이용해 처음부터 다시 시작되지 않게 사전에 처리할 수 있다.
Task, 백 스택
- 사용자가 특정 작업을 할 때 상호작용하는 Activity 의 집합
- 하나의 Task 에는 액티비티 집합을 열린 순서대로 정렬해놓는 백 스택이 존재한다.
- Stack 의 성질을 가지고 있으며 가장 최신에 열린 액티비티가 가장 맨위로 위치하게 된다.
- 예시: 이메일 앱에는 새 메시지 목록을 표시하는 액티비티가 존재하고, 사용자가 메시지 목록에서 메시지를 하나 클릭하면 메시지의 내용을 상세하게 볼 수 있도록 새로운 액티비티가 열리게 된다. 이때 메시지 상세보기 액티비티는 백 스택에 추가되며, 만약에 사용자가 뒤로가기 버튼을 탭하면 메시지 상세보기 액티비티는 백 스택에서 pop 된다.
백 스택의 기본 흐름
- 사용자가 앱을 시작하면 해당 앱의 Task 가 포그라운드로 나오게 된다.
- 만약, 백그라운드에 해당 앱의 Task 가 존재하지 않는다면 새로운 Task 가 생성되고, 이 앱의 MainActivity 가 백 스택의 루트 액티비티로 열리게 된다.
- 현재 액티비티가 또 다른 액티비티를 시작하면 새 액티비티가 백 스택 맨 위에 푸시되고 포커스를 갖게 된다.
- 이전 액티비티는 스택에 남아있지만 중지됨 상태를 갖게 된다. 액티비티가 중지되면 시스템은 UI의 상태를 보존한다.
- 만약 사용자가 뒤로가기 버튼을 누르면 현재 액티비티가 pop 되고, 이전 액티비티가 UI 의 상태를 유지한채로 시작된다.
- 스택의 액티비티는 절대로 다시 정렬되지 않으며, 이러한 성질을 계속 유지하게 된다.
- 사용자가 계속 뒤로 버튼을 누르면 계속 이전 액티비티가 나타나고, 만약 모든 액티비티가 스택에서 삭제되면 Task 는 더 이상 존재하지 않게 된다.
- 사용자가 새 Task 를 시작하거나 홈 버튼을 통해 홈 화면으로 이동할 때 현재 Task 는 통째로 백그라운드로 이동한다.
- Task 의 모든 액티비티는 백그라운드에 있는 동안 모두 중지되지만, Task 의 백 스택은 그대로 유지된다.
- 이후에 Task 가 다시 포그라운드로 돌아가게 되면 백 스택이 유지 되어있기에 사용자는 스택의 가장 상단에 있는 액티비티를 그대로 이어나갈 수 있다.
Intent Flag
FLAG_ACTIVITY_NEW_TASK
- 액티비티를 새 Task 에서 시작한다.
- 지금 시작하고 있는 액티비티에 대해 이미 실행 중인 Task 가 존재한다면 그 Task 의 마지막 상태가 복원되어 포그라운드로 이동하고 액티비티는 새 인텐트를 수신한다.
- 즉, *singleTask 와 동일한 동작이 발생한다.
- singleTask : 액티비티의 인스턴스가 이미 별도의 Task 에 있다면 시스템은 새 인스턴스를 생성하지 않고 기존 인스턴스로 라우팅한다. 즉, 액티비티의 인스턴스가 한 번에 하나만 존재할 수 있다.
FLAG_ACTIVITY_SINGLE_TOP
- 시작 중인 액티비티가 백 스택의 맨 위에 있는 액티비티면 새 인스턴스가 생성되는 대신 기존 인스턴스가 인텐트를 수신한다.
- 즉, singleTop 과 동일한 동작이 발생한다.
- singleTop
- 액티비티의 인스턴스가 현재 Task 의 백 스택 맨 위에 있으면 시스템은 액티비티의 인스턴스를 생성하지 않고 인텐트를 기존 인스턴스(맨 위에 있는)로 라우팅한다.
- 액티비티는 여러 번 인스턴스화될 수 있고, 여러 Task 나 한 Task 에 여러 인스턴스가 존재할 수 있지만 만약 백 스택 맨 위에 동일한 액티비티가 존재한다면 새 인스턴스를 생성하지 않는다.
- singleTop
FLAG_ACTIVITY_CLEAR_TOP
- 시작 중인 액티비티가 현재 Task 에서 이미 실행 중이면 액티비티의 새 인스턴스가 실행되는 대신 Task 의 맨 위에 있는 다른 모든 액티비티가 제거되고 이미 실행 중이던 액티비티가 맨 위로 위치하게 되며, 인텐트를 수신하게 된다.
'안드로이드 > Android' 카테고리의 다른 글
[Android] 메모리 누수 (0) | 2024.09.03 |
---|---|
[Android] App Manifest (0) | 2024.08.08 |
[Android] Gradle (0) | 2024.08.06 |