Android 액티비티와 인텐트

timothy jeong·2021년 11월 12일
0

Android with Kotlin

목록 보기
36/69

액티비티 화면 되돌리기

액티비티는 화면을 구성하는 컴포넌트이다. 따라서 한 액티비티에서 인텐트로 다른 액티비티를 실행하면 화면이 전환된다. 이때 고려할 사항이 있다. 어떤 액티비티가 다른 액티비티를 실행해 화면이 전환되었을 때 의도에 따라 화면을 되돌리거라 되돌리지 않을 수도 있다. 이런 상황을 고려해 액티비티를 시작하는 함수는 2개이다.

// 화면을 되돌릴 필요가 없을때
public void startActivity(Intent intent)

액티비티를 이동시키고 해당 액티비티에서 결과를 되돌려 받아야 하는 경우는 다양하다. 대표적으로 앱에서 카메라 앱을 시작하고 그 결과로 캡처된 사진을 받거나, 사용자가 연락처를 선택하도록 연락처 앱을 시작하고 그 결과로 연락처 세부정보를 수신할 수 있다.

결과를 되돌려 받기 위해해서는 결과를 돌려받을 액티비티와, 결과를 돌려줄 액티비티 두개가 준비되어 있어야하며, 결과를 돌려받을 액티비티에서 ActivityResultLauncher<Intent> 클래스를 이용하여 구현해야 할 사항이 있다.

결과를 되돌려 받을 액티비티

ComponentActivity 또는 Fragment에 있을 때, Activity Result API에서 제공하는 registerForActivityResult() API를 통해 결과 콜백을 등록할 수 있다.
코드 상에서는 registerForActivityResult( ActivityResultContracts.StartActivityForResult()) {} 을 구현하여 콜백이 동작하는 방식이다.

class MainActivity : AppCompatActivity() {
    lateinit var binding: ActivityMainBinding
    lateinit var resultLauncher : ActivityResultLauncher<Intent>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        resultLauncher = registerForActivityResult(
            ActivityResultContracts.StartActivityForResult()) {
            Log.d("Info", "resultCode : ${it.resultCode}")
            if (it.resultCode == RESULT_OK) {

                val resultStr = it.data!!.getStringExtra("data")
                Log.d("Info ", "the result = $resultStr")
                binding.TextView.text = resultStr
            }
        }

        binding.Button.setOnClickListener {
            val intent = Intent(this, OtherActivity::class.java)
            resultLauncher.launch(intent)
        }
    }
}

결과를 돌려줄 액티비티

class OtherActivity: AppCompatActivity() {
    lateinit var binding: ActivityOtherBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityOtherBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.btn1.setOnClickListener {
            intent.putExtra("data", binding.btn1.text)
            setResult(RESULT_OK, intent)
            finish()
        }

        binding.btn2.setOnClickListener {
            intent.putExtra("data", binding.btn2.text)
            setResult(RESULT_OK, intent)
            finish()
        }

        binding.btn3.setOnClickListener {
            intent.putExtra("data", binding.btn3.text)
            setResult(RESULT_OK, intent)
            finish()
        }
    }
}

인텐트 필터 활용하기

아래와 같이 인텐트 필터가 정의된 액티비티 컴포넌트가 있다고 할때, 외부 앱에서 이 컴포넌트를 인텐트에 담을 수 있는 방법은 무엇일까?

<activity android:name=".TwoActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="ACTION_EDIT"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:scheme="http"/>
            </intent-filter>
        </activity>

name 과 data 를 담아서 인텐트를 만들면 된다!

val intent = intent()
intent.action = "ACTION_EDIT"
intent.data = Uri.parse("http://www.google.com")
startActivity(intent)

// 생성자에서 처리할 수도 있다.
val intent = intent("ACTION_EDIT", Uri.parse("http://www.google.com")
startActivity(intent)

위의 코드에서는 카테고리 정보를 전달하지 않았는데 이는 인텐트 필터의 카테고리가 android.intent.category.DEFAULT 로 설정되었기 때문이다. 인텐트 필터를 참조하여 인텐트를 만들때 카테고리 정보를 전달하지 않으면 android.intent.category.DEFAULT 로 설정되어 생성한다. 그리고 anodrid:mimeType="image/*" 가 설정되어 있다면 이 또한 인텐트 생성에서 고려해줘야 한다.

액티비티 인텐트 동작 방식

만약 실행할 액티비티가 시스템에 없거나, 실행할 액티비티가 1개 이상이라면 어떻게 될까?

  • 없을때 : 인텐트를 시작한 곳에 오류 발생
  • 1개일 때 : 문제 없이 실행
  • n개일 때 : 사용자 선택으로 하나만 실행한다.

인텐트가 없을 때는 android.content.ActivityNotFoundException 이 발생한다. 따라서 인텐트를 이용해서 액티비티를 시작할때는 try~catch 를 이용해서 예외 처리도 해주는게 좋다.

n개일 때는 사용자 화면에 다이얼로그가 나오고, 사용자가 이를 선택해서 이용하게 된다. 만약 액티비티가 여러개더라도 특정 앱의 액티비티를 실행하고 싶다면 해당 앱의 패키지 명을 지정하면 된다.

val intent = intent(Intent.ACTION_VIEW, Uri.parse("geo:37.7749, 127.4194"))
intent.setPackage("com.google.android.apps.maps")
startActivity(intent)

패키지 공개 상태

안드로이드 11(API 30) 버전부터는 앱의 패키지 공개 상태를 지정하지 않으면 외부 앱의 패키지 정보에 접근할 수 없게 되었다. 이 내용은 인테트를 이용해 외부 앱과 연동하는 부분에는 영향이 없으며, 외부 앱을 연동하더라도 패키지 정보를 활용하지 않는다면 아무런 문제가 없다. 다만, 아래의 함수를 사용할 때는 패키지 공새 상태에 따라 영향을 받는다.

  • PackageManager.getPackageInfo()
  • PackageManager.queryIntentActivities()
  • PackageManager.getInstalledPackages()
  • PackageManager.getInstalledApplications()
  • Intent.resolveActivity()
  • bindService()

이러한 함수들을 오류없이 실행하려면 메니페스트 파일에 외부 앱의 정보에 접근하겠다고 선언해 줘야 한다.

<manifest ... >
  <queries>
    <package android:name="com.example.test_outter" />
  </queries>
  ....
</manigest>

여러 앱에 접근할 때는 package 태그를 여러 번 선언하면 되는데, queries 태그를 쓰지 않고 모든 외부 앱의 정보에 접근할 수 있도록 허용해달라는 의미로 아래와 같이 를 선언하면 된다.

<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>

이렇게 선언하고 테스트해도 외부 앱의 정보에 접근할 수 있지만 될 수 있으면 queries 태그를 이용할 것은 권장한다.

profile
개발자

0개의 댓글