[Android] 5(1). 액티비티

10 minute read


액티비티

안드로이드는 4개의 핵심 컴포넌트를 제공합니다. 아래 그림에서 1~4는 컴포넌트, 5와 6은 컴포넌트를 사용하기 위한 도구입니다.

image-20210729225140447

  1. 액티비티: 화면 UI를 담당하는 컴포넌트
  2. 브로드캐스트 리시버: 시스템 또는 사용자가 발생하는 메시지를 수신하는 컴포넌트
  3. 서비스: 백그라운드 코드 처리를 담당하는 컴포넌트(서브 스레드와 비슷한 개념으로 화면이 없는 Activity)
  4. 콘텐트 프로바이더: 앱끼리 데이터를 공유하기 위한 컴포넌트
  5. 인텐트: 1~3 까지 3개의 컴포넌트를 실행하기 위해 시스템에 전달되는 메시지 도구
  6. 콘텐트 리졸버: 콘텐트 프로바이더가 제공하는 데이터를 사용하기 위한 도구


이 중 먼저 액티비티에 대해 살펴봅니다. 액티비티는 사용자가 직접 보고 입력하는 화면을 담당하는 컴포넌트입니다.


컨텍스트


  • 컨텍스트
    • 시스템을 사용하기 위한 정보(프로퍼티)와 도구(메서드)가 담겨 있는 클래스 (액티비티 실행, 서비스 실행, 파일 읽고 쓰기, 권한 등)
    • 대부분의 컨텍스트는 컴포넌트 실행 시 함께 생성되고 생성된 컴포넌트가 가지고 있는 메서드를 호출하여 각각의 도구를 이용
    • 액티비티는 컨텍스트를 상속받았기 때문에 코드상에서 baseContext를 호출하는 것만으로 안드로이드의 기본 기능 사용 가능


  • 컨텍스트의 종류

    1. 애플리케이션 컨텍스트

      애플리케이션의 핵심 기능을 담고 있는 클래스로 앱을 통틀어 단 하나의 인스턴스만 생성. 액티비티나 서비스 같은 컴포넌트에서 applicationContext를 직접 호출해서 사용. (단 하나)

    2. 베이스 컨텍스트

      안드로이드의 4대 메이저 컴포넌트인 액티비티, 서비스, 컨텐트 프로바이더, 브로드캐스트 리시버의 기반 클래스. 각각의 컴포넌트에서 baseContext 또는 this로 호출해서 사용하며 컴포넌트의 개수만큼 컨텍스트도 생성. (여러 개)


컴포넌트별 컨텍스트의 기능

화면과 관련된 기능은 액티비티의 컨텍스트에서만 가능.

기능 \ 컨텍스트 Application Activity Service Content Provider Broadcast Receiver
Show a Dialog No Yes No No No
Start an Activity No Yes No No No
Layout inflation No Yes No No No
Start a Service Yes Yes Yes Yes Yes
Bind to a Service Yes Yes Yes Yes No
Send a Broadcast Yes Yes Yes Yes Yes
Register Broadcast Receiver Yes Yes Yes Yes No
Load Resource Values Yes Yes Yes Yes Yes



인텐트


액티비티를 실행하기 위해선 단순히 컨텍스트가 제공하는 메서드를 호출하면 되는데, 이때 실행할 액티비티가 명시된 인텐트를 해당 메서드에 전달해야 합니다.

이 인텐트는 ‘개발자의 의도’로, 어떤 의도를 가지고 메서드를 실행할 것인지를 인텐트에 담아서 안드로이드에 전달하면 안드로이드는 해당 인텐트를 해석하고 실행합니다.

프로젝트 생성 시 자동으로 만들어지는 MainActivity는 특별한 설정을 하지 않아도 안드로이드에 자동으로 등록되고 실행되지만, 이외에 다른 액티비티를 사용할 때는 반드시 인텐트에 새 액티비티의 이름과 데이터를 담아서 시스템에 전달해야 합니다.

