Activity의 개요

  • Activity 클래스는 안드로이드 앱의 중요한 구성 요소 중 하나
  • 안드로이드 시스템은 생명주기 중 하나의 단계에 해당하는 콜백 메서드를 호출하여 Activity 인스턴스를 시작

Activity의 개념

  • 앱은 다양한 상황에 놓일 수 있다
    ex) 사용자가 다양한 시점(장소)에서 시작될 수 있음
    ex) 앱은 사용자가 아닌 다른 주체에 의해 시작될 수도 있음
  • 이러한 다양한 상황에 앱이 효율적으로 작동할 수 있도록, Activity 클래스가 설계됨
  • 이를 위해 Activity는 사용자와 앱 사이의 중간점(컨트롤러)가 된다
  • Activity는 UI를 그리는 창을 제공한다
  • 일반적으로 앱을 구성하는 Activity들 중 하나가 기본 Activity로 지정되고, 사용자가 앱을 실행할 때 나타나는 첫 화면을 담당한다. 이후 다양한 Activity들은 다른 작업을 수행하도록 구현하고 제공할 수 있다.
  • 이러한 Activity들은 통일적인 사용자 경험을 제공하기 위해 함께 작동하지만, 각각의 Activity들은 최소한의 의존성만 있다

Activity 관리

  • 앱 Activity Life Cycle을 적절하게 관리해야 함
  • 앱 매니페스트에 활동에 대한 정보를 등록해야 함
    - 매니페스트 구성
    1. Activity 선언(name 속성과 함께)
    <manifest ... >
    <application ... >

    ...
    </application ... >
    2. 추가로 아이콘 또는 UI 테마와 같은 활동 특성을 정의하는 속성을 추가할 수도 있음

Intent Filter

  • Intent는 액티비티와 액티비티를 이어주는 역할
  • Intent Filter는 명시적 Intent뿐만 아니라 암시적 Intent를 기반으로 활동을 시작할 수 있는 기능을 제공
  • action : 컴포넌트 동작 방식에 대해 알려주는 용도이자 setAction의 인자값이 됩니다.
    category : 카테고리는 해당 인텐트가 어떤 범주에 속해있는지에 대한 것입니다.
    기본으로 제공되는 값으로 이것이 어떤식으로 실행되는지에 대한 것을 설정 가능합니다.
    android.intent.category.LAUNCHER 는 앱을 처음 실행할 때에 실행될 액티비티에 대한 것을 의미하기도 하고,
    android.intent.category.BROWSABLE 은 브라우저에 의해 처리되는 것,
    android.intent.category.DEFAULT 는 startIntent로 실행되는 액티비티라는 것을 의미합니다.
    이렇게 동작 방식을 결정하는데에 설정 가능하고, 커스텀 카테고리를 설정할수도 있습니다.
    data : 컴포넌트 실행 데이터 형식을 설정하는 것입니다.
  • 예를 들어, 다음 코드는 텍스트 데이터를 보내는 활동을 구성하고 다른 활동에서 요청을 수신하는 방법을 보여줍니다.
<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
	<intent-filter>
		<action android:name="android.intent.action.SEND" />
		<category android:name="android.intent.category.DEFAULT" />
		<data android:mimeType="text/plain" />
	</intent-filter>
</activity>

다음 코드는 위에서 설명한 활동을 호출하는 방법

val sendIntent = Intent().apply {
    action = Intent.ACTION_SEND
    type = "text/plain"
    putExtra(Intent.EXTRA_TEXT, textMessage)
}
startActivity(sendIntent)
  • Activity가 앱 내부에서만 포함되고, 다른 앱이 해당 Activity 하지 않는다면 다른 Intent Filter가 필요하지 않음

활동 수명 주기 관리

  • 하나의 Activity는 여러 가지 상태를 거친다

  • 일련의 콜백을 사용하여 이러한 상태 간 전환을 처리할 수 있다

  • 사용자가 활동을 벗어났다가 다시 돌아왔을 때 활동이 작동하는 방식을 수명 주기 콜백 메서드에서 선언하여 관리할 수 있음

onCreate()

반드시 이 콜백을 구현해야 함
시스템이 활동을 생성할 때 발생
Activity의 필수 구성 요소를 초기화한다
활동의 전체 수명 주기 동안 한 번만 발생해야 하는 기본 애플리케이션 시작 로직을 실행
setContentView()를 호출하여, Activity의 사용자 인터페이스에 대한 레이아웃을 정의해야 함
OnCreate()가 완료되면 다음 콜백은 항상 OnStart()

