[안드로이드 공식문서 파헤치기] Activity의 모든 것!

dada·2021년 9월 1일
1
post-thumbnail

액티비티는 일종의 어플리케이션 구성요소 로서, 사용자와 상호작용 할 수 있는 화면을 제공한다. 액티비티 마다 layout.xml 파일을 구현 함으로써 사용자에게 인터페이스를 제공 해 줄 수 있다.액티비티를 사용하기 위해서는 아래와 같은 내용들을 알아야 한다.
"액티비티는 기본적으로 컨테이너의 역할을 하고, 사용자 인터페이스 화면을 구성하는 컴포넌트이다."

  • setContentView()를 이용하여 액티비티의 View를 Draw
  • AndroidManifest 파일에 Activity를 등록
  • 라이프 사이클 콜백 처리
  • onCreate, onStart, onResume, onPause, onDestroy
  • 액티비티 시작시에 정보를 전달하거나 액티비티가 종료될 때 결과를 리턴할 수 있다.

✅안드로이드 앱 구성 요소(컴포넌트) 중 하나인 Activity

Activity, Service, Broadcast Receiver, Content Provider

  • 앱 구성 요소는 Android 앱의 필수적인 기본 구성 요소입니다

  • 각 구성 요소는 시스템이나 사용자가 앱에 들어올 수 있는 진입점입니다

  • 다른 구성 요소에 종속되는 구성 요소도 존재합니다.

  • 각 유형은 뚜렷한 목적을 수행하고 각자 나름의 수명 주기가 있어 구성 요소의 생성 및 소멸 방식을 정의합니다.

  • 각 구성요소의 특징은 이 포스팅을 참고해주세요!

✔Activity

  • Activity는 사용자와 상호작용하기 위한 진입점입니다.

  • 사용자 인터페이스(UI)를 포함한 화면 하나를 나타냅니다

  • 대부분의 앱은 한 개의 Activity가 아닌 여러 Activity로 이루어져 있습니다.
    =각 Activity는 또 다른 Activity를 시작할 수 있음

    • ex)예를 들어 이메일 앱이라면 새 이메일 목록을 표시하는 액티비티가 하나 있고, 이메일을 작성하는 액티비티가 또 하나, 그리고 이메일을 읽는 데 쓰는 액티비티가 또 하나 있을 수 있음
    • 앱의 활동 중 하나는 환경설정 화면을 구현하고 또 다른 활동은 사진 선택 화면을 구현
  • 여러 Activity가 함께 작동하는 것은 사실이지만, 각자 서로 독립되어 있습니다.

  • 따라서 Android 시스템에서 허용할 경우, 다른 앱이 이메일 앱의 Acitivity 중 하나에 접근 가능합니다.

    • ex)카메라 앱이라면 이메일 앱 안의 액티비티를 시작하여 사용자가 새 이메일을 작성하고 사진을 공유하게 할 수 있습니다.
  • Activity는 안드로이드 시스템과 안드로이드 앱의 주요 상호작용을 돕습니다.

  • Activity는 Activity라는 클래스를 상속한 SubClass를 작성함으로써 구현합니다.

✅Activity를 구체적으로 알아보자

  • main() 메소드를 호출하여 프로그램을 실행하는 프로그래밍 패러다임과 달리 Android 시스템은 Activity 수명 주기의 특정 단계에 해당하는 특정 콜백 메소드를 호출하여 앱을 실행하는 패러다임 을 가집니다

  • 모바일 앱 환경은 사용자와 앱의 상호작용이 항상 동일한 위치(ex.Main Activity)에서 시작되는 것이 아닙니다

  • 즉, 사용자와 앱의 상호작용에 따라 앱이 시작되는 Activity가 다를 수 있고, Activity 외의 다른 컴포넌트를 통해 앱이 시작될 수도 있습니다

  • 이메일 앱일 경우, 홈 화면에서 앱을 실행시키면 (ex.Main Activity)에서 이메일 목록을 보여주는 Activity가 먼저 시작되지만 SNS 앱을 사용하고 있는 상태에서 이메일을 작성하기 위해 SNS 앱에서 이메일 앱을 열면 이메일 작성 Activity가 먼저 시작되도록 할 수 있음 +이런 방식으로 Activity는 앱과 사용자의 상호작용을 위한 진입점 역할을 합니다

  • 일반적으로 앱에서 하나의 활동이 기본 활동(Launch) 으로 지정되며 이 기본 활동은 사용자가 앱을 실행할 때 표시되는 첫 번째 화면

  • 각 Activity는 또 다른 Activity를 시작할 수 있습니다

    • ex)간단한 이메일 앱의 기본 활동은 이메일 받은편지함을 표시하는 화면. 여기에서 기본 활동은 이메일 작성 및 개별 이메일 열기와 같은 작업을 위한 화면을 제공하는 다른 활동들을 실행
  • 앱의 Activity를 사용하려면 앱의 manifest에 활동 관련 정보를 등록하고 활동 수명 주기를 적절히 관리해야합니다