image-20210729232942819

  1. 실행할 대상의 액티비티 이름과 전달할 데이터를 담아서 인텐트를 생성
  2. 생성한 인텐트를 startActivity( ) 메서드에 담아서 호출하면 액티비티 매니저에 전달됨
  3. 액티비티 매니저는 인텐트를 분석하여 지정한 액티비티를 실행
  4. 전달된 인텐트는 최종 목적지인 타깃 액티비티까지 전달
  5. 타깃 액티비티에서는 전달받은 인텐트에 데이터가 있다면 이를 꺼내 쓸 수 있음



새 액티비티 만들고 실행하기


1. 액티비티 생성

[Project(Android)] - [java] - 패키지명 우클릭 - [New] - [Activity] - [Empty Activity] 로 새 프로젝트 생성.

~Activity 라고 액티비티 이름을 입력하면 activity_~ 라는 xml 파일 자동 생성.


2. 뷰 바인딩

gradle 파일에서 뷰 바인딩 허용하고 MainActivity.kt 에서 binding 프로퍼티 생성, SetContentView 에 binding.root 전달.


3. 인텐트 생성: Intent(컨텍스트, 액티비티)

액티비티 호출을 위해서는 의도를 포함하는 Intent 인스턴스를 전달해야 함.

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

class::java 는 Intent를 사용하기 위한 작성 규칙


4. 액티비티를 호출하며 인텐트 전달: startActivity(인텐트)

startActivity(intent)



액티비티 사이에 값 주고받기


액티비티는 인텐트에 실행 메시지, 데이터 등을 주고 받을 수 있음.

인텐트 내부에는 번들(Bundle)이라는 저장 공간이 있고, 이 번들에 데이터를 담아 주고받음.

인텐트에 값을 입력할 때는 키와 값의 조합으로 넣고, 꺼낼 때는 키로 참조한다.

액티비티로부터 값을 돌려받는 구조

image-20210731230520308

메인 액티비티


1. 인텐트에 값 넣기: putExtra( )

intent.putExtra("from", "Hello Bundle") // String 키, String 값
intent.putExtra("from2", 2021)          // String 키, Int 값


2. 액티비티를 호출하며 데이터를 가진 인텐트 전달: startActivityForResult( )

앞에서 액티비티 호출 시에는 startActivity( ) 메서드를 사용한다고 했지만, 데이터를 가지는 인텐트를 전달할 때는 startActivityForResult( )를 사용해야 함

startActivityForResult(intent, 99)

startActivityForResult의 파라미터

  • 파라미터 1: 인텐트 인스턴스.
  • 파라미터 2: requestCode. 메인 액티비티에서 여러 개의 서브 액티비티 호출 시 어디에서 호출했는 지 구분하기 위한 용도


3. 서브 액티비티가 보낸 값 받기: onActivityResult( )

서브 액티비티 또한 종료할 때 자신을 호출한 메인 액티비티에 값을 보낼 수 있다. 이때 onActivityResult( ) 메서드를 사용하고, 함수가 정의되는 위치는 MainActivity 클래스 안이다. (onCreate 안 아님)

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?){
  super.onActivityResult(requestCode, resultCode, data)
  // 아래는 전형적인 코드 구성
  // resultCode가 정상인지 체크
  if (resultCode == RESULT_OK){
        when(requestCode) {
            99 -> {
                // 서브 액티비티에서 보낸 정보 받기. Safe Call 사용. 
                val message = data?.getStringExtra("returnValue")
                // 화면에 토스트로 보여주기. show()를 호출해야 화면에 나타남
                Toast.makeText(this, message, Toast.LENGTH_LONG).show()
            }
        }
    }
}

onActivityResult 의 파라미터

  • 파라미터 1: 메인 액티비티에서 서브 액티비티 호출 시 전달했던 requestCode.
  • 파라미터 2: resultCode. 서브 액티비티에서 보내는 종료 신호. RESULT_OK 또는 RESULT_CANCELED
  • 파라미터 3: 서브 액티비티에서 보낸 인텐트 인스턴스.