This callback is called only when there is a saved instance that is previously saved by using
    // onSaveInstanceState(). We restore some state in onCreate(), while we can optionally restore
    // other state here, possibly usable after onStart() has completed.
    // The savedInstanceState Bundle is same as the one used in onCreate().
    override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
        textView.text = savedInstanceState?.getString(TEXT_VIEW_KEY)
    }
    // invoked when the activity may be temporarily destroyed, save the instance state here
    override fun onSaveInstanceState(outState: Bundle?) {
        outState?.run {
            putString(GAME_STATE_KEY, gameState)
            putString(TEXT_VIEW_KEY, textView.text.toString())
        }
        // call superclass to save any view hierarchy
        super.onSaveInstanceState(outState)
    }

onStart()

onCreate()가 종료되면, 시작 상태로 전환되고 사용자가 활동을 볼 수 있게 됨
Activity를 시작하고 사용자와 상호 작용하기 위한 최종 준비에 해당하는 내용을 포함
앱이 UI를 관리하는 코드를 초기화

onResume()

Activity가 사용자와 상호 작용하기 직전에 시스템이 이 콜백을 호출
Activity가 Activity 스택의 맨 위에 존재하게 된다
모든 사용자 입력을 캡처
앱의 핵심 기능을 대부분 구현
onPause() 콜백은 항상 onResume() 다음에 수행

onPause()

Activity가 포커스를 잃고 일시 중지 상태로 들어가면 시스템이 onPause()를 호출
기술적인 측면에선 사용자의 활동이 부분적으로 여전히 표시되는 상태지만, 대부분 사용자가 활동을 종료하고 있음을 나타내며 곧 중지됨 또는 재개됨 상태로 전환됨
사용자가 UI를 업데이트할 것으로 예상하는 경우, UI를 계속 업데이트할 수 있음
ex) 내비게이션 지도 화면 또는 재생 중인 미디어 플레이어
이러한 작업의 경우, 시스템은 포커스가 손실되더라도 UI는 계속 업데이트될 것으로 예상
아주 잠깐 실행되므로 저장 작업을 실행하기에는 시간이 부족할 수 있습니다 응용 프로그램 또는 사용자 데이터를 저장하거나, 네트워크 호출을 수행하거나, 데이터베이스 트랜잭션을 실행해서는 안 됨
실행이 완료되면 다음 콜백은 onStop(), onResume() 중 하나

onStop()

사용자가 활동을 더 이상 볼 수 없을 때 시스템이 호출
Activity가 삭제되고 있거나, 새 Activity가 시작되고 있거나, 기존 Activity가 재개된 상태로 전환되어 중지된 Activity 등에서 호출
중지된 활동을 더 이상 볼 수 없음
시스템에서 호출하는 다음 콜백은 onRestart()(사용자와 상호 작용하기 위해 되돌아오는 경우) 또는 Activity가 완전히 종료되는 경우 onDestroy()
앱이 사용자에게 보이지 않는 동안 앱은 필요하지 않은 리소스를 해제하거나 조정해야 합니다 - UI 관련 리소스와 작업을 완전히 해제하거나 조정할 때, CPU를 비교적 많이 소모하는 종료 작업을 실행

onRestart()

중지 상태의 작업이 다시 시작하려고 할 때 시스템이 이 콜백을 호출
다시 시작하면 중지된 시점의 작업 상태가 복원
이 콜백은 항상 onStart() 다음에 수행

onDestroy()

활동이 삭제되기 전에 시스템이 이 콜백을 호출
이 콜백은 Activity가 수신하는 마지막 콜백
일반적으로 Activity 또는 Activity를 포함하는 프로세스가 파괴될 때, Activity의 모든 자원이 해제되도록 구현



임시 UI 상태 저장 및 복원

  • 사용자는 회전 또는 멀티 윈도우 전환 같은 구성 변경사항이 발생하더라도 Activity의 UI 상태를 동일하게 유지할 것이라 기대
  • 사용자는 일시적으로 앱에서 다른 앱으로 전환했다가 다시 앱으로 돌아왔을 때도 UI 상태를 유지할 것이라 기대
  • 하지만 시스템은 이런 구성 변경이 발생하면 기본적으로 Activity을 소멸시켜 Activity 인스턴스에 저장된 모든 UI 상태를 제거함
  • 또한 사용자가 나가서 Activity가 중단되면, 시스템은 애플리케이션의 프로세스를 소멸시킬 수 있음
  • 이처럼 시스템 제약으로 인해 활동이 소멸될 때 ViewModel, onSaveInstanceState() 및/또는 로컬 저장소를 결합하여 사용자의 임시 UI 상태를 보존하는 것이 필요함
  • UI 데이터가 간단하고 가벼울 경우, 모든 구성 변경 및 시스템이 시작한 프로세스가 종료될 때 onSaveInstanceState()만으로도 UI 상태를 보존할 수 있음
  • 그러나 onSaveInstanceState()가 직렬화/역직렬화 비용을 발생시키기 때문에 대부분의 경우에는 ViewModel과 onSaveInstanceState()를 모두 사용해야 함

