외로운 Nova의 작업실

안드로이드 앱 프로그래밍 - 21(인텐트 이해하기) 본문

Programming/Kotlin - Android

안드로이드 앱 프로그래밍 - 21(인텐트 이해하기)

Nova_ 2023. 2. 7. 15:42

- 인텐트

인텐트란 한마디로 컴포넌트를 실행하려고 시스템에 전달하는 메시지라고 정의할 수 있습니다. 즉, 기능을 수행하는 함수를 제공하는 클래스가 아니라 데이터를 담는 클래스입니다. 이 인텐트 데이터는 컴포넌트를 실행하는 정보이며 이 정보가 담긴 인텐트 객체를 시스템에 전달하며 컴포넌트가 실행됩니다. 

 

<예시>

인텐트가 무엇인지 알아보고자 한 앱에 MainActivity와 DetailActivity거 있다고 가정해봅시다. MainActivity가 실행되고나서 DetailActivity로 화면을 전환한다면 DetailActivity 클래스의 객체를 생성해서 실행하면 되지만 만약 DetailActivity가 안드로이드의 컴포넌트 클래스라면 개발자가 코드에서 직접 생성해서 실행할 수 없습니다. 이때는 시스템에게 DetailActivity를 실행해달라고 부탁해야합니다. 부탁할때 실행할 컴포넌트의 정보를 담는 곳이 바로 인텐트입니다. 시스템은 이 인텐트 정보를 분석해서 그에 맞는 컴포넌트를 실행해줍니다. 이러한 기능은 같은 앱의 컴포넌트 뿐만아니라 외부 앱의 컴포넌트와 연동할때도 마찬가지입니다.

 

<컴포넌트 등록>

이 앱에 어떤 컴포넌트가 있는지 시스템에게 알려주기위해 메니페스트 파일에 컴포넌트를 등록시켜줘야합니다. 아래는 메니페스트파일에 MainActivity와 DetailAtivity를 등록한 메니페스트파일 코드입니다.

<activity android:name=".DetailActivity"></activity>
        <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>

 

<인텐트를 시스템에 전달>

등록을 시켜줬다면 아래와 같은 코드로 인텐트를 시스템에 전달할 수 있습니다. 

val intent: Intent = Intent(this, DetailActivity::class.java)
startActivity(intent)

 

<실습>

메인 액티비티에서 버튼을 누르면 DetailActivity로 넘어가는 실습을 해보도록 하겠습니다. 아래는 메인 액티비티 코드입니다.

package com.example.undersatnd_intetn

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.undersatnd_intetn.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        var Binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(Binding.root)

        Binding.ChangeActivityBtn.setOnClickListener(){
            val intent: Intent = Intent(this, DetailActivity::class.java)
            startActivity(intent)
        }
    }
}

아래는 메인 액티비티.xml 코드입니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/Change_Activity_Btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Main"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

아래는 DetailActivity 코드입니다.

package com.example.undersatnd_intetn

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.undersatnd_intetn.databinding.ActivityDetailBinding

class DetailActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val Binding = ActivityDetailBinding.inflate(layoutInflater)
        setContentView(Binding.root)
    }
}

아래는 DetailActivity.xml 코드입니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Detail"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

아래는 실행화면입니다. Main 버튼을 누르면 Detail 버튼이 있는 액티비티로 넘어갑니다.

 

- 인텐트 엑스트라 데이터

그런데 MainActivity에서 인텐트를 이용해 DetailActivity를 실행할때 데이터를 전달해야한다면 어떻게 해야할까요? 인텐트에 엑스트라 데이터를 넣어서 함께 시스템에 전달해야합니다. 인텐트에 엑스트라 데이터를 추가하는 함수는 putExtra() 함수입니다. putExtra() 함수의 첫번째 매개변수는 데이터의 식별자이며 두 번째 매개변수는 전달할 데이터입니다. 아래는 메인액티비티.kt 안에서의 예시입니다.

Binding.ChangeActivityBtn.setOnClickListener(){
            val intent: Intent = Intent(this, DetailActivity::class.java)
            intent.putExtra("data1", "hello")
            intent.putExtra("data2", 10)
            startActivity(intent)
        }

 인텐트로 실행된 컴포넌트에서 엑스트라 데이터를 가져오려면 getIntent() 함수로 자신을 실행한 인텐트 객체를 얻어야합니다. 이후 데이터 타입에따라 데이터를 가져옵니다. 아래는 데이터 타입에따른 함수입니다.

  • getIntExtra(String name, int defaultValue)
  • getStringExtra(String name)
  • getDoubleExtra(String name, double defalutValue)

실제 MainActivity에서 넘겨준 값을 DetailActivity에서 받아 버튼의 text를 변경해보는 코드를 짜보겠습니다.

class DetailActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val Binding = ActivityDetailBinding.inflate(layoutInflater)
        setContentView(Binding.root)

        val intent = intent
        val data1 = intent.getStringExtra("data1")
        val data2 = intent.getIntExtra("data2", 0)

        Binding.button.text = data1
    }
}