Toast.makeText의 파라미터

  • 파라미터 1: 컨텍스트 객체
  • 파라미터 2: 화면에 띄울 메시지.
  • 파라미터 3: 화면에 띄우는 시간. Toast.LENGTH_SHORT 또는 Toast.LENGTH_LONG.

[Override methods] 와 [Implement methods]

적절한 스코프 내에 마우스 포인터를 두고, [Ctrl + o] 또는 [Ctrl + i] 를 누르면 각각 그 위치에서 오버라이드 가능한 메서드들과 구현 가능한 메서드들의 목록을 보여준다. 또는 마우스 오른쪽 버튼을 누르고 [Generate] - [Override/Implement methods …] 로 찾을 수도 있다.


서브 액티비티

1. 메인 액티비티에서 보낸 값 받기

binding.to1.text = intent.getStringExtra("from1") // 문자열 값을 꺼낼 때는 getStringExtra()
binding.to2.text = "${intent.getIntExtra("from2", 0)}" // 정수 값을 꺼낼 때는 getIntExtra()

인텐트에 들어있는 값을 받을 때는 **get<데이터타입>Extra(키 이름)** 모양의 메서드를 사용합니다. intent 프로퍼티는 자신을 호출하는 메인 액티비티에서 보낸 인텐트 객체입니다.

Int, Float 등의 데이터 타입을 가진 값을 받을 경우 위처럼 0(defaultValue)을 지정해주고, 문자열로 사용하고 싶다면 “${ }” 로 값을 감싸서 문자열로 변환합니다.


2. 메인 액티비티로 보낼 인텐트 생성하고 데이터 넣기

// 메인 액티비티에 돌려줄 인텐트 인스턴스
// 돌려줄 때는 대상을 지정 안해도 됨
val returnIntent = Intent()
// 돌려줄 인텐트에 값 전달
returnIntent.putExtra("returnValue", binding.editMessage.text.toString())

서브 액티비티에서 메인 액티비티로 돌아갈 때 보낼 인텐트를 만들 때는 대상을 지정할 필요없음 (당연하니까).

마찬가지로 인텐트에 데이터를 넣을 때는 putExtra() 메서드를 사용하고 키와 값 쌍을 전달


3. 상태 값 설정하고 메인 액티비티로 인텐트 전달: setResult( )

// setResult(상태 값, 인텐트) 메서드 실행 시 자신을 호출한 액티비티로 인텐트 전달
setResult(RESULT_OK, returnIntent)

서브 액티비티에서 메인 액티비티로 돌아갈 때는 setResult( ) 사용.

setResult 의 파라미터

  • 파라미터 1: 메인 액티비티로 보낼 종료 코드. RESULT_OK 또는 RESULT_CANCELED
  • 파라미터 2: 메인 액티비티로 보낼 인텐트 인스턴스.

4. 액티비티 종료: finish( )

finish()

액티비티를 종료할 때는 finish( ) 메서드 호출


전체 코드

  • MainActivity.kt
package kr.co.hanbit.activitypractice

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import kr.co.hanbit.activitypractice.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    val binding by lazy { ActivityMainBinding.inflate(layoutInflater)}

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        // 인텐트 생성
        // ::class.java 라고 정확하게 입력, 인텐트 사용 규칙
        val intent = Intent(this, SubActivity::class.java)

        // 인텐트에 값 전달
        intent.putExtra("from1", "Hello Bundle")
        intent.putExtra("from2", 2021)

        // 액티비티 호출 -> startActivity() 메서드는 호출한 액티비티에서 값을 받을 수 없음
        // binding.btnStart.setOnClickListener { startActivity(intent) }
        // 호출한 액티비티에서 값을 받고 싶을 때는 startActivityForResult() 메서드 사용
        // 두번째 파라미터인 requestCode는 메인 액티비티에서 서브 액티비티를 호출하는 버튼이 여러 개 있을 때
        // 어떤 버튼에서 호출된 것인지를 구분
        binding.btnStart.setOnClickListener { startActivityForResult(intent, 99) }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        // resultCode가 정상인지 체크
        if (resultCode == RESULT_OK){
            when(requestCode) {
                99 -> {
                    // 서브 액티비티에서 보낸 정보 받기
                    val message = data?.getStringExtra("returnValue")
                    // 화면에 토스트로 보여주기. show()를 호출해야 화면에 나타남
                    Toast.makeText(this, message, Toast.LENGTH_LONG).show()
                }
            }
        }

    }
}


  • SubActivity.kt