인스턴스 상태

정상적인 앱 동작으로 인해 활동이 소멸되는 시나리오
1. 사용자가 뒤로가기를 누르거나, 활동이 finish() 메서드를 호출하여 자체적인 소멸 신호를 보내는 경우
= 해당 Activity 인스턴스에 관한 시스템과 사용자의 내용이 모두 영구적으로 사라짐 = 해당 경우, 사용자의 기대와 시스템의 동작이 일치 - 추가 작업이 필요하지 않음
2. 시스템이 시스템 제약(예: 구성 변경 또는 메모리 부족)으로 인해 활동을 소멸시킬 경우
= 실제 Activity 인스턴스는 사라지더라도 시스템에 존재했다는 정보는 남음
= 사용자가 활동으로 다시 돌아가려고 시도하면, 시스템이 소멸 당시 활동의 상태를 나타내는 저장된 데이터 셋을 사용하여 해당 Acitivity의 새로운 인스턴스를 생성
= 시스템이 이전 상태를 복원하기 위해 사용하는 저장된 데이터가 인스턴스 상태임
= 이는 Bundle 객체에 저장된 키-값 쌍의 컬렉션

  • 기본적으로 시스템은 Bundle 인스턴스 상태를 사용하여, Activity 레이아웃의 각 View 객체 관련 정보를 저장
    = 따라서 Activity 인스턴스가 소멸되고 재생성되더라도 레이아웃의 상태는 별도의 코드 요청 없이 이전 상태로 복원됨
    = 하지만 Activity에서 사용자 진행 상태를 추적하는 멤버 변수처럼, Activity에 복원에 필요한 상태 정보가 더 많이 있는 경우도 있음
  • Bundle 객체는 메인 스레드에서 직렬화되어야 하고 시스템 프로세스 메모리를 사용하므로 소량의 데이터를 보존하는 데만 적합
  • 극소량 이상의 데이터를 보존하려면 영구 로컬 저장소, onSaveInstanceState() 메서드, ViewModel 클래스로 데이터를 보존하는 복합적인 방법을 사용해야

onSaveInstanceState()를 사용하여 간단하고 가벼운 UI 상태 저장

  • Activity가 정지되기 시작하면, 인스턴스 상태 Bundle에 상태 정보를 저장할 수 있도록 시스템이 onSaveInstanceState() 메서드를 호출
  • Activity의 추가적인 인스턴스 상태 정보를 저장하려면 onSaveInstanceState()를 재정의
  • Activity가 예상치 못하게 소멸될 경우 저장되는 Bundle 객체에 키-값 쌍을 추가
  • 사용자가 활동을 명시적으로 닫는 경우 또는 finish()가 호출된 경우에는onSaveInstanceState()가 호출되지 않음
  override fun onSaveInstanceState(outState: Bundle?) {
    // Save the user's current game state
    outState?.run {
        putInt(STATE_SCORE, currentScore)
        putInt(STATE_LEVEL, currentLevel)
    }
    // Always call the superclass so it can save the view hierarchy state
    super.onSaveInstanceState(outState)
}
companion object {
    val STATE_SCORE = "playerScore"
    val STATE_LEVEL = "playerLevel"
}

저장된 인스턴스 상태를 사용하여 활동 UI 상태 복원

  • Activity가 이전에 소멸된 후 재생성되면, 시스템이 활동에 전달하는 Bundle로부터 저장된 인스턴스 상태를 복구할 수 있음.
  • onCreate() 및 onRestoreInstanceState() 콜백 메서드 둘 다 인스턴스 상태 정보를 포함하는 동일한 Bundle을 수신
  • onCreate() 메서드는 시스템이 활동의 새 인스턴스를 생성하든, 이전 인스턴스를 재생성하든 상관없이 호출되므로 읽기를 시도하기 전에 번들 상태가 null인지 반드시 확인
    = null일 경우, 시스템은 이전에 소멸된 활동의 인스턴스를 복원하지 않고 새 인스턴스를 생성합니다.
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState) // Always call the superclass first
    // Check whether we're recreating a previously destroyed instance
    if (savedInstanceState != null) {
        with(savedInstanceState) {
            // Restore value of members from saved state
            currentScore = getInt(STATE_SCORE)
            currentLevel = getInt(STATE_LEVEL)
        }
    } else {
        // Probably initialize members with default values for a new instance
    }
    // ...
}
  • onCreate() 대신, onRestoreInstanceState()를 구현하는 방법도 있음.
  • 이는 시스템이 onStart() 메서드 다음에 호출
  • 시스템은 복원할 저장 상태가 있을 경우에만 onRestoreInstanceState()를 호출
    = 따라서 Bundle이 null인지 확인할 필요가 없습니다.
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
    // Always call the superclass so it can restore the view hierarchy
    super.onRestoreInstanceState(savedInstanceState)
    // Restore state members from saved instance
    savedInstanceState?.run {
        currentScore = getInt(STATE_SCORE)
        currentLevel = getInt(STATE_LEVEL)
    }
}
  • 항상 onRestoreInstanceState()의 상위 클래스 구현을 호출(기본 구현에서 뷰 계층 구조의 상태를 복원할 수 있도록)

