활동 소개

Activity 클래스는 Android 앱의 중요한 구성요소로 활동이 실행되고 결합되는 방식은 플랫폼 애플리케이션 모델의 기본 요소입니다. main 메서드를 사용하여 앱을 실행하는 프로그래밍 패러다임과 달리 Android 시스템은 수명 주기의 특정 단계에 해당하는 특정 콜백 메서드를 호출하여 Activity 인스턴스의 코드를 시작합니다.

이 문서에서는 활동의 개념을 소개한 후 활동을 사용한 작업 방법에 관한 간단한 지침을 몇 가지 설명합니다. 앱 아키텍처 권장사항에 관한 자세한 내용은 앱 아키텍처 가이드를 참고하세요.

활동의 개념

모바일 앱 환경은 사용자와 앱의 상호작용이 항상 동일한 위치에서 시작되는 것이 아니라는 점에서 데스크톱 앱 환경과 다릅니다. 대신 사용자 여정은 흔히 비결정론적으로 시작됩니다. 예를 들어 홈 화면에서 이메일 앱을 열면 이메일 목록이 표시될 수 있습니다. 이에 반대로 소셜 미디어 앱을 사용하고 있는 상태에서 이메일 앱을 실행하면 이메일을 작성하기 위한 이메일 앱 화면으로 바로 이동할 수 있습니다.

Activity 클래스는 이 패러다임을 촉진하도록 설계되었습니다. 한 앱이 다른 앱을 호출할 때 호출 앱은 다른 앱을 전체적으로 호출하는 것이 아니라 다른 앱의 활동을 호출합니다. 이런 방식으로 활동은 앱과 사용자의 상호작용을 위한 진입점 역할을 합니다. 활동은 Activity 클래스의 서브클래스로 구현됩니다.

활동은 앱이 UI를 그리는 창을 제공합니다. 이 창은 일반적으로 화면을 채우지만 화면보다 작고 다른 창 위에 떠 있을 수 있습니다.

일반적으로 앱에서 하나의 활동이 기본 활동으로 지정되며 이 기본 활동은 사용자가 앱을 실행할 때 표시되는 첫 번째 화면입니다. 최신 Compose 앱에서는 뷰 계층 구조를 소유하는 대신 단일 활동 아키텍처에서 컴포저블을 호스팅하므로 이 활동만 필요합니다. 앱에 화면용 활동이 여러 개 있는 대신 활동 호스트의 컴포저블이 여러 탐색 대상을 갖습니다.

앱에서 활동을 사용하려면 앱의 매니페스트에 활동 관련 정보를 등록해야 하며 활동 수명 주기를 파악하는 것이 좋습니다. 이 문서의 나머지 부분에서는 이러한 주제를 소개합니다.

manifest 구성

앱에서 활동을 사용하려면 매니페스트에 활동 및 관련된 특정 속성을 선언해야 합니다.

활동 선언

활동을 선언하려면 매니페스트 파일을 열고 <activity> 요소를 <application> 요소의 하위 요소로 추가해야 합니다. 예를 들면 다음과 같습니다.

<manifest ... >
  <application ... >
      <activity android:name=".ExampleActivity" />
      ...
  </application ... >
  ...
</manifest >

이 요소의 유일한 필수 속성은 활동의 클래스 이름을 지정하는 android:name입니다. 또한 라벨, 아이콘 또는 UI 테마와 같은 활동 특성을 정의하는 속성도 추가할 수 있습니다. 이러한 속성 및 기타 속성에 관한 자세한 내용은 <activity> 요소 참조 문서를 참고하세요.

인텐트 필터 선언

인텐트 필터는 Android 플랫폼의 매우 강력한 기능입니다. 인텐트 필터는 명시적 요청뿐만 아니라 암시적 요청을 기반으로도 활동을 실행하는 기능을 제공합니다. 예를 들어 명시적 요청은 'Gmail 앱에서 이메일 보내기 활동을 시작'하도록 시스템에 지시할 수 있습니다. 이와 반대로 암시적 요청은 '작업을 할 수 있는 Activity로 이메일 보내기 화면을 시작'하도록 시스템에 지시합니다. 시스템 UI에서 사용자에게 작업을 실행할 때 어떤 앱을 사용할지 묻는 메시지가 표시되면 바로 인텐트 필터가 작동한 것입니다.

<activity> 요소에서 <intent-filter> 속성을 선언하여 이 기능을 활용할 수 있습니다. 이 요소의 정의에는 <action> 요소와 선택적으로 <category> 요소 또는 <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>
   <intent-filter>
        <action android:name="android.intent.action.SENDTO" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:scheme="mailto" />
    </intent-filter>
