Register
- com.example.register
- 회원 가입 프로젝트

- 이름, 전화번호, 라디오 버튼, 약관동의 를 하나씩 체크를 할 때마다 신청 위에 있는 프로그레스 바가 진행 상황을 추적하고 100%가 되면 “신청”버튼을 활성화 한다.
Android Application Component
- 앱을 실행 시키는 진입점
- 사용자가 앱 아이콘을 클릭, 상단 Notification 클릭, 공유하기 또는 파일 열기 등 앱을 실행 시킬 수 있는 진입점
- new 연산을 통해 개발자가 직접 객체를 생성할 수 없다 → 생명 주기를 안드로이드 운영체제가 관리한다
- 앱이 가진 Component의 정보를 안드로이드 시스템이 알아야 한다. AndroidManifest.xml
앱을 실행했을 때 어떤 위젯 레이아웃(컴포넌트)이 제일 처음에 뜨도록 하는건 안드로이드 시스템이 한다.
Intent : 컴포넌트간의 통신
AndroidManifest.xml
<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>
- Activity : GUI를 제공하며 사용자와 상호작용하는 Component
- Service : 화면 없이 백그라운드에서 작업을 하는 컴포넌트
- Content Provider : 앱이 가진 데이터를 공유하는 컴포넌트. 다른 앱이 내 앱의 정보를 사용할 수 있게 해준다.
- 예 : 사진첩, 갤러리, 주소록, 캘린더
- Broadcast Receiver : 시스템이 발생시킨 이벤트를 받을 수 있는 컴포넌트.
Activity LifeCycle

- onCreate(필수 override 함수) : Activity가 생성될 때
- onStart() : Activity가 사용자에게 보일 때
- onResume() : 사용자와 상호작용이 가능할 때
- onPause() :시스템 팝업 메시지가 떴을때 UI는 화면에 그려지지만 포커스를 잃은 상태
- onStop() : 화면에서 UI가 완전히 사라지는 것
- onDestroy() : 앱 종료
- 반대 순서
- onPause → onStop → onDestroy
onCreate()가 실행되고 Listener를 등록됐을 때 그 후 앱이 종료가 됐을 때 반드시 리스너를 삭제해주어야 함
- 만약 onCreate() 에서 등록한 리스너가 계속해서 켜져있으면 메모리 누수가 발생함.
onDestroy() 에서 리스너를 삭제를 해주어야하는게 제일 좋다
onCreate(), onDestroy()는 한 번만 사용한다.
Logcat
- Android 개발 중에 debug를 할 수 있도록 제공
- 사용법
- Log.i(tag:String?, message:String)
- i: info, d:debug, w:warning, e:error
Lifecycle 확인
onStart() : 화면에 포커스가 되면 onStart Log가 뜬다.
onStop() : 화면에서 UI가 사라지면(홈 버튼)을 누르면 Log가 뜬다.
override fun onStart() {
super.onStart()
// Lifecycle 함수를 override할때는 super의 함수를 반드시 호출해야함
Log.i("MainActivity", "onStart")
}
override fun onStop() {
super.onStop()
Log.i("MainActivity", "onStop")
}

Lifecycle Observer - Android Jetpack
- Activity의 Lifecycle에 맞추어 동작해야 하는 기능이 있는 경우 (카메라, 위치 확인 등) 각 lifecycle 함수에서 해당 기능을 초기화, 멈춤, 해제 등의 함수를 호출해야 한다.
- 문제점
- 각 lifecycle 함수가 복잡해진다.
- 해당 기능의 활성화에 지연이 발생할 경우 Activity가 stop되고 난 후 callback이 실행될 수 도 있다. → 호출 이후 Activity의 상태와 상관없이 코드가 실행된다.
- Lifecycle은 Android 시스템에서 관리를 하기때문에 우리는 이 Lifecycle에 변동이 있을 때 어떠한 특정한 동작을 하고 싶다 - Lifecycle Observer 사용으로 생명주기(LifeCycle) 변경 시 override한 onXXX() 메소드를 실행시키고 싶다.
카메라와 같이 센서를 필요로 하는건 다 비동기식으로 불러서 사용이 될 때까지 기다려야 되는데
만약의 상황에서 사용자가 카메라를 실행하고 알수없는 이유로 카메라의 준비과정이 너무 오래 걸려 사용자가 해당 페이지를 벗어나거나 앱을 종료하였다. 그런데 앱이 종료되었는데 카메라가 준비되어 onCreate() 함수가 실행되어 이제 UI를 그릴려고 할 때 이미 앱이 종료가 되어 Activity의 모든 정보들이 메모리에서 지워진 후이다, 이럴 때 이 앱의 모든 변수들이 null 이기에 NullPointException이 뜬다..
Android Jetpack
https://developer.android.com/jetpack?hl=ko
개발자들이 앱 개발을 할 때 반복된 코드를 줄이고 하위버전 호환성을 가지는 코드를 쉽게 작성 하도록 도와주는 라이브러리 모음
- Lifecylce Observer
- Activity, Fragment 등과 같은 LifecycleOwner의 Lifecycle 상태에 대한 정보를 다른 객체가 관찰할 수 있도록 하는 클래스