Activity간 이동

  • 특정 시점에서 다른 Activity를 시작해야 하는 경우, 새로 시작하려는 Activity에서 결과를 받기를 원하는지 여부에 따라 startActivity()나 startActivityForResult() 메서드 중 하나를 사용하여 시작
  • 두 가지 경우 모두 Intent 객체를 전달
  • Intent 객체는 시작하고자 하는 Activity를 정확히 나타내거나, 실행하고자 하는 작업의 유형을 설명
  • Intent 객체는 시작된 Activity에서 사용할 소량의 데이터를 포함할 수 있음
  • startActivity()
    새로 시작된 Activity가 결과를 반환할 필요가 없을 경우, 현재 Activity에서 startActivity() 메서드를 호출하면 이를 시작할 수 있음
val intent = Intent(this, SignInActivity::class.java)
startActivity(intent)
  • 또한 애플리케이션에서 다른 몇 가지 작업을 수행하고자 할 수도 있음
    = 이메일 보내기, SMS 보내기 또는 상태 업데이트 등의 작업
    = 이 경우, 본인의 애플리케이션에 그러한 동작을 실행할 자체 Activity가 없을 수도 있음
    = 따라서 기기에 있는 다른 애플리케이션이 제공하는 Activity을 대신 활용하여 동작을 실행
  • 실행하고자 하는 동작을 설명하는 인텐트를 생성하면, 시스템이 적절한 Activity를 다른 애플리케이션에서 시작
  • 인텐트를 처리할 수 있는 Activity가 여러 개 있는 경우, 사용자는 어느 것을 사용할지 선택할 수 있음
val intent = Intent(Intent.ACTION_SEND).apply {
    putExtra(Intent.EXTRA_EMAIL, recipientArray)
}
startActivity(intent)
  • startActivityForResult()
    = 활동이 종료될 때 결과를 반환받고자 할 수도 있음
    = 이 메서드에서는 정수 매개변수가 호출을 식별
    = 이 식별자는 동일한 Activity에서 여러 개의 startActivityForResult(Intent, int) 호출을 명확히 구분하는 역할
    = 이는 글로벌 식별자가 아니며, 다른 앱이나 활동과 충돌할 위험이 없음
    = 결과는 onActivityResult(int, int, Intent) 메서드를 통해 반환
  • 하위 활동이 존재하면 setResult(int)를 호출하여 상위 활동으로 데이터를 반환할 수 있음 - 하위 활동은 항상 결과 코드를 제공
    = 이는 표준 결과인 RESULT_CANCELED, RESULT_OK가 되거나
    = RESULT_FIRST_USER로 시작하는 임의의 맞춤 값이 될 수도 있음
  • 또한 하위 활동은 원하는 모든 추가 데이터가 포함된 Intent 객체를 반환할 수도 있음
class MyActivity : Activity() {
    // ...
    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
            // When the user center presses, let them pick a contact.
            startActivityForResult(
                    PICK_CONTACT_REQUEST)
            return true
        }
        return false
    }
    override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
        when (requestCode) {
            PICK_CONTACT_REQUEST ->
                if (resultCode == RESULT_OK) {
                    startActivity(Intent(Intent.ACTION_VIEW, intent?.data))
                }
        }
    }
    companion object {
        internal val PICK_CONTACT_REQUEST = 0
    }
}

Activity 조정

  • 한 Activity가 다른 Activity를 시작하면 두 개 Activity 모두 수명 주기가 전환됨
  • 다른 Activity이 생성되는 동안 첫 번째 Activity은 작동을 중단하고 일시중지됨 또는 정지됨 상태로 들어감
  • 이와 같은 Activity이 디스크 또는 다른 곳에 저장된 데이터를 공유하는 경우, 첫 번째 Activity는 두 번째 Activity가 생성되기 전에 완전히 중단되지 않는다는 점을 이해
  • Activity A가 Activity B를 시작할 때 발생하는 작업 순서는 아래와 같음
    = Activity A의 onPause() 메서드가 실행
    = Activity B의 onCreate(), onStart() 및 onResume() 메서드가 순차적으로 실행
    = Activity A가 더 이상 화면에 표시되지 않는 경우 이 Activity의 onStop() 메서드가 실행
profile
어제보다 더 나음을 위해.

0개의 댓글