</activity>

이 예에서 <action> 요소는 이 활동이 데이터를 전송하도록 지정합니다. <category> 요소를 DEFAULT로 선언하면 활동이 실행 요청을 수신할 수 있습니다. <data> 요소는 이 활동이 전송할 수 있는 데이터 유형을 지정합니다. 다음 코드 스니펫은 위에 설명된 활동을 호출하여 이메일을 작성하는 방법을 보여줍니다.

fun composeEmail(addresses: Array<String>, subject: String) {
    val intent = Intent(Intent.ACTION_SENDTO).apply {
        data = Uri.parse("mailto:") // Only email apps handle this.
        putExtra(Intent.EXTRA_EMAIL, addresses)
        putExtra(Intent.EXTRA_SUBJECT, subject)
    }
    if (intent.resolveActivity(packageManager) != null) {
        startActivity(intent)
    }
}

앱이 독립적인 상태를 유지하도록 만들고 다른 앱이 앱 활동을 활성화하지 못하게 하려면 다른 어떤 인텐트 필터도 필요하지 않습니다. 다른 애플리케이션에서 사용하지 못하게 하려는 활동에는 인텐트 필터가 없어야 하며, 개발자는 명시적 인텐트를 사용하여 활동을 직접 시작할 수 있습니다. 활동이 인텐트에 어떻게 응답할 수 있는지에 관한 자세한 내용은 인텐트 및 인텐트 필터를 참고하세요.

수신 인텐트 처리

다음 예는 단일 텍스트 공유, 단일 이미지, 다중 이미지 배열 등 여러 인텐트 유형을 처리하면서 활동 수명 주기를 관리하는 패턴을 보여줍니다. 다양한 입력을 중앙 집중식 handleIntent 함수를 통해 라우팅하면 ACTION_SENDACTION_SEND_MULTIPLE 작업이 모두 올바르게 파싱되고 반응형 UI 업데이트를 위해 ViewModel에 위임됩니다.

class ExampleActivity : ComponentActivity() {
  private val viewModel: MyViewModel by viewModels()

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    handleIntent(intent)
    setContent {
      ComposeApp(viewModel)
    }
  }

  override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    setIntent(intent)
    handleIntent(intent)
  }

  private fun handleIntent(intent: Intent?) {
    when (intent?.action) {
      Intent.ACTION_SEND -> {
        if ("text/plain" == intent.type) {
          intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
            viewModel.handleText(it) // Update UI to reflect text being shared
          }
        } else if (intent.type?.startsWith("image/") == true) {
          (intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java))?.let {
            viewModel.handleImage(it) // Update UI to reflect image being shared
          }
        }
      }

      Intent.ACTION_SEND_MULTIPLE -> {
          if (intent.type?.startsWith("image/") == true) {
              intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri::class.java)?.let {
                  viewModel.handleMultipleImages(it) // Update UI to reflect multiple images being shared
              }
          } else {
              // Handle other types
          }
      }

      else -> {
          // Handle other intents
      }
    }
  }
}

권한 선언

매니페스트의 <activity> 태그를 사용하여 특정 활동을 시작할 수 있는 앱을 제어할 수 있습니다. 상위 활동과 하위 활동이 모두 각 매니페스트에서 동일한 권한을 가지고 있지 않다면 상위 활동이 하위 활동을 실행할 수 없습니다. 상위 활동에서 <uses-permission> 요소를 선언할 때에는 각 하위 활동에 일치하는 <uses-permission> 요소가 있어야 합니다.

예를 들어 앱에서 SocialApp이라는 가상의 앱을 사용하여 소셜 미디어의 게시물을 공유하려면 다음과 같이 게시물을 호출하는 앱이 보유해야 하는 권한을 SocialApp 자체에서 정의해야 합니다.

<manifest>
<activity android:name="...."
   android:permission="com.google.socialapp.permission.SHARE_POST"

/>

그러면 SocialApp을 호출하려면 앱의 권한 세트가 SocialApp의 매니페스트에 설정된 권한 세트와 일치해야 합니다.

<manifest>
   <uses-permission android:name="com.google.socialapp.permission.SHARE_POST" />
</manifest>

권한 및 보안에 관한 전반적인 내용은 보안 체크리스트를 참고하세요.

활동 수명 주기 관리