package kr.co.hanbit.activitypractice

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kr.co.hanbit.activitypractice.databinding.ActivitySubBinding

class SubActivity : AppCompatActivity() {

    val binding by lazy {ActivitySubBinding.inflate(layoutInflater)}

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        // 텍스트뷰에 인텐트에 담겨온 값 입력
        binding.to1.text = intent.getStringExtra("from1") // 문자열 값을 꺼낼 때는 getStringExtra()
        binding.to2.text = "${intent.getIntExtra("from2", 0)}"

        // 서브 액티비티 종료 시 자신을 호출했던 메인 액티비티로 값을 돌려주기
        binding.btnClose.setOnClickListener {
            // 메인 액티비티에 돌려줄 인텐트 인스턴스
            // 돌려줄 때는 대상을 지정 안해도 됨
            val returnIntent = Intent()
            // 돌려줄 인텐트에 값 전달
            returnIntent.putExtra("returnValue", binding.editMessage.text.toString())
            // setResult(상태 값, 인텐트) 메서드 실행 시 자신을 호출한 액티비티로 인텐트 전달
            setResult(RESULT_OK, returnIntent)
            // 액티비티 종료
            finish()
        }
    }
}



액티비티 생명주기


안드로이드는 앱이 실행된 후 다른 액티비티 화면으로 전환되거나, 스마트폰 화면이 꺼지거나, 앱이 종료될 때 와 같이 상태 변화가 있을 때마다 화면에 보여지는 액티비티의 생명주기 메서드를 호출하여 상태 변화를 알려줍니다.

액티비티 생명 주기 메서드

생명주기 메서드 액티비티 상태 설명
onCreate( ) 만들어짐 액티비티가 생성됩니다. 우리가 실제 코드를 가장 많이 작성하는 메서드입니다.
onStart( ) 화면에 나타남 화면에 보이기 시작합니다.
onResume( ) 화면에 나타남 실제 액티비티가 실행되고 있습니다.
  현재 실행 중 (실행 중은 생명 주기 메서드가 따로 없고, onResume 이 호출되었다면 실행 중이라는 의미입니다.)
onPause( ) 화면이 가려짐 액티비티 화면의 일부가 다른 액티비티에 가려집니다.
onStop( ) 화면이 없어짐 다른 액티비티가 실행되어서 화면이 완전히 가려집니다.
onDestroy( ) 종료됨 종료됩니다.


  • 생명주기 메서드 오버라이딩
    override fun onCreate(savedInstanceState: Bundle?){
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

		override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
        super.onCreate(savedInstanceState, persistentState)
    }

    override fun onStart() {
        super.onStart()
    }

    override fun onResume() {
        super.onResume()
    }

    override fun onPause() {
        super.onPause()
    }

    override fun onStop() {
        super.onStop()
    }

    override fun onDestroy() {
        super.onDestroy()
    }

생명주기 메서드를 실행할 때는 반드시 super를 먼저 호출해야 합니다.

대표적으로 영상 플레이어를 사용할 때 화면 전환 시 리소스를 아끼기 위해 onPause( ) 또는 onStop( ) 메서드를 사용합니다.


생명주기 콜백의 이해

  1. 액티비티 생성 시
    • onCreate 메서드로 생성된 후 화면 구성 요소를 메모리에 로드.
    • onStart와 onResume을 호출하여 화면의 구성 요소를 나타내고 사용자와 상호작용 시작.
  2. 액티비티 제거 시 (finish 메서드 호출 또는 뒤로가기)
    • onPause와 onStop 메서드를 동시에 호출
    • 최종적으로 onDestroy 메서드를 호출하며 액티비티가 메모리에서 제거.
  3. 새로운 액티비티 생성 시
    • 메인 액티비티는 onPause를 거쳐 onStop 메서드까지만 호출. 종료되지는 않음.
    • 서브 액티비티는 onStart와 onResume을 연속적으로 호출한 후 실행 상태.
  4. 새로운 액티비티가 기존 액티비티를 모두 가리지는 않으며 생성 시
    • 메인 액티비티는 onPause 까지만 호출한 후 일시 정지(Paused) 상태 대기.
    • 서브 액티비티가 종료되면 메인 액티비티는 onStart 를 거치지 않고 바로 onResume 호출.