- Lifecycle 사용하지 않은 경우의 코드 : MainActivity.kt

- Lifecycle 사용하기 - libs.versions.toml 수정 및 sync
[versions]
lifecycle = "2.7.0"
[libraries]
androidx-lifecycle-runtime-ktx = {group = "androidx.lifecycle", name="lifecycle-runtime-ktx", version.ref = "lifecycle"}
build.gradle.kts(Module: app) - dependencies 추가
implementation(libs.androidx.lifecycle.runtime.ktx)
Lifecycle을 참조하는 경우

class MyCycle: DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
Log.i("MainActivity", "Mycycle-Started")
}
override fun onStop(owner: LifecycleOwner) {
Log.i("MainActivity", "Mycycle-Stopped")
}
}
class MainActivity : AppCompatActivity() {
myCycle = MyCycle() // 객체 생성
lifecycle.addObserver(myCycle) // 객체를 lifecycle에 등록
....
override fun onStart() {
super.onStart()
Log.i("MainActivity", "onStart")
// Actitivy의 lifecycle에 맞춰서 해당 함수를 일일이 호출해야 함
}
// onStop() Log
override fun onStop() {
super.onStop()
Log.i("MainActivity", "onStop")
}
override fun onResume() {
super.onResume()
Log.i("MainActivity", "onResume")
}
메인 엑티비티의 lifecycle에 변화가 생기면 옵저버의 오버라이드 함수가 트리거됨
activity에서 생명주기를 관리 장점
- 생명주기와 관련된 코드를 액티비티나 프래그먼트 내에서 작성하지 않아 유지보수에 도움이 되고 생명주기와 밀접한 관련을 가진 코드를 작성하기가 더 쉬워집니다.
Intent
안드로이드의 4대 컴포넌트 중 하나
- Message를 주고 받기 위한 객체
- Application component를 요청 : Activity, Service의 시작, Broadcast 전달
- 명시적 Intent와 암시적 Intent가 있다.
- 명시적 Intent : fully qualified class name을 제공. 내 앱 안의 Activity 또는 Service 시작(내 앱안에서 명시적으로 메시지를 보내고 싶을 때)
- 내가 필요한 클래스를 명시적으로 딱 찍을때
- 암시적 Intent : 필요한 작업(동작)을 선언. 해당 기능을 지원할 수 있는 앱을 선택 할 수 있도록 한다. 대상은 안 정해지고 하고 싶은 동작을 적는다. → “내가 카메라를 켤 때 (전면 카메라, 후면 카메라) 중 무엇을 켤것인가?” 라는걸 물어보는 것
- 다른 앱의 기능을 빌릴 때
- AndroidManifest.xml 에 선언된 Activity에 자신이 처리할 수 있는 일을 적는다
Intent : 애플리케이션 끼리 주고 받을 수 있는 메세지 같은 것
안드로이드 → 어플 , 어플 → 안드로이드, 어플 ↔ 어플
안드로이드 상에서 어플과 안드로이드 상에 끊임없이 주고 받는 메세지
대표적인 Broadcast : 와이파이가 꺼졌다 켜졌다, 배터리가 없다, 부팅이 완료되었다 , …..
Broadcast도 Intent이다.
명시적 Intent
- 프로젝트에 Activity 추가하기
- MainActivity가 포함된 패키지에서 우클릭
- New
- Activity
- EmptyActivity

- Activity Name : CourseActivity
→ CourseActivity.kt 생성됨
→ res/layout/activity_course.xml 자동 생성됨
→ AndroidManifest.xml 에 정보가 자동 추가됨

- CourseActivity에도 ViewBinding을 적용한다.
class CourseActivity : AppCompatActivity() {
private val binding by lazy { ActivityCourseBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
....
ViewCompat.setOnApplyWindowInsetsListener(binding.main) { v, insets ->
....
- activity_course.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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CourseActivity">
<TextView
android:id="@+id/textViewName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="이름 정보가 없습니다."
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textViewType"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="분반 정보가 없습니다."
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textViewName" />
<Button
android:id="@+id/buttonPhoto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="사진불러오기"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textViewType" />
<Button
android:id="@+id/buttonOk"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="확인"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/buttonCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="취소"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/buttonOk" />
<ImageView
android:id="@+id/imageViewPhoto"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/buttonCancel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/buttonPhoto"
app:srcCompat="@drawable/ic_account_circle" />
</androidx.constraintlayout.widget.ConstraintLayout>
- MainActivity에서 CourseActivity를 부르는 방법 :
- MainActivity의 onCreate 함수 끝 부분에 다음 코드 추가
맨 끝에 java를 붙이는 이유는 Intent는 Java로 구현이 되어져 있다. 그래서 intent를 Java 클래스 형식으로 전달을 함binding.buttonApply.setOnClickListener { val intent = Intent(this, CourseActivity::class.java) startActivity(intent) // 안드로이드 운영체제한테 해당 Activity를 실행하라고 요청 }
이제 MainActivity에서 buttonApply를 클릭하면 CourseActivity로 화면이 넘어가짐
- MainActivity의 onCreate 함수 끝 부분에 다음 코드 추가
- Activity Stack
- 생성된 Activity는 Stack에 쌓이며 가장 위의 Activity가 보인다.
- Back키를 누르거나 Activity 스스로 finish() 함수를 호출하면 종료된다.
- Activity는 싱글톤이 아니며 startActivity를 호출한 횟수만큼 생성된다
- 싱글톤 : 객체가 딱 하나만 만들어지는 것
- 만약 다른 Activty에서 뒤로 돌아가기를 할 때 finish()를 사용하지 않고, startActivity를 사용할 경우 또 Activity가 생성된다.
- CourseActivty의 onCreate 안에 finish()를 호출하여본다.
binding.buttonCancel.setOnClickListener { finish() }
→ buttonCancel 버튼이 눌리면 종료됨.
개발자가 임의로 Activity를 종료시킬수도 있음.
MainActivity에서 CourseActivity가 부를 때 startActivty() 를 사용하는데 해당 함수는 반환타입이 없다.
그래서 MainActivity에서 startActitvity를 하면 Main위에 뜨긴 뜨는데 Main에서는 Course가 띄워졌는지 안 띄워졌는지 알지를 못한다.
왜냐하면 Android가 Activtiy를 사용 관리하기때문에.
그러면 데이터를 어떻게 전달해 줄 것인가??? → Intent 사용
Activity간의 데이터 전달
- 단반향 : 호출 하는 Activity가 데이터 함께 전달하기
- intent에 데이터를 담아서 전달함
- MainActivity에서 사용자 이름, 분반(성인반/학생반)을 CourseActivity로 전달
- CourseActivity에서는 받은 데이터 꺼내기
MainActivity
binding.buttonApply.setOnClickListener {
val intent = Intent(this, CourseActivity::class.java)
intent.putExtra("name", binding.editTextName.text.toString()) // intent에 데이터를 담아서 넘김
intent.putExtra("value", 1234) // intent에 int를 담아서 넘김
startActivity(intent) // 안드로이드 운영체제한테 해당 Activity를 실행하라고 요청
}
intent.putExtra(name, object) 형식으로 데이터를 담는다.
CourseActivity
val str = intent.getStringExtra("name")!! // getStringExtra는 String? 타입임
val value = intent.getIntExtra("value", -1)
// int는 null이 불가하기에 default값을 줘야한다.
Log.d("CourseActivity", str)
Log.d("CourseActivity", "$value")

MainActivity
binding.buttonApply.setOnClickListener {
val type = if(binding.radioButtonAdult.isChecked) binding.radioButtonAdult.text
else binding.radioButtonStudent.text // true면 성인, false면 학생
val intent = Intent(this, CourseActivity::class.java)
intent.putExtra("name", binding.editTextName.text.toString())
intent.putExtra("type", type)
startActivity(intent) // 안드로이드 운영체제한테 해당 Activity를 실행하라고 요청
}
CourseActivity
val name = intent.getStringExtra("name")!! // getStringExtra는 String? 타입임
val type = intent.getStringExtra("type")!!
Log.d("CourseActivity", name)
Log.d("CourseActivity", type)

- 양방향 : 호출하는 Activity에서도 데이터를 주고 호출을 받은 Activity는 종료할 때 결과를 반환한다.

- 이 방법의 단점
- 하나의 Activity에서 여러 요청을 보낼 수 있는데 callback은 onActivityResult 함수 하나만 제공하여 코드가 복잡해진다.
- 요청한 데이터에 따라 다양한 결과가 있을 수 있는데 Intent의 data로만 전달되어 null check 등의 부가 코드가 많다.
setResult(), onActivityResult()는 현재는 사용하지 않음
Activity Result - Android Jetpack
- 기존 startActivityForResult + onActivityResult의 불편한 점을 개선한 클래스
- activity에 요청을 전달하고 그 결과용 callback을 함께 지정하는 객체를 사용한다.
- 명시적 Intent와 암시적 Intent에 모두 사용할 수 있으며 ActivityResultContract 라는 파라미터로 요청의 종류를 알린다.
https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContracts


다른 앱한테 부탁(카메라 등등)하는 코드도 모여있고, 내 앱한테 데이터를 부탁하는 StartActivityForResult도 만들어져있음.
MainActivity
class MainActivity : AppCompatActivity() {
private val getResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
Log.i("MainActivity", "${it.resultCode}")
} // Activity Result
// 첫 번째 파라미터인 ActivityResultContract의 종류에 따라 람다식의 파라미터 타입이 정해짐
...
binding.buttonApply.setOnClickListener {
...
getResult.launch(intent) // 안드로이드 운영체제한테 해당 Activity를 실행하라고 요청
}
CourseActivty
override fun onCreate(savedInstanceState: Bundle?) {
....
setResult(Activity.RESULT_CANCELED)
// setResult 함수는 두번째 파라미터로 Intent 타입의 받을 수 있으며 만약 intent를 넘길 경우
// resultLauncher의 람다식에서 it.data로 읽을 수 있다.
val name = intent.getStringExtra("name")!! // getStringExtra는 String? 타입임
val type = intent.getStringExtra("type")!!
Log.d("CourseActivity", name)
Log.d("CourseActivity", type)
binding.buttonOk.setOnClickListener {
setResult(Activity.RESULT_OK) // OK 버튼으로 종료할 때만 RESULT_OK를 전달
finish() // Activty를 종료시킴 - Stack에서 뺀다.
}
binding.buttonCancel.setOnClickListener { finish() }

상태코드가 미리 정의가 되어져 있음
암시적 Intent
- 필요한 작업(동작)을 적어 다른 앱의 기능을 불러 사용한다.
- 사진 추가 Button을 눌렀을 때 폰에 저장된 사진을 선택할 수 있게 하고 이를 내 앱에 출력해본다.
- Emulator를 사용하는 경우 이미지를 미리 다운받아 두거나 emulator의 카메라 앱으로 사진을 찍어둔다.
- ActivityResultContract 중 사진 한 장을 요청하는 Contract를 사용한다.
- ActivityResultContracts.GetContent
- 이 경우 람다식의 파라미터는 이미지의 Uri가 된다.
private val requestPhoto = // 이미지를 요청하는 코드
registerForActivityResult(ActivityResultContracts.GetContent()) {
binding.imageViewPhoto.setImageURI(it) // 보안때문에 실제 파일 경로는 알지 못함
// 가상의 주소로 이미지를 가져옴
}
override fun onCreate(savedInstanceState: Bundle?) {
....
binding.buttonPhoto.setOnClickListener { requestPhoto.launch("image/*") }



Register
- com.example.register
- 회원 가입 프로젝트

- 이름, 전화번호, 라디오 버튼, 약관동의 를 하나씩 체크를 할 때마다 신청 위에 있는 프로그레스 바가 진행 상황을 추적하고 100%가 되면 “신청”버튼을 활성화 한다.
Android Application Component
- 앱을 실행 시키는 진입점
- 사용자가 앱 아이콘을 클릭, 상단 Notification 클릭, 공유하기 또는 파일 열기 등 앱을 실행 시킬 수 있는 진입점
- new 연산을 통해 개발자가 직접 객체를 생성할 수 없다 → 생명 주기를 안드로이드 운영체제가 관리한다
- 앱이 가진 Component의 정보를 안드로이드 시스템이 알아야 한다. AndroidManifest.xml
앱을 실행했을 때 어떤 위젯 레이아웃(컴포넌트)이 제일 처음에 뜨도록 하는건 안드로이드 시스템이 한다.
Intent : 컴포넌트간의 통신
AndroidManifest.xml
<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>
- Activity : GUI를 제공하며 사용자와 상호작용하는 Component
- Service : 화면 없이 백그라운드에서 작업을 하는 컴포넌트
- Content Provider : 앱이 가진 데이터를 공유하는 컴포넌트. 다른 앱이 내 앱의 정보를 사용할 수 있게 해준다.
- 예 : 사진첩, 갤러리, 주소록, 캘린더
- Broadcast Receiver : 시스템이 발생시킨 이벤트를 받을 수 있는 컴포넌트.
Activity LifeCycle

- onCreate(필수 override 함수) : Activity가 생성될 때
- onStart() : Activity가 사용자에게 보일 때
- onResume() : 사용자와 상호작용이 가능할 때
- onPause() :시스템 팝업 메시지가 떴을때 UI는 화면에 그려지지만 포커스를 잃은 상태
- onStop() : 화면에서 UI가 완전히 사라지는 것
- onDestroy() : 앱 종료
- 반대 순서
- onPause → onStop → onDestroy
onCreate()가 실행되고 Listener를 등록됐을 때 그 후 앱이 종료가 됐을 때 반드시 리스너를 삭제해주어야 함
- 만약 onCreate() 에서 등록한 리스너가 계속해서 켜져있으면 메모리 누수가 발생함.
onDestroy() 에서 리스너를 삭제를 해주어야하는게 제일 좋다
onCreate(), onDestroy()는 한 번만 사용한다.
Logcat
- Android 개발 중에 debug를 할 수 있도록 제공
- 사용법
- Log.i(tag:String?, message:String)
- i: info, d:debug, w:warning, e:error
Lifecycle 확인
onStart() : 화면에 포커스가 되면 onStart Log가 뜬다.
onStop() : 화면에서 UI가 사라지면(홈 버튼)을 누르면 Log가 뜬다.
override fun onStart() {
super.onStart()
// Lifecycle 함수를 override할때는 super의 함수를 반드시 호출해야함
Log.i("MainActivity", "onStart")
}
override fun onStop() {
super.onStop()
Log.i("MainActivity", "onStop")
}

Lifecycle Observer - Android Jetpack
- Activity의 Lifecycle에 맞추어 동작해야 하는 기능이 있는 경우 (카메라, 위치 확인 등) 각 lifecycle 함수에서 해당 기능을 초기화, 멈춤, 해제 등의 함수를 호출해야 한다.
- 문제점
- 각 lifecycle 함수가 복잡해진다.
- 해당 기능의 활성화에 지연이 발생할 경우 Activity가 stop되고 난 후 callback이 실행될 수 도 있다. → 호출 이후 Activity의 상태와 상관없이 코드가 실행된다.
- Lifecycle은 Android 시스템에서 관리를 하기때문에 우리는 이 Lifecycle에 변동이 있을 때 어떠한 특정한 동작을 하고 싶다 - Lifecycle Observer 사용으로 생명주기(LifeCycle) 변경 시 override한 onXXX() 메소드를 실행시키고 싶다.
카메라와 같이 센서를 필요로 하는건 다 비동기식으로 불러서 사용이 될 때까지 기다려야 되는데
만약의 상황에서 사용자가 카메라를 실행하고 알수없는 이유로 카메라의 준비과정이 너무 오래 걸려 사용자가 해당 페이지를 벗어나거나 앱을 종료하였다. 그런데 앱이 종료되었는데 카메라가 준비되어 onCreate() 함수가 실행되어 이제 UI를 그릴려고 할 때 이미 앱이 종료가 되어 Activity의 모든 정보들이 메모리에서 지워진 후이다, 이럴 때 이 앱의 모든 변수들이 null 이기에 NullPointException이 뜬다..
Android Jetpack
https://developer.android.com/jetpack?hl=ko
개발자들이 앱 개발을 할 때 반복된 코드를 줄이고 하위버전 호환성을 가지는 코드를 쉽게 작성 하도록 도와주는 라이브러리 모음
- Lifecylce Observer
- Activity, Fragment 등과 같은 LifecycleOwner의 Lifecycle 상태에 대한 정보를 다른 객체가 관찰할 수 있도록 하는 클래스

- Lifecycle 사용하지 않은 경우의 코드 : MainActivity.kt

- Lifecycle 사용하기 - libs.versions.toml 수정 및 sync
[versions]
lifecycle = "2.7.0"
[libraries]
androidx-lifecycle-runtime-ktx = {group = "androidx.lifecycle", name="lifecycle-runtime-ktx", version.ref = "lifecycle"}
build.gradle.kts(Module: app) - dependencies 추가
implementation(libs.androidx.lifecycle.runtime.ktx)
Lifecycle을 참조하는 경우

class MyCycle: DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
Log.i("MainActivity", "Mycycle-Started")
}
override fun onStop(owner: LifecycleOwner) {
Log.i("MainActivity", "Mycycle-Stopped")
}
}
class MainActivity : AppCompatActivity() {
myCycle = MyCycle() // 객체 생성
lifecycle.addObserver(myCycle) // 객체를 lifecycle에 등록
....
override fun onStart() {
super.onStart()
Log.i("MainActivity", "onStart")
// Actitivy의 lifecycle에 맞춰서 해당 함수를 일일이 호출해야 함
}
// onStop() Log
override fun onStop() {
super.onStop()
Log.i("MainActivity", "onStop")
}
override fun onResume() {
super.onResume()
Log.i("MainActivity", "onResume")
}
메인 엑티비티의 lifecycle에 변화가 생기면 옵저버의 오버라이드 함수가 트리거됨
activity에서 생명주기를 관리 장점
- 생명주기와 관련된 코드를 액티비티나 프래그먼트 내에서 작성하지 않아 유지보수에 도움이 되고 생명주기와 밀접한 관련을 가진 코드를 작성하기가 더 쉬워집니다.
Intent
안드로이드의 4대 컴포넌트 중 하나
- Message를 주고 받기 위한 객체
- Application component를 요청 : Activity, Service의 시작, Broadcast 전달
- 명시적 Intent와 암시적 Intent가 있다.
- 명시적 Intent : fully qualified class name을 제공. 내 앱 안의 Activity 또는 Service 시작(내 앱안에서 명시적으로 메시지를 보내고 싶을 때)
- 내가 필요한 클래스를 명시적으로 딱 찍을때
- 암시적 Intent : 필요한 작업(동작)을 선언. 해당 기능을 지원할 수 있는 앱을 선택 할 수 있도록 한다. 대상은 안 정해지고 하고 싶은 동작을 적는다. → “내가 카메라를 켤 때 (전면 카메라, 후면 카메라) 중 무엇을 켤것인가?” 라는걸 물어보는 것
- 다른 앱의 기능을 빌릴 때
- AndroidManifest.xml 에 선언된 Activity에 자신이 처리할 수 있는 일을 적는다
Intent : 애플리케이션 끼리 주고 받을 수 있는 메세지 같은 것
안드로이드 → 어플 , 어플 → 안드로이드, 어플 ↔ 어플
안드로이드 상에서 어플과 안드로이드 상에 끊임없이 주고 받는 메세지
대표적인 Broadcast : 와이파이가 꺼졌다 켜졌다, 배터리가 없다, 부팅이 완료되었다 , …..
Broadcast도 Intent이다.
명시적 Intent
- 프로젝트에 Activity 추가하기
- MainActivity가 포함된 패키지에서 우클릭
- New
- Activity
- EmptyActivity

- Activity Name : CourseActivity
→ CourseActivity.kt 생성됨
→ res/layout/activity_course.xml 자동 생성됨
→ AndroidManifest.xml 에 정보가 자동 추가됨

- CourseActivity에도 ViewBinding을 적용한다.
class CourseActivity : AppCompatActivity() {
private val binding by lazy { ActivityCourseBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
....
ViewCompat.setOnApplyWindowInsetsListener(binding.main) { v, insets ->
....
- activity_course.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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CourseActivity">
<TextView
android:id="@+id/textViewName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="이름 정보가 없습니다."
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textViewType"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="분반 정보가 없습니다."
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textViewName" />
<Button
android:id="@+id/buttonPhoto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="사진불러오기"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textViewType" />
<Button
android:id="@+id/buttonOk"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="확인"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/buttonCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="취소"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/buttonOk" />
<ImageView
android:id="@+id/imageViewPhoto"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/buttonCancel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/buttonPhoto"
app:srcCompat="@drawable/ic_account_circle" />
</androidx.constraintlayout.widget.ConstraintLayout>
- MainActivity에서 CourseActivity를 부르는 방법 :
- MainActivity의 onCreate 함수 끝 부분에 다음 코드 추가
맨 끝에 java를 붙이는 이유는 Intent는 Java로 구현이 되어져 있다. 그래서 intent를 Java 클래스 형식으로 전달을 함binding.buttonApply.setOnClickListener { val intent = Intent(this, CourseActivity::class.java) startActivity(intent) // 안드로이드 운영체제한테 해당 Activity를 실행하라고 요청 }
이제 MainActivity에서 buttonApply를 클릭하면 CourseActivity로 화면이 넘어가짐
- MainActivity의 onCreate 함수 끝 부분에 다음 코드 추가
- Activity Stack
- 생성된 Activity는 Stack에 쌓이며 가장 위의 Activity가 보인다.
- Back키를 누르거나 Activity 스스로 finish() 함수를 호출하면 종료된다.
- Activity는 싱글톤이 아니며 startActivity를 호출한 횟수만큼 생성된다
- 싱글톤 : 객체가 딱 하나만 만들어지는 것
- 만약 다른 Activty에서 뒤로 돌아가기를 할 때 finish()를 사용하지 않고, startActivity를 사용할 경우 또 Activity가 생성된다.
- CourseActivty의 onCreate 안에 finish()를 호출하여본다.
binding.buttonCancel.setOnClickListener { finish() }
→ buttonCancel 버튼이 눌리면 종료됨.
개발자가 임의로 Activity를 종료시킬수도 있음.
MainActivity에서 CourseActivity가 부를 때 startActivty() 를 사용하는데 해당 함수는 반환타입이 없다.
그래서 MainActivity에서 startActitvity를 하면 Main위에 뜨긴 뜨는데 Main에서는 Course가 띄워졌는지 안 띄워졌는지 알지를 못한다.
왜냐하면 Android가 Activtiy를 사용 관리하기때문에.
그러면 데이터를 어떻게 전달해 줄 것인가??? → Intent 사용
Activity간의 데이터 전달
- 단반향 : 호출 하는 Activity가 데이터 함께 전달하기
- intent에 데이터를 담아서 전달함
- MainActivity에서 사용자 이름, 분반(성인반/학생반)을 CourseActivity로 전달
- CourseActivity에서는 받은 데이터 꺼내기
MainActivity
binding.buttonApply.setOnClickListener {
val intent = Intent(this, CourseActivity::class.java)
intent.putExtra("name", binding.editTextName.text.toString()) // intent에 데이터를 담아서 넘김
intent.putExtra("value", 1234) // intent에 int를 담아서 넘김
startActivity(intent) // 안드로이드 운영체제한테 해당 Activity를 실행하라고 요청
}
intent.putExtra(name, object) 형식으로 데이터를 담는다.
CourseActivity
val str = intent.getStringExtra("name")!! // getStringExtra는 String? 타입임
val value = intent.getIntExtra("value", -1)
// int는 null이 불가하기에 default값을 줘야한다.
Log.d("CourseActivity", str)
Log.d("CourseActivity", "$value")

MainActivity
binding.buttonApply.setOnClickListener {
val type = if(binding.radioButtonAdult.isChecked) binding.radioButtonAdult.text
else binding.radioButtonStudent.text // true면 성인, false면 학생
val intent = Intent(this, CourseActivity::class.java)
intent.putExtra("name", binding.editTextName.text.toString())
intent.putExtra("type", type)
startActivity(intent) // 안드로이드 운영체제한테 해당 Activity를 실행하라고 요청
}
CourseActivity
val name = intent.getStringExtra("name")!! // getStringExtra는 String? 타입임
val type = intent.getStringExtra("type")!!
Log.d("CourseActivity", name)
Log.d("CourseActivity", type)

- 양방향 : 호출하는 Activity에서도 데이터를 주고 호출을 받은 Activity는 종료할 때 결과를 반환한다.

- 이 방법의 단점
- 하나의 Activity에서 여러 요청을 보낼 수 있는데 callback은 onActivityResult 함수 하나만 제공하여 코드가 복잡해진다.
- 요청한 데이터에 따라 다양한 결과가 있을 수 있는데 Intent의 data로만 전달되어 null check 등의 부가 코드가 많다.
setResult(), onActivityResult()는 현재는 사용하지 않음
Activity Result - Android Jetpack
- 기존 startActivityForResult + onActivityResult의 불편한 점을 개선한 클래스
- activity에 요청을 전달하고 그 결과용 callback을 함께 지정하는 객체를 사용한다.
- 명시적 Intent와 암시적 Intent에 모두 사용할 수 있으며 ActivityResultContract 라는 파라미터로 요청의 종류를 알린다.
https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContracts


다른 앱한테 부탁(카메라 등등)하는 코드도 모여있고, 내 앱한테 데이터를 부탁하는 StartActivityForResult도 만들어져있음.
MainActivity
class MainActivity : AppCompatActivity() {
private val getResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
Log.i("MainActivity", "${it.resultCode}")
} // Activity Result
// 첫 번째 파라미터인 ActivityResultContract의 종류에 따라 람다식의 파라미터 타입이 정해짐
...
binding.buttonApply.setOnClickListener {
...
getResult.launch(intent) // 안드로이드 운영체제한테 해당 Activity를 실행하라고 요청
}
CourseActivty
override fun onCreate(savedInstanceState: Bundle?) {
....
setResult(Activity.RESULT_CANCELED)
// setResult 함수는 두번째 파라미터로 Intent 타입의 받을 수 있으며 만약 intent를 넘길 경우
// resultLauncher의 람다식에서 it.data로 읽을 수 있다.
val name = intent.getStringExtra("name")!! // getStringExtra는 String? 타입임
val type = intent.getStringExtra("type")!!
Log.d("CourseActivity", name)
Log.d("CourseActivity", type)
binding.buttonOk.setOnClickListener {
setResult(Activity.RESULT_OK) // OK 버튼으로 종료할 때만 RESULT_OK를 전달
finish() // Activty를 종료시킴 - Stack에서 뺀다.
}
binding.buttonCancel.setOnClickListener { finish() }

상태코드가 미리 정의가 되어져 있음
암시적 Intent
- 필요한 작업(동작)을 적어 다른 앱의 기능을 불러 사용한다.
- 사진 추가 Button을 눌렀을 때 폰에 저장된 사진을 선택할 수 있게 하고 이를 내 앱에 출력해본다.
- Emulator를 사용하는 경우 이미지를 미리 다운받아 두거나 emulator의 카메라 앱으로 사진을 찍어둔다.
- ActivityResultContract 중 사진 한 장을 요청하는 Contract를 사용한다.
- ActivityResultContracts.GetContent
- 이 경우 람다식의 파라미터는 이미지의 Uri가 된다.
private val requestPhoto = // 이미지를 요청하는 코드
registerForActivityResult(ActivityResultContracts.GetContent()) {
binding.imageViewPhoto.setImageURI(it) // 보안때문에 실제 파일 경로는 알지 못함
// 가상의 주소로 이미지를 가져옴
}
override fun onCreate(savedInstanceState: Bundle?) {
....
binding.buttonPhoto.setOnClickListener { requestPhoto.launch("image/*") }