활동은 수명 주기 전체 기간에 걸쳐 여러 상태를 거칩니다. 상태 간 전환을 처리하는 데 일련의 콜백을 사용할 수 있습니다. 다음 섹션에서는 이러한 콜백을 소개합니다. Compose 앱에서는 이러한 콜백에 직접 연결하는 것이 권장되지 않습니다. 대신 Lifecycle API를 사용하여 상태 변경을 관찰하세요. 자세한 내용은 Compose로 수명 주기 통합을 참고하세요.

onCreate

시스템이 활동을 생성할 때 실행되는 이 콜백을 구현해야 합니다. 구현 시 활동의 필수 구성요소를 초기화해야 합니다. 예를 들어 앱은 여기에서 뷰를 생성하고 데이터를 목록에 결합해야 합니다.

Compose 앱에서는 아래와 같이 이 콜백을 사용하여 setContent를 통해 호스트 컴포저블을 설정합니다.

class MyActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text(text = stringResource(id = R.string.greeting))
        }
    }
}

onCreate이 완료되면 다음 콜백은 항상 onStart입니다.

onStart

onCreate가 종료되면 활동은 '시작됨' 상태로 전환되고 활동이 사용자에게 표시됩니다. 이 콜백에는 활동이 포그라운드로 나와서 대화형이 되기 위한 최종 준비에 준하는 작업이 포함됩니다.

onResume

활동이 사용자와 상호작용을 시작하기 직전에 시스템은 이 콜백을 호출합니다. 이 시점에서 활동은 액티비티 스택의 맨 위에 있으며 모든 사용자 입력을 캡처합니다. 앱의 핵심 기능은 대부분 onResume 메서드로 구현됩니다.

onPause 콜백은 항상 onResume 뒤에 옵니다.

onPause

활동이 포커스를 잃고 '일시중지됨' 상태로 전환될 때 시스템은 onPause를 호출합니다. 예를 들어 이 상태는 사용자가 뒤로 또는 최근 버튼을 탭할 때 발생합니다. 시스템이 활동에서 onPause를 호출할 때 이는 엄밀히 말하면 활동이 여전히 부분적으로 표시되지만 대체로 사용자가 활동을 떠나고 있으며 활동이 조만간 '중지됨' 또는 '다시 시작됨' 상태로 전환됨을 나타냅니다.

사용자가 UI 업데이트를 기다리고 있다면 '일시중지됨' 상태의 활동은 계속 UI를 업데이트할 수 있습니다. 이러한 활동의 예에는 내비게이션 지도 화면 또는 미디어 플레이어 재생을 표시하는 활동이 포함됩니다. 이러한 활동이 포커스를 잃더라도 사용자는 UI가 계속 업데이트될 것으로 예상합니다.

애플리케이션 또는 사용자 데이터를 저장하거나, 네트워크 호출을 하거나, 데이터베이스 트랜잭션을 실행하는 데 onPause를 사용해서는 안 됩니다. 데이터 저장에 관한 자세한 내용은 임시 UI 상태 저장 및 복원을 참고하세요.

onPause 실행이 완료되면 다음 콜백은 활동이 '일시중지됨' 상태로 전환된 후 발생하는 상황에 따라 onStop 또는 onResume입니다.

onStop

활동이 사용자에게 더 이상 표시되지 않을 때 시스템은 onStop을 호출합니다. 이는 활동이 제거 중이거나 새 활동이 시작 중이거나 기존 활동이 '다시 시작됨' 상태로 전환 중이고 중지된 활동을 다루고 있기 때문에 발생할 수 있습니다. 이 모든 상황에서 중지된 활동은 더 이상 표시되지 않습니다.

시스템이 호출하는 다음 콜백은 활동이 사용자와 상호작용하기 위해 다시 시작되면 onRestart이며 이 활동이 완전히 종료되면 onDestroy입니다.

onRestart

'중지됨' 상태의 활동이 다시 시작되려고 할 때 시스템은 이 콜백을 호출합니다. onRestart는 활동이 중지된 시간부터 활동 상태를 복원합니다.

이 콜백 뒤에 항상 onStart가 옵니다.

onDestroy

시스템은 활동이 소멸되기 전에 이 콜백을 호출합니다.

이 콜백은 활동이 수신하는 마지막 콜백입니다. onDestroy는 일반적으로 활동 또는 활동이 포함된 프로세스가 제거될 때 활동의 모든 리소스를 해제하도록 구현됩니다.

이 섹션에서는 이 주제에 관한 소개만 제공합니다. 활동 수명 주기 및 관련 콜백에 관한 자세한 내용은 활동 수명 주기를 참고하세요.