액티비티 백스택

  • 백스택: 액티비티가 호출(실행)되며 쌓이는 것을 담아두는 저장 공간. 사용자는 맨 위 액티비티를 보게 되며, 뒤로가기 버튼을 누르거나 현재 액티비티를 종료하면 스택에서 제거되어 다음에 쌓여있던 액티비티가 화면에 보임.


테스크와 프로세스

  • 프로세스: 애플리케이션의 실행 단위. 하나의 프로세스는 여러 개의 액티비티를 관리 및 실행.

  • 테스크: 애플리케이션에서 실행되는 프로세스를 관리하는 작업 단위.

안드로이드에서 테스크는 다른 프로세스의 액티비티를 함께 담을 수 있고 서로 공유할 수 있습니다.

예를 들어 카메라 기능을 간단한 코드로 호출해서 사용하면 실제로는 카메라 앱의 독자적인 프로세스가 실행되고 카메라 액티비티 또한 카메라 앱의 프로세스에 의해 처리됩니다.

다음은 특정 앱의 액티비티에서 카메라를 사용할 때 인텐트를 시스템을 통해 카메라 앱에 전달하는 예제 코드입니다. 카메라 앱을 호출하는 코드를 간략하게 구현하면 다음과 같습니다.

class Activity_B: AppCompatActivity(){
  val REQ_CAMERA = 100
  // ... 중략
  fun openCamera(){
    val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
    startActivityForResult(intent, REQ_CAMERA)
  }
}


액티비티 태스크 관리하기

액티비티 태스크는 두 가지 방법으로 관리할 수 있습니다.

  • 방법 1. AndroidManifest.xml 에서 매니페스트 설정
// 예시 코드
// <activity> 태그 안에 사용할 때는 모든 속성 이름 앞에 android: 가 붙어야 함. 
<activity android: name=".MainActivity" android:launchMode="singleInstance"></activity>
속성 설명
launchMode 호출할 액티비티를 새로 생성할 것인지 재사용할 것인지 결정. 기본 값은 항상 새로 생성.
4가지 모드: standard, singleTop, singleTask, singleInstance
taskAffinity affinity가 동일한 액티비티들은 같은 task에 들어감. 기본 값은 manifest에 정의된 패키지명이르모 기본적으로 한 앱의 모든 액티비티들을 동일한 affinity를 가짐.
affinity를 사용하여 액티비티를 서로 다르게 그룹화하거나, 서로 다른 앱(프로세스)에 정의된 액티비티를 같은 태스크에 둘 수 있음.
allowTaskReparenting 기본값은 false. true일 경우 호출한 액티비티를 동일한 affinity를 가진 태스크에 쌓이도록 함.
clearTaskOnLaunch 기본값은 false. true일 경우 액티비티가 재실행될 때 실행된 액티비티의 수와 관계없이 메인 액티비티를 제외하고 모두 제거.
alwaysRetainTaskState 기본값은 false. 사용자가 특정 시간 이상동안 앱을 사용하지 않을 경우 시스템이 루트 액티비티(태스크에서 가장 많이 실행된 액티비티)를 제외한 액티비티들을 제거. true일 경우 시스템이 관여하지 않음.
finishOnTaskLaunch 앱을 다시 사용할 때 태스크에 이 옵션이 true인 액티비티가 있다면 해당 태스크를 종료시킴.


  • 방법 2. startActivity 메서드에 전달되는 intent에 플래그 값 추가
