액티비티 컴포넌트

이윤설·2024년 8월 30일
0

액티비티는 안드로이드 앱에서 화면을 구성하는 컴포넌트로, 이미 알아보았었다.
하지만 액티비티를 제대로 이해하려면 화면 구성 방법 이외에 동작 방식 등을 구체적으로 알아야 한다.

1. 인텐트 이해하기

안드로이드 앱은 모두 4개의 컴포넌트로 개발하는데, 이때 핵심 클래스가 바로 Intent다.
인텐트는 하나의 액티비티가 다른 액티비티를 론칭(시작)시킬 수 있는 매커니즘이다.
또는 인텐트는 '컴포넌트를 실행하려고 시스템에 전달하는 메시지'라고 할 수 있다.

예를 들어, 한 앱에 MainActivity와 DetailActivity가 있다고 가정하자.
MainActivity가 실행되고 나서 DetailActivity로 화면을 전환한다면 DetailActivity의 객체를 생성해서 실행하면 될 것이다.

하지만 DetailActivity가 만약 안드로이드의 컴포넌트 클래스라면, 개발자가 코드에서 직접 생성해서 실행할 수 없다. 왜냐하면 컴포넌트 클래스는 시스템이 생성해서 실행하는 클래스이므로, 개발자가 작성하는 코드로 생명주기를 관리할 수 없기 때문이다.

결국 MainActivity 클래스에서 DetailActivity 클래스를 실행하려면 시스템에 인텐트를 전달해 줘야 한다. 그러면 시스템에서 인텐트의 정보를 분석해서 그에 맞는 컴포넌트를 실행해 준다.

이러한 인텐트의 중재 역할을 같은 앱의 컴포넌트 뿐만 아니라 외부 앱의 컴포넌트와 연동할 때도 마찬가지다.

Intent는 앱 내의 다양한 컴포넌트(액티비티, 서비스, 브로드캐스트 리시버 등) 간의 통신을 가능하게 한다. Intent는 두 가지 주요 유형으로 나뉜다.

Intent의 주요 역할

  • 컴포넌트 간의 통신: Intent는 Android의 컴포넌트들(예: 액티비티, 서비스, 브로드캐스트 리시버)이 서로 상호작용할 수 있게 한다. 예를 들어, 한 액티비티에서 다른 액티비티를 시작하거나, 서비스를 시작하는 데 사용된다.

  • 데이터 전달: Intent를 통해 한 컴포넌트에서 다른 컴포넌트로 데이터를 전달할 수 있다.
    예를 들어, 사용자 입력 데이터를 한 액티비티에서 다른 액티비티로 전달할 때 Intent를 사용한다.

  • 시스템 기능 요청: 시스템에서 제공하는 기능을 사용할 수 있다. 예를 들어, 웹 페이지 열기, 연락처 앱 실행, 사진 촬영 등의 작업을 수행할 수 있다.

Intent의 유형

Explicit Intent (명시적 인텐트)

  • 명시적 Intent는 특정한 컴포넌트(액티비티, 서비스 등)를 직접 지정하여 호출하는 인텐트입니다.
  • 주로 같은 앱 내에서 컴포넌트 간의 통신에 사용된다.
  • 컴포넌트 이름을 정확하게 지정하기 때문에 어떤 컴포넌트를 호출할지 명확히 지정한다.

사용 예시:

  • 한 액티비티에서 다른 액티비티로 이동할 때.
  • 서비스 시작이나 특정 브로드캐스트 리시버 호출 시.

Implicit Intent (암시적 인텐트)

  • 암시적 Intent는 특정 컴포넌트를 지정하지 않고, 수행하고자 하는 작업의 유형을 지정하여 시스템이 해당 작업을 처리할 수 있는 컴포넌트를 선택하도록 한다.
  • 주로 다른 앱이나 시스템 기능과 상호작용할 때 사용된다.
  • 필터(예: 액션, 데이터 타입, 카테고리)를 설정하여 특정 작업을 처리할 수 있는 모든 앱을 호출할 수 있다.

사용 예시:

  • 웹 페이지 열기, 이메일 전송, 전화를 걸기 등.
  • 외부 앱이나 다른 앱의 기능 호출 시.

Intent의 구성 요소

  • Action: 수행하려는 작업의 타입을 정의한다. 예를 들어, Intent.ACTION_VIEW, Intent.ACTION_SEND 등이 있다.
  • Data: 작업에 필요한 데이터를 전달한다. 예를 들어, 웹 페이지를 열 때 URI를 전달할 수 있다.
  • Category: 인텐트를 처리할 수 있는 컴포넌트의 범주를 지정한다. 일반적으로 Intent.CATEGORY_DEFAULT를 사용한다.
  • Extras: 추가 데이터를 키-값 쌍으로 전달한다. 예를 들어, 텍스트 메시지, 숫자, 배열 등을 전달할 수 있다.
  • Flags: 인텐트의 동작 방식을 제어하는 플래그를 설정한다. 예를 들어, 새로운 액티비티를 시작할 때 기존의 스택을 유지할지 초기화할지를 결정하는 플래그가 있다.