✅Activity 사용을 위해 manifest에 등록하기

  • manifest에 Activity관련된 특정 속성을 선언해야 앱에서 Activity를 사용할 수 있습니다
    • 만약 어떤 앱에 MainActivity와 LoginActivity가 존재한다면 이 두 Activity 모두 manifest에 등록해야 합니다
  • Activity를 선언: manifests폴더->AndroidManifest.xml 파일을 열고 <application> 하위 요소로 <activity> 요소를 추가(하기 사진은 추가되지 않은 MainActivity상태)
    <manifest ... >
      <application ... >
          <activity android:name=".ExampleActivity" />
          ...
      </application ... >
      ...
    </manifest >
  • 요소의 유일한 필수 속성은 android:name 이라는 속성. 이 속성의 속성값으로 해당 Activity 클래스 이름을 작성합니다
  • 라벨, 아이콘 또는 UI 테마와 같은 활동 특성을 정의하는 속성도 추가 가능합니다

✅Activity가 다른 앱의 구성요소에게 무언가를 요청할때는 어떻게할까?

  • Activity가 다른 앱의 구성요소(4대 컴포넌트)에게 무언가를 요청할 때는 Intent라는 메시징 객체를 사용해야 합니다. Intent는 다른 앱 구성 요소(시스템이나 사용자가 앱에 들어올 수 있는 진입점/액티비티,서비스,Broadcast Receiver,콘텐츠 제공자)로부터 작업을 요청하는 데 사용합니다

  • 어떠한 의도를 가지고 실행할 것인지를 Intent에 담아서 안드로이드에 전달하면 해당 Intent를 해석하고 실행합니다

  • Intent를 요청하는 상황은 대표적으로 3가지가 있습니다

      1. Activity 시작: 새로운 Activity 인스턴스를 시작하기 위해 Intent의 멤버함수인 startActivity()로 전달
      1. Service 시작: 서비스를 시작하여 일회성 작업을 수행하도록 하려면(ex.파일 다운로드) Intent를 startService()에 전달
      1. BroadCast 전달: Intent를 sendBroadcast() 또는 sendOrderedBroadcast()에 전달하면 다른 앱에 브로드캐스트를 전달할 수 있음
  • 이 중에서 Activity가 새로운 Activity를 시작하기 위해 Intent를 사용하는 경우를 자세히 알아보겠습니다.

✔1. 명시적 Intent

  • 이름 그대로 ‘명시’되어 있는 Intent
  • 뭘 명시할까요? 바로 Activity 또는 서비스 클래스 이름입니다!
  • 앱 안에서 Activity나 서비스 같은 구성 요소를 시작할 때 사용하기 때문에 시작하고자 하는 Activity, Service class 이름을 알고 있어야합니다

✔2. 암시적 Intent

  • 특정 구성요소의 이름을 명시 하지 않지만 그 대신 수행할 일반적인 작업을 선언해서 다른 앱의 구성요소가 이 작업을 처리하게 하고플때 사용합니다
  • val intent = Intent(Intent.ACTION_VIEW, Uri.parse("http://m.naver.com"))

암시적 Intent는 어떻게 동작할까요?

