안드로이드 SDK 의 생명주기 메소드들을 이용해 이에 적합한 작업을 수행할 수 있도록 코드를 작성할 수 있다. 각각 생명주기의 콜백함수를 잘 이용해야 한다.
onCreate(savedInstantceState: Bundle?) : 가장 많이 사용.
onRestart() : 액티비티가 다시 시작될 때 onStart() 직전 호출된다.
onStart() : 액티비티가 백그라운드에서 포어그라운드로 이동할 때 수행되는 첫 번째 콜백이다.
onRestoreInstanceState(savedInstanceState: Bundle?) : savedInstanceState 로 상태를 저장한 경우 onStart 이후에 호출되는 메서드. onCreate(savedInstanceState:Bundle?) 말고도 여기서 상태를 복원할 수도 있다.
onResume() : 액티비티 생성 마지막 과정에서 호출되는 콜백. 백그라운드에서 포어그라운드로 돌아올 때 실행.
onSaveInstanceState(outState: Bundle?) : 액티비티 상태를 저장하기에 좋은 함수다. (액티비티 deinit 혹은 백그라운드 상태변경 시 호출되는 함수인 듯)
onPause() : 액티비티 백그라운드 전환 또는 대화상자나 다른 액티비티가 나타날 때 호출.
onStop() : 액티비티가 가려질 때 호출. 액티비티는 백그라운드.
onDestroy() : 시스템 자원이 부족할 시 안드로이드가 자동으로 액티비티를 deinit(finish 함수 호출).
액티비티 시작 -> onCreate() -> onStart() -> (액티비티 화면 표시) -> onResume() -> (액티비티 동작 및 실행) -> (홈버튼 눌러서 액티비티 보이지 않음) -> onStop() -> (다시 앱 실행) -> onRestart() -> onStart, onResume, 액티비티 실행, onPause, onStop -> (앱 종료) -> onDestroy() -> (액티비티 종료)
package com.example.activitycallback
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import android.util.Log
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG, "onCreate")
}
override fun onRestart() {
super.onRestart()
Log.d(TAG, "onRestart")
}
override fun onStart() {
super.onStart()
Log.d(TAG, "OnStart")
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
Log.d(TAG, "OnRestoreInstanceState")
}
override fun onResume() {
super.onResume()
Log.d(TAG, "onResume")
}
override fun onPause() {
super.onPause()
Log.d(TAG, "onPause")
}
override fun onStop() {
super.onStop()
Log.d(TAG, "onStop")
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
Log.d(TAG, "onSaveInstanceState")
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy")
}
companion object {
private const val TAG = "MainActivity"
}
}
기기 회전이 구성 변경을 일으켜 액티비티를 재생성한다고 배웠다. 메모리 확보를 위해 액티비티 종료가 발생하기도 한다.
그 때문에 액티비티 상태를 보존하고 복원하는 것이 중요하다.
android:textSize 의 단위를 sp 로 지정하는데, 이는 밀도 독립적인 픽셀을 나타낸다. 앱이 실행되는 기기의 밀도에 따라 크기를 정의하되 사용자 설정에 따라 텍스트 크기도 변경 가능하다.
layout xml 파일에서 안드로이드 프레임워크는 id 가 지정된 경우에만 상태를 저장한다.
onSaveInstanceState 에서 Bundle 값을 키-값 형태로 저장하고 onRestoreInstanceState 나 onCreate 에서 다시 가져와 사용한다. 간단한 로직이라면 onCreate 에서, 아니면 onRestoreInstanceState 에서 복구한다.
Bundle 을 이용하는 방법도 있지만 안드로이드 프레임워크에서 제공하는 안드로이드 아키텍처 컴포넌트(AAC, Android Architecture Component) 중 하나인 ViewModel 을 이용해 상태를 저장하고 복원할 수도 있다.
안드로이드에서 인텐트는 컴포넌트 간의 통신 메커니즘이다. 앱을 제작할 때 대부분의 경우 현재 액티비티에서 어떤 동작이 발생하면 특정한 다른 액티비티가 시작되기를 원할 수 있다.
정확히 어떤 액티비티가 시작될지 지정하는 걸 명시적 인텐트라고 한다.
묵시적 인텐트의 예시는 카메라이다. 카메라 시작 인텐트를 보내고 시스템이 그걸 처리하는 방식이다.
인텐트 관련 이벤트에 응답하려면 인텐트 필터를 등록해야 한다. 인텐트 필터는 AndroidManifest.xml 의 intent-filter 태그를 사용한다.
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
android.intent.action.MAIN 는 앱의 주 진입점을 말한다. android.intent.category.LAUNCHER 는 앱이 런처에 표시되게 해준다. 둘을 합치면 런처에서 앱 아이콘을 클릭할 때 앱이 실행된다는 의미다.
실제 앱을 만들어서 인텐트를 이용해 두 액티비티가 상호작용하는 과정을 설명한다.
const val RAINBOW_COLOR_NAME = "RAINBOW_COLOR_NAME"
const val RAINBOW_COLOR = "RAINBOW_COLOR"
const val DEFAULT_COLOR = "#FFFFFF"
class MainActivity : AppCompatActivity() {
private val startForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
activityResult -> val data = activityResult.data
val backgroundColor = data?.getIntExtra(
RAINBOW_COLOR,
Color.parseColor(DEFAULT_COLOR))
?: Color.parseColor(DEFAULT_COLOR)
val colorName = data?.getStringExtra(RAINBOW_COLOR_NAME) ?: ""
val colorMessage = getString(R.string.color_chosen_message, colorName)
val rainbowColor = findViewById<TextView>(R.id.rainbow_color)
rainbowColor.setBackgroundColor(ContextCompat.getColor(this, backgroundColor))
rainbowColor.text = colorMessage
rainbowColor.isVisible = true
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.submit_button)
.setOnClickListener {
startForResult.launch(
Intent(
this,
RainbowColorPickerActivity::class.java))
}
}
}
class RainbowColorPickerActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_rainbow_color_picker)
val colorPickerClickListener = View.OnClickListener { view ->
when (view.id) {
R.id.red_button -> setRainbowColor(
getString(R.string.red),
R.color.red)
R.id.orange_button -> setRainbowColor(
getString(R.string.orange),
R.color.orange)
R.id.yellow_button -> setRainbowColor(
getString(R.string.yellow),
R.color.yellow)
R.id.green_button -> setRainbowColor(
getString(R.string.green),
R.color.green)
R.id.blue_button -> setRainbowColor(
getString(R.string.blue),
R.color.blue)
R.id.indigo_button -> setRainbowColor(
getString(R.string.indigo),
R.color.indigo)
R.id.violet_button -> setRainbowColor(
getString(R.string.violet),
R.color.violet)
else -> {
Toast.makeText(this,
getString(R.string.unexpected_color), Toast.LENGTH_LONG
).show()
}
}
}
findViewById<View>(R.id.red_button).setOnClickListener(colorPickerClickListener)
findViewById<View>(R.id.blue_button).setOnClickListener(colorPickerClickListener)
findViewById<View>(R.id.yellow_button).setOnClickListener(colorPickerClickListener)
findViewById<View>(R.id.green_button).setOnClickListener(colorPickerClickListener)
findViewById<View>(R.id.blue_button).setOnClickListener(colorPickerClickListener)
findViewById<View>(R.id.indigo_button).setOnClickListener(colorPickerClickListener)
findViewById<View>(R.id.violet_button).setOnClickListener(colorPickerClickListener)
}
private fun setRainbowColor(colorName: String, color: Int) {
Intent().let { pickedColorIntent ->
pickedColorIntent.putExtra(RAINBOW_COLOR_NAME, colorName)
pickedColorIntent.putExtra(RAINBOW_COLOR, color)
setResult(Activity.RESULT_OK, pickedColorIntent)
finish()
}
}
}
registerForActivityResult 를 이용해서 액티비티의 상호작용을 미리 정의해 놓는다. registerForActivityResult 내에는 activityResult.data 를 통해 원시타입 데이터들을 사용할 수 있다. 키는 미리 상수로 정의해 두었다. 이를 통해 넘어온 데이터로 뷰의 속성을 새로 정의하고 있다.
registerForActivityResult 로 만들어지는 ActivityResultLauncher 의 launch 함수를 이용해 인텐트를 정의한다. Intent(this, RainbowColorPickerActivity::class.java) 는 새로운 액티비티를 생성한다는 의미이다. MainActivity.kt 는 이쯤 보면 될 것 같다.
RainbowColorPickerActivity.kt 는 setRainbowColor 가 중요하겠다. Intent() 를 이용해 MainActivity 에서 생성한 인텐트를 불러오고 전달된 String, Int 를 putExtra 함수로 추가하고 있다. 세팅이 끝났다면 setResult 로 결과 반환 및 finish 로 액티비티를 종료시키고 이전 액티비티를 보여주게 한다.
앱을 런처에서 열면 앱은 자체적인 Task 를 생성하고 생성한 각 액티비티는 Back Stack 에 추가된다. 여기서 동작방식이 액티비티 런치 모드에 따라 달라진다.
아래 3개는 일반적으로 사용하지는 않는다.
Activity 를 추가하게 되면 AndroidManifest.xml 에 activity 태그가 추가되는데 여기에 속성으로 android:launchMode="singleTop" 을 주게 되면 singleTop 모드가 된다. 기본은 당연히 standard.