인텐트 필터 (Intent Filters)

인텐트 필터는 컴포넌트가 처리할 수 있는 인텐트의 종류를 정의한다.
필터는 AndroidManifest.xml 파일에 정의되며, 컴포넌트가 어떤 인텐트를 받을 수 있는지를 명시한다. 인텐트 필터를 사용하여 컴포넌트가 특정 작업이나 데이터를 처리할 수 있는지 여부를 결정할 수 있다.

인텐트 사용 사례

  • 액티비티 전환: 한 액티비티에서 다른 액티비티로 이동.
  • 데이터 전달: 두 액티비티 간에 데이터 전달 (예: 사용자 입력 데이터).
  • 서비스 시작: 백그라운드 작업을 수행하기 위해 서비스 시작.
  • 외부 앱 호출: 브라우저에서 웹 페이지 열기, 전화 앱에서 전화 걸기.
  • 브로드캐스트 전송: 시스템이나 앱 내에서 특정 이벤트가 발생했음을 알리기 위해 브로드캐스트 전송.

인텐트를 시스템에 전달하는 방법

        <activity
            android:name=".DetailActivity"
            android:exported="true" />
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
  • 액티비티는 매니페스트 파일에 등록해야 한다.
  • 액티비티 뿐만 아니라 서비스, 브로드캐스트 리시버, 콘텐츠 프로바이더도 매니페스트 파일에 등록해야 한다. 왜냐하면 시스템에 컴포넌트를 알려야 하기 때문이다.
  • 시스템은 런타임 때 매니페스트 파일의 정보를 참고하여 앱을 실행한다. 만약 어떤 컴포넌트를 개발해 놓고 매니페스트 파일에 등록하지 않으면 시스템은 해당 컴포넌트를 알 수 없다. 따라서 인텐트가 해당 컴포넌트를 실행할 수 없다.
val intent: Intent = Intent(this, DetailActivity::class.java)
startActivity(intent)

이 코드는 안드로이드 앱에서 현재 화면에서 다른 화면(액티비티)으로 이동하는 기본적인 방법을 보여준다.

  1. Intent 객체 생성:

    val intent: Intent = Intent(this, DetailActivity::class.java)
    • Intent: 다른 화면(액티비티), 서비스, 또는 다른 작업을 요청할 때 사용하는 Android의 기본 클래스다.
    • this: 현재 화면(액티비티)을 가리킨다. 이 코드를 작성하고 있는 액티비티의 인스턴스를 가리킨다.
    • DetailActivity::class.java: 이동하고자 하는 대상 화면(액티비티)의 클래스다. 여기서는 DetailActivity라는 이름의 액티비티로 이동하겠다는 의미다.

    이 줄의 코드는 현재 액티비티(this)에서 DetailActivity로 이동하겠다는 의도를 가진 Intent 객체를 만든다.

  2. 새로운 화면 시작:

    startActivity(intent)
    • startActivity(intent): 방금 생성한 Intent 객체를 사용해서 DetailActivity를 시작한다.
    • 이 코드를 실행하면 현재 화면에서 DetailActivity라는 새 화면으로 이동한다.

쉽게 비유하자면:

  • Intent택시를 호출하는 것과 비슷하다. 여기서 택시를 부르는 명령이 Intent 객체를 만드는 것이고, 택시의 목적지가 DetailActivity이다.
  • startActivity(intent)는 택시에 탑승하여 목적지(DetailActivity)이동하는 것과 같다.

인텐트 엑스트라 데이터

그런데 MainActivity에서 인텐트를 이용해 DetailActivity를 실행할 때 데이터를 전달해야 한다면, 어떠한 방법을 사용할까?

인텐트에 컴포넌트 실행을 요청할 때 데이터를 함께 전달하려면 EXTRA DATA를 이용해야 한다.
엑스트라 데이터란, 인텐트에 담는 부가정보이다.

// 엑스트라 데이터 추가
val intent: Intent = Intent(this, DetailActivity::class.java)
intent.putExtra("data1", "hello")
intent.putExtra("data2", 10)
startActivity(intent)

// 엑스트라 데이터 가져오기
val data1 = intent.getStringExtra("data1")
val data2 = intent.getIntExtra("data2", 0)

엑스트라 데이터를 추가하거나 가져올땐 위와 같이 작성하면 된다.

액티비티 화면 되돌리기 - ActivityResultLauncher

액티비티는 화면을 구성하는 컴포넌트이다. 따라서 한 액티비티에서 인텐트로 다른 액티비티를 실행하면 화면이 전환된다. 그런데 화면을 전환했다가 다시 돌아왔을 때 사후 처리가 필요한 때가 있고, 불필요한 때가 있다.

  • 사후 처리 O: 카카오톡의 프로필 설정 화면에서 갤러리 앱의 목록을 띄운 후 다시 설정 화면으로 돌아올 때는 갤러리에서 사용자가 선택한 사진을 프로필 사진으로 등록해야 한다.
  • 사후 처리 X: 이메일 앱의 목록 화면에서 상세보기 화면으로 전환했다가 되돌아왔을 때는 다시 목록 화면이 보이면 될 뿐 특별한 처리는 필요 없다.