1. Activity A가 의도를 가진 class인 Intent를 만들어서 startActivity 인자에 넣어 새로 시작할 Activity의 정보가 담긴 Intent를 안드로이드 시스템에 토스!
2. Android 시스템이 핸드폰에 설치되어 있는 모든 앱에서 해당 Intent와 일치하는 Intent 필터를 찾는다
3. 일치하는 Intent 필터 항목 찾으면! 시스템이 해당 Activity의 onCreate() 메소드 호출해서 Intent 던지기!!! 하면 Activity 시작
4. 만약 일치하는 Intent Filter가 여러 앱에 존재한다면? 안드로이드 시스템은 포기하지 않는다. 시스템에서 대화상자를 표시하여 사용자가 어떤 앱을 사용할건지 직접 선택하게함

Intent Filter는 무엇일까요?

  • Intent Filter는 앱의 매니페스트 파일에 들어있는 표현으로, 해당 구성 요소가 수신하고자 하는 Intent의 유형을 나타냄
  • 예를 들어, 구성 요소(액티비티, 서비스, 브로드캐스트) 중 액티비티라는 구성 요소에 대한 인텐트 필터를 선언하면 다른 여러 앱이 특정한 종류의 인텐트를 가지고 이 앱의 액티비티를 직접 시작할 수 있음
  • 따라서 어떤 액티비티에 대한 인텐트 필터를 선언하지 않은 경우, 그 액티비티는 다른 앱에 의하여 시작될 수 없고, 해당 앱 내에서 명시적인 인텐트로만 시작할 수 있음(암시적인 인텐트로 다른 앱을 시작하려면 시작하려는 앱에 최소 한 개의 엑티비티에 대한 인텐트 필터 필요)
  • 주의)
    • 앱의 보안을 보장하기 위해서 구성 요소 중 서비스 에 대한 암시적 인텐트는 하지 않는 것이 좋음
    • Service는 사용자 인터페이스 없이 백그라운드에서 작업을 수행하는 구성 요소라서 사용자가 어느 서비스가 시작되는지 눈으로 확인할 수 없기 때문
    • 따라서 서비스를 시작하려면 항상 명시적 인텐트만 사용하고 암시적 인텐트를 위한 서비스 인텐트 필터는 선언하지 말아야 함

✅수명주기란?

  • 안드로이드 4대 컴포넌트는 수명주기라는 것을 가지고 있습니다. 우리가 알아보고 있는 Activity도 자신의 주명주기 안에서 실행되거나 인스턴스가 소멸되는 등 상태가 전환됩니다.

  • 수명주기란 Activity 클래스가 생성되거나, 중단 또는 다시 시작하거나, 종료되는 등의 'Activity의 상태'를 수명주기라고 할 수 있습니다

  • 사용자가 앱을 탐색하고, 앱에서 나가고, 앱으로 다시 돌아가면, 앱의 Activity 인스턴스는 수명 주기 안에서 서로 다른 상태를 통해 전환됩니다. 예를 들어, 사용자가 유튜브를 보다가 인스타그램을 켰을때 유튜브 영상은 일시정지하고 네트워크 연결을 종료하도록 코드를 작성할 수 있습니다. 사용자가 다시 유튜브로 돌아오면 네트워크를 다시 연결하고 일시정지한 지점에서 동영상을 다시 시작하도록 허용합니다. 이처럼 각 상태변화에 적합한 작업을 실행할 수 있도록 개발자는 Activity의 수명주기를 제대로 아는 것이 중요합니다

  • 그렇다면 이런 상태변화을 개발자는 어떻게 알아차리고, 상태를 직접 전환하므로서 사용자에게 편리한 환경을 제공할 수 있을까요?

  • 이를 위해 Activity 는 수명 주기 단계 간 상태전환을 알아차릴 수 있는 6가지 콜백 onCreate(), onStart(), onResume(), onPause(), onStop(), onDestroy() 제공합니다 이러한 수명주기 콜백을 잘 구현하면 아래와 같은 문제가 발생하지 않도록 예방하는 데 도움이 될 수 있습니다.

      1. 사용자가 앱을 사용하는 도중에 전화가 걸려오거나 다른 앱으로 전환할 때 비정상 종료되는 문제
      1. 사용자가 앱을 활발하게 사용하지 않는 경우 귀중한 시스템 리소스가 소비되는 문제
      1. 사용자가 앱에서 나갔다가 나중에 돌아왔을 때 사용자의 진행 상태가 저장되지 않는 문제
      1. 화면이 가로 방향과 세로 방향 간에 회전할 경우, 비정상 종료되거나 사용자의 진행 상태가 저장되지 않는 문제