한번 실행해보겠습니다.

 

- 액티비티 화면 되돌리기

액티비티는 화면을 구성하는 컴포넌트입니다. 따라서 한 액티비티에서 다른 액티비티를 인텐트로 실행하면 화면이 전환됩니다. 이때 의도에따라 화면을 되돌리거나 되돌리지 않을 수 있습니다. 되돌리는 기능을 사용할때는 보통 휴대폰인증에 사용됩니다. 원래 앱에서 휴대폰인증을 하기위해 PASS 앱의 액티비티를 실행시키고 그에따른 결과값을 다시 원래앱으로 받아서 처리하는 기능을 구현할 수 있습니다. 되돌림에따른 함수들은 아래와 같습니다.

  • startActivity(Intent intent) : 화면을 되돌릴 필요가 없을때 사용합니다.
  • startActivityForResult(Intent intent, int requerstCode) : 화면 되돌려야할때 사용합니다.

하지만 이제 startActivityForResult는 안정성이 떨어져 더이상 사용하지 않습니다. 이제는 registerForActivityResult함수로 콜백함수를 정의하고 이 함수의 반환값으로 launch() 함수로 intent를 실행하여 intent가 반환될때 콜백함수를 실행합니다. 이를 활용하여 DetailActivity에서 world를 액스트라 데이터로 전해주고 MainActivity의 버튼 텍스트를 Main에서 world로 변경시켜보겠습니다. 먼저 MainActivity.kt 코드입니다.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        var Binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(Binding.root)

        //콜백함수 등록
        val getResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){
            if(it.resultCode == RESULT_OK){
                val returnIntent = intent
                val data1 = it.data?.getStringExtra("data1")
                Binding.ChangeActivityBtn.text = data1
            }
        }

        //콜백함수를 이용해서 인텐트 전달
        Binding.ChangeActivityBtn.setOnClickListener(){
            val intent: Intent = Intent(this, DetailActivity::class.java)
            intent.putExtra("data1", "hello")
            intent.putExtra("data2", 10)
            getResult.launch(intent)
        }
    }
}

아래는 DetailActivity.kt 코드입니다.

class DetailActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val Binding = ActivityDetailBinding.inflate(layoutInflater)
        setContentView(Binding.root)

        val intent = intent
        val data1 = intent.getStringExtra("data1")
        val data2 = intent.getIntExtra("data2", 0)

        Binding.button.text = data1

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


    }
}

실행시켜보겠습니다.

 

- 인텐트 필터

지금까지 DetailActiviy 컴포넌트 정보를 인텐트에 담을때 다음과 같이 지정했습니다.

val intent: Intent = Intent(this, DetailActivity::class.java)

하지만 이는 내부의 앱 컴포넌트만이 저렇게 사용할 수 있습니다. 이를 명시적 인텐트라고 합니다. 따라서 외부 컴포넌트를 실행할때는 Activity의 이름이아닌 action, data, category등의 속성을 이용하여 외부 컴포넌트를 구별합니다. 이를 암시적 인텐트라고합니다. 먼저 DetailActivity를 외부에서도 사용할 수 있게 선언해보겠습니다.

<activity android:name=".DetailActivity"></activity>
        <activity android:name=".implicitActivity" android:exported = "true">
            <intent-filter>
                <action android:name="ACTION_EDIT"/>
            </intent-filter>
        </activity>
        <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>

외부에서는 action 속성이 ACTION_EDIT인 컴포넌트로 접근할 수 있습니다. 인텐트 필터의 하위 에는 action, category, data태그를 이용해서 정보를 설정할 수 있습니다. 각 태그에 설정하는 값은 다음과 같은 의미입니다.

  • <action> : 컴포넌트의 기능을 나타내는 문자열입니다. 컴포넌트의 기능을 나타낼것을 권장하고 있습니다.
  • <category> : 컴포넌트가 포함되는 범주를 나타내는 문자열입니다. 개발자가 임의로 정할 수 있지만 대부분 플랫폼 API에서 제공하는 문자열을 이용합니다.
  • <data> : 컴포넌트에서 어떤 성격의 데이터를 처리하는지를 나타냅니다. android:sheme, android:host, android:port, android:mimeType등의 속성을 이용합니다.

만약 다른 앱에서 implicit액티비티를 실행하려고한다면 아래와 같이 인텐트를 코딩해야합니다.

val intent = Intet()
intent.action = "ACTION_EDIT"
intent.data = Uri.parse("understand_intetn.example.com")
startActivity(intent)

 

- 액티비티 인텐트 동작 방식

시스템은 컴포넌트를 action, category, data등의 문자열 정보로 찾기때문에 없거나 여러개일 수 있습니다. 만약 없다면 오류가 발생하여 try catch문으로 예외처리를 해줘야합니다. 또한 여러개라면 아래와 같이 선택 다이얼로그가 뜹니다.

만약 한개라면 정상적으로 실행이 됩니다.

 

Comments