이처럼 사후 처리 여부에 따라 인텐트로 액티비티를 시작하는 방법은 3가지로 나뉜다.

  • public void startActivity(Intent intent): 사후 처리가 필요 없을 때 사용한다.
  • public void startActivityForResult(Intent intent, int requestCode): 사후 처리가 필요 할 때 사용한다 (but deprecated)
  • ActivityResultLauncher: 사후 처리가 필요할 때 사용한다 (권장)

다음 코드는 액티비티에서 결과를 되돌리는 코드다. 사용자가 뒤로가기 버튼을 누르지않고 자동으로 화면을 되돌릴 때는 finish() 함수를 사용한다.
finish()는 현재 화면에 보이는 액티비티를 종료해 달라고 시스템에 요청한다.

intent.putExtra("resultData", "world")
setResult(RESULT_OK, intent)
finish()

또한 setResult()를 통해 결과를 어떻게 되돌릴지 지정할 수 있다.

  • RESULT_OK: 요청을 제대로 처리함
  • RESULT_CANCELED: 요청을 제대로 처리하지 모함

Contract, ActivityResultLauncher, launch

Contract, ActivityResultLauncher, 그리고 launch는 Android에서 액티비티 간의 데이터를 주고받는 최신 방법인 Activity Result API와 관련된 용어들입니다. 이 API는 이전에 사용되던 startActivityForResult 방식보다 더 간결하고 구조화된 방식으로 액티비티 간의 결과 처리를 관리할 수 있게 해줍니다.

1. Contract

Contract어떤 작업을 요청하고, 그 결과를 어떻게 처리할지 정의한 계약이라고 생각할 수 있다. ActivityResultContract는 일반적으로 두 가지 주요 역할을 한다.

  1. Input 정의: 다른 액티비티를 호출할 때 전달할 입력 데이터를 정의한다.
  2. Output 정의: 호출된 액티비티가 작업을 마치고 돌려줄 결과 데이터를 정의한다.

Android는 몇 가지 기본적인 ActivityResultContract 클래스를 제공하며,
이를 사용하면 파일 선택, 사진 촬영, 권한 요청 등 다양한 작업을 수행할 수 있다.
또한, 개발자가 필요에 따라 사용자 정의 계약을 만들 수도 있다.

예를 들어:

  • ActivityResultContracts.StartActivityForResult: 다른 액티비티를 호출하고 결과를 받아오는 계약.
  • ActivityResultContracts.RequestPermission: 특정 권한을 요청하고, 그 결과를 받아오는 계약.

2. ActivityResultLauncher

ActivityResultLauncherContract에 따라 액티비티를 시작하고 그 결과를 처리할 수 있게 하는 객체이다. 이 객체는 Contract와 연결되며, launch 메서드를 통해 특정 작업을 시작할 수 있다.

  • 생성 방법: ActivityResultLauncherregisterForActivityResult 메서드를 통해 생성된다. 이 메서드는 두 가지를 인수로 받는다:
    1. ActivityResultContract: 어떤 작업을 수행할지 정의한다.
    2. 결과를 처리할 콜백 함수: 작업이 완료되었을 때 그 결과를 처리하는 코드를 정의한다.

3. launch

launchActivityResultLauncher를 사용하여 Contract에 정의된 작업을 시작하는 메서드다. launch를 호출할 때, Contract에서 정의된 인풋 데이터를 전달할 수 있다.

전체적인 흐름

  1. Contract 설정:

    • Contract를 정의하거나 기본 제공되는 ActivityResultContract를 사용하여, 어떤 작업을 요청할지와 그 결과가 무엇인지 정의한다.
  2. ActivityResultLauncher 등록:

    • registerForActivityResult를 사용하여 ActivityResultLauncher를 등록하고, Contract와 결과를 처리할 콜백을 설정한다.
  3. launch 호출:

    • 특정 이벤트(예: 버튼 클릭)에서 launch를 호출하여 작업을 시작하고, 필요한 인풋 데이터를 전달한다.

예시

// 1. Contract 설정 및 ActivityResultLauncher 등록
val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
    // 2. 결과 처리 (예: 선택한 이미지 URI 처리)
    imageView.setImageURI(uri)
}

// 3. launch 호출 (예: 버튼 클릭 시)
button.setOnClickListener {
    getContent.launch("image/*") // 이미지 파일 선택
}
  • ActivityResultContracts.GetContent(): 사용자가 콘텐츠를 선택할 수 있는 기본 계약(예: 이미지 선택).
  • getContent.launch("image/*"): 특정 MIME 타입의 콘텐츠를 선택하도록 요청. 여기서는 이미지 파일 선택.

요약

  • Contract로 주고받을 데이터의 형식을 정의하고,
    ActivityResultLauncher로 이 과정을 준비한 다음,
    launch로 실제 데이터를 보내고 결과를 받아온다.

  • Contract, ActivityResultLauncher, launch 이 세 가지를 합치면 결국 큰 하나의 데이터 전달 과정이 된다.

profile
화려한 외면이 아닌 단단한 내면

0개의 댓글