✅Activity 수명주기


  • 수명주기 순서가 저절로 외워지긴 하지만 가끔 헷갈리길래, 수명주기들 앞글자인 CSRPSD를 활용해서 스시랑 파스다라고 외웠다,,ㅎㅎ

✅다양한 case 별 Activity 수명주기

→ 최초로 Activity a 실행시

Activity a : onCreate()
Activity a : onStart()
Activity a : onResume()

→ 투명한 Activity b가 Activity a 위에 띄워질 시

Activity a : onCreate()
Activity a : onStart()
Activity a : onResume()
Activity a : onPause()
Activity b : onCreate()
Activity b : onStart()
Activity b : onResume()

→ finish 버튼 눌러서 Activity b 종료

Activity b : onPause()
Activity a : onResume()
Activity b : onStop()
Activity b : onDestroy()

Q) 액티비티에서 뒤로가기 버튼을 누르면?

A)

  • Activity::onBackPressed() 콜백 함수가 호출됩니다.Androidx에선 androidx.activity.OnBackPressedCallback 추상 클래스를 제공합니다.
  • OnBackPressedCallback::handleOnBackPressed() 메서드를 제공하고 있어서, 액티비티와의 의존성을 줄이고, Fragment에서 '뒤로 가기 버튼 로직'을 추가 할 수 있습니다.
  • OnBackPressedDispatcher.addCallback(LifecycleOwner, OnBackPressedCallback)을 사용해서 Fragment를 등록할 수 있습니다.

Q) 액티비티에서 홈 버튼을 눌렀을 때, 콜백 함수?

A)

  • 홈 버튼은 앱에서 특별히 제어 할 수 없습니다.
  • 하지만, 사용자가 홈 키를 눌러 사용중인 앱이 Background로 넘어가는 경우 Activity::onUserLeaveHint() 함수가 호출됩니다.
  • 앱을 사용중에 전화가 걸려올때/타이머 등에 의해 호출 X
  • 사용자가 선택적으로 액티비티가 백그라운드로 이동하는 경우에만 호출됩니다.
  • Home키는 키입력 이벤트로 전달되지 않아 검출이 어려운데 이 메서드가 호출될 때 Home키에 의해 백드라운드가 됨을 알 수 있습니다.
  • 또, 홈버튼 누르면 백그라운드로 가기 때문에 무조건 onStop()을 호출한다는거 기억!

Q) 현재 A 액티비티가 포그라운드 상태. 이 때 B액티비티로 전환 되면 발생하는 lifecycle Callback 함수는?

A)

  • A : onPause()
    B : onCreate()
    B : onStart()
    B : onResume()
    A : onStop()

Q) B 액티비티에서 finish() 함수를 호출하게 되면 발생하는 Lifecycle 순서는?

A)

  • B : onPause()
    A : onRestart()
    A : onStart()
    A : onResume()
    B : onStop()
    B : onDestroy()

Q) Activity위에 dialog가 띄워질때 Activity수명주기는?

A)

  • Activity의 수명주기에는 어떠한 변화도 없습니다.
  • 새로운 Activity를 위에 띄우는게 아니라 다이어로그를 호출하면 Activity의 생명주기는 어떠한 변화도 없습니다. 왜냐하면 콜백되는 함수가 없기 때문입니다!! 즉 onPause() 된게 아니라서 onResume()도 호출되지 않습니다
  • 다이얼로그가 Activity의 일부이기 때문입니다. 즉 새로운 Activity가 최상단으로 온 상황이 아니기 때문에 아무런 함수가 호출되지 않습니다