플래그 설명
FLAG_ACTIVITY_CLEAR_TOP 호출하는 액티비티가 스택에 있으면 해당 액티비티를 Top으로 만들기 위해 그 위에 존재하던 액티비티를 모두 삭제.
예를 들어 액티비티 A, B, C, D, E 순으로 스택에 쌓여있을 때 C를 호출하면 D, E를 삭제하여 C 를 화면에 나타냄.
FLAG_ACTIVITY_NEW_TASK 새로운 태스크를 생성하여 안에 액티비티를 추가할 때 사용. 단, 기존에 존재하는 태스크 중 생성하려는 액티비티와 동일한 affinity를 가지고 있는 태스크가 있으면 해당 태스크로 액티비티가 들어감.
FLAG_ACTIVITY_MULTIPLE_TASK 호출되는 액티비티를 메인으로 하는 새로운 태스크를 생성. 이렇게 하면 동일한 액티비티를 하나 이상의 태스크에서 열 수 있다.
FLAG_ACTIVITY_NEW_TASK와 함께 사용해야 함.
FLAG_ACTIVITY_SINGLE_TOP 호출되는 액티비티가 Top에 있으면 해당 액티비티를 다시 생성하지 않고, 존재하던 액티비티를 재사용함.
액티비티 A, B, C 가 있을 때 C를 호출하면 기존과 동일하게 A, B, C가 나옴.



정리


  • 안드로이드 4대 컴포넌트: 액티비티, 브로드캐스트 리시버, 서비스, 컨텐트 프로바이더

    컴포넌트 보조 도구: 인텐트, 컨텐트 리졸버

  • 컨텍스트에는 애플리케이션 컨텍스트와 베이스 컨텍스트가 있음

    • 애플리케이션 컨텍스트는 앱의 핵심 기능을 담고 있는 단 하나의 컨텍스트.
    • 베이스 컨텍스트는 개별 컴포넌트 생성 시 함께 생성되는 컨텍스트. 각각의 컴포넌트에서 baseContext 또는 this로 호출.
  • 인텐트는 컴포넌트가 다른 컴포넌트를 호출할 때 그 대상, 데이터 등을 가지고 있는 보조 도구이다.

    • 컴포넌트에서 인텐트를 만들고 컴포넌트 호출 메서드에 인텐트를 담아서 실행하면, 컴포넌트 매니저가 그 값을 분석하여 목적지 컴포넌트까지 전달해준다. 호출된 컴포넌트에서는 호출한 컴포넌트에서 보낸 데이터 값 등을 참조할 수 있다.
  • 액티비티 사이에 값을 주고받는 과정

    • 인텐트 생성 ➡ 인텐트에 값 넣기 ➡ 액티비티를 호출하며 데이터를 가진 인텐트 전달 ➡ 메인 액티비티에서 보낸 값 받기 ➡ 메인 액티비티로 보낼 인텐트 생성하고 값 넣기 ➡ 상태값 설정하고 메인 액티비티로 인텐트 전달 ➡ 액티비티 종료
  • 액티비티의 생명주기 메서드는 6가지로, onCreate, onStart, onResume, onPause, onStop, onDestroy가 있다.

    • 액티비티 생성 시: onCreate ➡ onStart ➡ onResume
    • 액티비티 제거 시: onPause, onStop ➡ onDestroy
    • 새로운 액티비티 생성 시: onPause, onStop(메인 액티비티) ➡ onStart, onResume(서브 액티비티)
    • 새로운 액티비티가 기존 액티비티를 모두 가리지는 않으며 생성 시: onPause(서브 액티비티 진행) ➡ onResume(서브 액티비티 종료)
  • 액티비티 백스택이란 액티비티가 호출(실행)되며 쌓이는 것을 담아주는 저장 공간.

  • 프로세스는 애플레케이션의 실행 단위이며, 테스크는 이러한 프로세스를 관리하는 작업 단위.

    • 서로 다른 프로세스는 동일한 테스크에 함께 담을 수 있고 데이터를 공유할 수 있다.
    • 액티비티 태스크는 매니페스트 설정 또는 인텐트에 플래그 값 설정을 통해 관리할 수 있다.

Categories:

Updated:

Leave a comment