✅Activity의 3가지 상태

  • Active or Running 상태
    • 액티비티가 전면에서 실행되고 있을 때
    • 현재 Task에 대한 Activity Stack의 최상위에 존재할 때
  • Pause 상태
    • 다른 액티비티가 위에 존재 하지만 그 액티비티가 투명상태 혹은 전체 화면을 채우지 못해 아직은 이전 액티비티가 보이는 상태
  • Stop 상태
    • 다른 Activity에 의해 완전히 가려져 더 이상 사용자에게 보여지지 않을 때

✅Launch Mode 4가지 ?

  • standard(default)
    • 인텐트 할 때마다 Activity를 새로 생성
  • singleTop
    • Intent 때마다 새로 생성하나, 동일한 Activity가 해당 테스크의 top에 있을 경우 새로 생성 하지 않고 기존에 있던 Activity 호출
    • 생명 주기는 onPause() -> onNewIntent() -> onResume()
  • singleTask
    • 하나의 Activity만 생성 되나, 다른 Activity가 해당 태스크의 일부가 되는 것을 허용
  • singleInstance
    • 이 옵션도 singleTask와 비슷하나 그 어떤 Activity와도 섞이지 않고 유일한 Activity로 동작
    • task 안에 activity가 하나만 존재

✅onSaveInstanceState

  • 시스템이 이전 상태를 복원하기 위해 사용하는 저장된 데이터를 InstanceState라고 하며, 이는 Bundle 객체에 저장된 키-값 쌍의 컬렉션입니다.

  • 기본적으로 시스템은 Bundle 인스턴스 상태를 사용하여 활동 레이아웃의 각 View 객체 관련 정보를 저장합니다(예: EditText 위젯에 입력된 텍스트 값).

  • onSaveInstanceState()를 사용하여 간단하고 가벼운 UI 상태 저장할 수 있습니다.

  • Activity가 onStop상태에 들어서면 인스턴스 상태 번들에 상태 정보를 저장할 수 있도록 시스템이 onSaveInstanceState() 메서드를 호출합니다. 이 메서드의 기본 구현은 EditText 위젯 내 텍스트 또는 ListView 위젯의 스크롤 위치와 같은 활동의 뷰 계층 구조에 대한 임시 정보를 저장합니다.

  • 활동의 추가적인 인스턴스 상태 정보를 저장하려면 onSaveInstanceState()를 재정의하고, 활동이 예상치 못하게 소멸될 경우 저장되는 Bundle 객체에 키-값 쌍을 추가해야 합니다.

  • onSaveInstanceState()를 재정의할 경우 기본 구현에서 뷰 계층 구조의 상태를 저장하고자 한다면 상위 클래스 구현을 호출해야 합니다.

  • 용자가 Activity를 명시적으로 닫는 경우 또는 finish()가 호출된 경우에는onSaveInstanceState()가 호출되지 않습니다.

static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";
// ...
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 상태 복원
    활동이 이전에 소멸된 후 재생성되면, 시스템이 활동에 전달하는 Bundle로부터 저장된 인스턴스 상태를 복구할 수 있습니다.

  • onCreate()onRestoreInstanceState() 콜백 메서드 둘 다 인스턴스 상태 정보를 포함하는 동일한 Bundle을 수신합니다.

  • onCreate() 메서드는 시스템이 Activity의 새 인스턴스를 생성하든, 이전 인스턴스를 재생성하든 상관없이 호출되므로 읽기를 시도하기 전에 번들 상태가 null인지 반드시 확인해야 합니다. null일 경우, 시스템은 이전에 소멸된 Activity의 인스턴스를 복원하지 않고 새 인스턴스를 생성합니다.

  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인지 확인할 필요가 없습니다.

  • 항상 onRestoreInstanceState()의 상위 클래스 구현을 호출하여 기본 구현에서 뷰 계층 구조의 상태를 복원할 수 있도록 합니다.

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)
    }
}
profile
'왜?'라는 물음을 해결하며 마지막 개념까지 공부합니다✍

0개의 댓글