[13. 액티비티 컴포넌트]
[13-1. 인텐트 이해]
[인텐트] : intent
-인텐트: 컴포넌트 실행 정보 담긴 객체
-인텐트 객체를 시스템에 전달하면 시스템에서 해당 인텐트 정보 분석 후 그에 맞는 컴포넌트를 실행하는 구조.
일반 클래스 | -개발자가 직접 생명주기 관리 O |
컴포넌트 클래스 | -시스템이 생명주기 관리O - 이 클래스에 한해서는 개발자가 직접 접근 불가능 시스템에 요청하고 시스템이 실행하는 구조로 접근만 가능 O |
-시스템은 런타임 때 ‘매니페스트 파일’ 참조하여 앱을 실행함
-> 컴포넌트(액티비티/서비스/브로드캐스트 리시버/콘텐츠 프로바이더)를 '매니페스트 파일'에 등록하여 알림. |
-따라서, 시스템에 사용할 컴포넌트를 ‘매니페스트 파일’ 통해서 먼저 알려야 함
-이제 인텐트 단위로 시스템에 요청해서 해당 컴포넌트 실행 가능한 상태됨.
➀ 매니페스트 파일에 ‘컴포넌트’ 등록
-‘액티비티’의 경우, 액티비티 클래스 하나당 <actiity> 태그 하나로 등록. name 속성 필수
➁ 인텐트를 시스템에 전달 : startActivity() 함수
[인텐트 ‘엑스트라 데이터‘]
-엑스트라 데이터 : 인텐트 안에 담는 부가 정보 데이터
-실행요청과 함께 전달할 데이터를 ’엑스트라 데이터‘로 인텐트에 담아서 전달함
<인텐트에 ’엑스트라 데이터 추가> : putExtra() 함수
putExtra(데이터식별자, 전달데이터) |
<‘엑스트라 데이터’ 가져오기> : get_Extra() 함수
-intent 객체로 접근하여 데이터 가져오는 함수를 ‘타입별’ 제공
getIntExtra() getStringExtra() getDoubleExtra() |
[화면 전환 후 되돌아오기 | ‘사후처리’]
-화면 전환 후 다시 되돌아온 화면에서 ‘사후처리’ 필요 여부에 따라 방법 나뉨
<인텐트를 ‘시스템’에 전달 방식> (3가지)
➀ startActivity | 사후처리 X |
➁ startActivityForResult (전통) | 사후처리 O (11버전 이전까지 사용) |
③ ActivityResultLauncher (최근 권장) | 사후처리 O (11버전 이상부터 권장) |
▶[전통 방식] : startActivityForResult() 함수 이용
* 사후처리: 현재의 액티비티에서 인텐트로 실행 후 결과 돌려받아야 할 때.
ⓐ 결과 돌려받을 액티비티 시작
startActivityForResult(intent, 10)
ⓑ 결과코드 지정 후 자동 화면 종료 finish()
intent.putExtra(“resultData”, “world”)
setResult(RESULT_OK, intent) //결과코드 지정
finish() //현재 액티비티 화면 자동 종료 시스템에 요청
<결과코드 지정> : setResult()
RESULT_OK, RESULT_CANCLELED 등 상수로 지정
ⓒ 결과 돌려받아서 사후처리 : onAtivityResult() 자동 호출
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
. . .
}
<함수 매개변수 설명>
-requestCode: 인텐트 시작한 곳에서 인텐트 구분 위한 코드
-resultCode : 인텐트 실행된 곳에서 돌려받은 결과 코드
-data : 인텐트 객체
▶[최근 권장] : ActivityResultLauncher
-Contract -> ActivityResultLauncher -> launch 구조
⇒ [Contract ]
-ActivityResultContract 상속받은 서브 클래스
<대표적인 Contract >
PickContact | 선택한 연락처의 Uri 획득 |
RequestPermission | 권한요청, 허락여부 파악 |
RequestMultiplePermissions | 여러 권한 동시 요청 |
StartActivityForResult | 인텐트 발생, 액티비티 실행 결과 획득 |
TakePicturePreview | 사진촬영 후 비트맵 획득 |
TakePicture | 사진촬영, 저장, 비트맵 획득 |
⇒ [ActivityResultLauncer]
-registerForActivityResult() 함수로 생성하는 객체
-이 함수 매개변수에 (Contract객체, Callback객체) 등록해줌
-Contract객체 : 실제 작업자 -Callback 객체: 결과 처리 |
⇒ [launch() ]
-launch() 함수 호출하는 순간 ActivityResultLaunch에 등록된 Contract 객체 실행됨
➀ ActivityResultLauncher 생성
-registerForAcitivityResult() 함수 안에 Contract, Callback 객체 등록하여 ActivityResultLauncher 객체 받음.
val requestLauncher :ActivityResultLauncher<Intent> = registerForActivityResult (
ActivityResultContracts.StartActivityForResult() ) //Contract 객체
{ //Callback 객체
val resultData = it.data?.getStringExtra(“result”)
binding.mainResultView.text= “result:$resultData”
}
➁ ActivityResultLauncher 실행
위에서 생성한 ActivityResultLauncher 객체를 launch() 로 실행시켜줌
val intent: Intent = Intent(this, DetailActivity::class.java)
requestLauncher.launch(intent)
[인텐트에 실행 대상 컴포넌트 정보 지정 방식]
➀ ‘명시적’ 인텐트
-내부 앱 컴포넌트 요청하는 인텐트 객체 만들 때 사용
-클래스 타입 레퍼런스 정보 활용한 인텐트
-매니페스트 파일에 액티비티 등록 시, android:name 속성만 선언함
val inent:Intent = Intent(this, DetailActivity::class.java)
➁ ‘암시적‘ 인텐트
-외부 앱 컴포넌트 요청하는 인텐트 객체 만들 때 사용
-인텐트 필터 정보를 활용한 인텐트
-매니페스트 파일에 액티비티 등록 시, name 속성 + <인텐트 필터> 설정 필요
<인텐트 필터> : <intent-filter> 태그 사용
-<activity> / <service> / <receiver> ’컴포넌트 등록 태그 하위‘에 작성 가능
다시 <인텐트 필터>의 하위에는 <action> / <category> / <data> 이용하여 정보 설정 가능
<action> | 컴포넌트 기능 나타내는 문자열 |
<category> | 컴포넌트 포함 범주 나타내는 문자열 |
<data> | 컴포넌트에 필요한 데이터 정보 |
▷인텐트 필터 대표 사용 예시
-MainActivity 는 사용자가 런처화면에서 ’앱 아이콘‘ 터치 시 실행되는 메인 액티비티
-결국, ’앱 아이콘‘은 런처화면의 앱 액티비티로 분류됨 (즉, 외부 앱)
-따라서 MainActivity 또한 매니페스트 파일에서 <intent-filter> 지정하여 사용함
<activityt 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>
[매니페스트에 선언된 컴포넌트 정보를 이용하는 방법]
➀ 인텐트의 프로퍼티 이용하는 방법
val intent = Intent()
intent.action = “ACTION_EDIT” //기능 정보 사용
intent.data=Uri.parse(“http://www.google.com”) //데이터 정보 사용
startActivity(intent)
➁ 인텐트의 생성자 이용하는 방법
var intent = Intent(“ACTION_EDIT”, Uri.parse(“http://www.google.com”))
startActivity(intent)
[액티비티 인텐트 동작 방식]
1) 인텐트로 실행할 액티비티 1개 : 정상 실행
2) 인텐트로 실행할 액티비티 X : 오류 발생
-> try-catch() 구문으로 ’예외 처리‘ 하여 실행
val intent = Intent(“ACTION_HELLO”)
try { startActivity(intent)
} catch(e:Exception) {
Toast.makeText(this, “no...app”, Toast.LENGTH_SHORT).show()
}
3) 인텐트로 실행할 액티비티 n개: 사용자 선택 or 패키지 지정 필요
ⓐ 사용자 선택
-다이얼로그 띄워서 사용자에게 어떤 앱 액티비티 실행할지 묻고 선택한 액티비티 실행함
ⓑ 해당 앱의 패키지명 지정 : setPackage(“ ”)
[패키지 공개 상태]
-안드로이드 11 버전 이상부터 앱 패키지 공개상태 지정 안하면 외부 앱 정보 접근 불가능
<패키지 공개 상태에 따라 영향 받는 함수들>
PackageManager.getPackageInfo() |
PackageManager.queryIntentACtivities() |
Intent.resolveActivity() |
PackageManager.getInstalledPackages() |
PackageManager.getInstalldeApplications() |
bindService() |
<매니페스트 파일에 외부 앱 정보 접근 선언 필수>
➀ 특정 외부 앱에 접근 선언 (권장)
-<queries> 태그의 하위에 <package> 태그 이용 -> 접근할 앱 패키지명 선언
<manifest . . .
<queries>
<package android:name=“com.example.test_outter”/>
</queries>
. . .
</manifest>
➁ 모든 외부 앱에 접근 선언
-<uses-permission> 태그로 선언
<uses-permission android:name = “anroid.permission.QUERY_ALL_PACKAGES”/>
[13-2. 액티비티 생명주기]
* 생명주기 : 액티비티 생성 ~ 소멸 과정
[액티비티 상태]
활성 | 액티비티 화면 출력 O. 사용자 이벤트 처리 O (포커스 얻음) |
일시 정지 | 액티비티 화면 출력 O. 사용자 이벤트 처리 X (포커스 잃음) |
비활성 | 액티비티 화면 출력 X. 종료된 건 X 단지 비활성 상태 |
⇒[활성상태] : onCreate() -> onStart() -> onResume() 까지 호출된 상태
onCreate() -> onStart() -> onResume() 까지 호출 |
⇒[일시정지 상태] : -> onPause() 까지 호출된 상태
다시 포커스 얻으면 -> onResume() 함수 자동 호출돼서 활성상태로 되돌아감
-> onPause() 까지 호출 |
⇒[비활성 상태] ->onPause() -> onStop() 까지 호출된 상태
다시 액티비티 화면 보이면 -> onRestart() -> onStart() -> onResume() 까지 호출됨
>onPause() -> onStop() 까지 호출 |
⇒[액티비티 종료] ->onDestroy() 까지 호출된 상태
->onDestroy() 까지 호출 |
<액티비티 종료되는 경우> -안드로이드 12버전부터 루트 액티비티 아닐 때 ’뒤로가기‘ 버튼 누를 경우 -finish() 함수를 내부적으로 호출하는 상황 -화면 회전 |
[액티비티 상태 저장] : Bundle 객체 사용
-액티비티 종료 후 다시 실행시 복원 필요한 데이터를 Bundle 객체로 담아둠
<’상태 저장‘ 필요한 대표 사례 | 화면 회전>
| 액티비티 실행 상태
onCreate() -> onStart() -> onResume()
| ‘화면 회전’ 시. 액티비티 종료됐다가
-> onPause() -> onStop() -> onSaveInstanceState() -> onDestroy()
| 다시 액티비티 객체 자동 생성되어 회전’된 액티비티가 화면에 출력됨
-> onCreate() -> onStart() -> onRestoreInstanceState() -> onResume()
▷[‘Bundle 객체’ 를 매개변수로 갖는 생명주기 함수]
-번들 객체 이용하여 데이터 저장/복원할 수 있음
onCreate (savedInstanceState: Bundle) |
onSaveInstanceState (outState: Bundle) |
onRestoreInstanceState (savedInstanceState: Bundle) |
▷[Bundle 객체에 데이터 저장] | putString() , putInt() 함수
-개발자가 ‘번들’ 객체에 데이터 담아두면 자동으로 데이터를 ‘자체 캐싱 파일’ 에 저장됨
-그리고 다시 액티비티 실행 될 때 ‘캐싱 파일’ 존재하면 해당 내용 읽어서 번들객체에 담아
onCreate(), onRestoreInstanceState()의 매개변수에 담아 전달해줌
override fun onSavedInstanceState(outState : Bundle) {
super.onSavedInstanceState(outState)
outState.putString(“data1”, “hello”)
outState.putInt(“data2”, 10)
}
▷[Bundle 객체에 저장된 데이터 가져오기] | getString(), getInt() 함수
-액티비티 종료되었다가 다시 실행될 때, Bundle 객체를 통해 데이터 계속 유지 가능
[13-3. 액티비티 제어]
[소프트 키보드 제어]
*소프트 키보드 : 시스템 제공 키보드
-기본 동작 : 사용자가 액티비티 화면 속 글 입력 뷰에 포커싱하는 순간 자동 올라옴 : 사용자가 뒤로가기 버튼 누르는 순간 자동으로 사라짐 |
▷[입력 매니저] | InputMethodManager 클래스
-코드에서 ‘특정 순간’ 키보드 제어 함수 제공하는 InputMethodManager 클래스 사용
hideSoftInputFromWindow() | 화면에서 키보드 사라짐 |
showSoftInput () | 화면에 키보드 나타나게 함 |
toggleSoftInput () | 키보드를 토글방식으로 제어함 |
<InputMehtodManager 소속 함수들>
*주의* showSoftInput(View view, int flags) 의 첫 번째 매개변수는 글 입력될 뷰인데 이 뷰 포커싱 있는 상태에서만 키보드 나타날 수 O requestFocus()로 해당 뷰에 강제 포커싱 후 -> showSoftInput() 사용할 것 |
[액티비티 화면 속 ‘입력모드’ 설정]
-액티비티 화면에서 ‘소프트 키보드’ 올라올 때 액티비티 화면 상태 조정
-필수 설정 X. 일반적으로 시스템에서 액티비티 상태 알아서 조정해주기 때문
-<단, 개발자가 직접 설정 원할 경우>
|매니페스트 파일에 <activity> 태그의 windowSoftInputMode 속성 이용해야 함
[액티비티 방향 설정]
<activity android:screenOrientation 속성에 값을 줌>
landscape | 가로 고정 |
portrait | 세로 고정 |
[액티비티 ‘전체 화면‘ 설정]
-전체 화면 설정 : 액션바 조차 출력되지 X. 오직 콘텐츠 화면으로 꽉 채운 화면 의미
➀ 액티비티 적용 테마에 액션바 출력 안되도록 설정 | NoctionBar 테마 ➁ 액티비티 코드에서 전체화면 출력 코드 설정 - WindowInsetsController 클래스의 함수 이용 |
<style name="Theme.AndroidLab" parent="Theme.MaterialComponents.DayNight.NoActionBar">
- API 레벨 29 까지는 window.setFlags() 함수로 사용했으나,
- API 레벨 30 이상부터는 WindowInsetsController 클래스 함수 이용하여 액티비티 창 설정함
-> 따라서, API 호환성 고려하여 코드 작성 필수
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.setDecorFitsSystemWindows(false)
val controller = window.insetsController
if(controller != null) [
controller.hide(WindowInsets.Type.statusBars() or
WindowInsets.Type.navigationBars() )
controller.systemBarBehavior=
WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
}else {
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN)
}
[13-4. 태스크 관리]
*태스크 관리 : ‘액티비티 생성/관리 방식’ 제어하는 일
-기본적으로 시스템이 제공하는 액티비티 태스크 관리 ‘기본규칙’ 존재 O
-다만, 개발자가 별도로 코드 작성하여 액티비티 태스크 제어 O
*액티비티 태스크 : 앱 실행될 때, 시스템에서 액티비티 각종 정보 저장하는 공간
▶ [ ‘시스템에서’ 태스크 관리 ]
* ‘태스크’ : 사용자 관점에서 프로그램의 논리적 실행 단위
-시스템은 액티비티 실행 정보를 저장하기 위해 ‘태스크’를 만든다.
-사용자 관점에서의 하나의 앱 실행 단위를 태스크 정보에 묶어 저장하는데, 실제로는 진짜 앱 하나가 아니라, 앱 2개 연동된 구조일 수도 있음. 그래서 사용자 입장에서의 논리적인 실행 단위라고 하는 것. |
➀ <진짜 1개의 앱 실행 | 태스크 1개>
➁ <실제로는 2개 앱 연동되어 실행 | 태스크 1개>
-액티비티는 인텐트 발생 시 무조건 객체 생성 -이미 같은 액티비티 실행된 상태라 같은 객체가 있더라도, 다시 인텐트 발생 시 해당 객체 생성 후 태스크 정보에 등록된다. 즉, 태스크 속 액티비티 객체들은 모두 사용자 관점에서의 논리적 실행 단위라는 것. |
▶ [ ‘개발자’가 태스크 제어 ]
-개발자가 원하는 대로 액티비티 객체 생성 후 태스크에 등록, 제어하는 방식
방법 ➀. 매니페스트 파일 속 <activity> 태그의 launchMode 에 속성값 지정 방법 ➁. 코드에서 인텐트의 flags 속성 정보 설정 제어 |
방법. ➀ <매티페스트 파일에서 launchMode로 제어>
<activity android:name=“.TwoActivity” android:launchMode=“singleTop”>
<launchMode 설정 가능 속성값 종류> : 액티비티에서 인텐트 발생 시
standard (기본값) | ‘스탠더드’ | 항상 객체 생성 -> 태스크에 등록 |
singleTop | ‘싱글탑‘ | 태스크 위쪽에 있는 액티비티의 경우 중복 등록 X 대신 기존 객체의 onNewIntent() 함수 자동 호출됨 |
singleTask |‘싱글 태스크’ | 다시 새로운 태스크 만들어 등록 |
singleInstance |‘싱글 인스턴스’ | 해당 설정 적용된 액티비티 하나만 새로운 태스크 만들어 등록 |
방법. ➁ <코드에서 인텐트 flags 속성에 설정>
val intent = Intent(thism TwoActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP //속성 설정
startActivity(intent)
[13-5. 액티비티 ANR 문제와 코루틴]
[ANR 문제]
-ANR (Activity Not Respaonse) 문제 : 액티비티가 사용자 이벤트에 반응X 오류
-시스템에서 액티비티 실행함
-시스템의 수행 흐름에서 시간 오래 걸리는 작업 끝나지 않으면, 사용자 이벤트에 반응 X
(ex, 서버 통신 네트워크 작업 등)
*UI 스레드(메인 스레드) : 시스템에서 화면 액티비티 출력하는 수행 흐름 일컫는 말
[ANR 문제 해결 방법]
ⓐ 네트워크 통신 지원 전문 라이브러리 사용
-라이브러리 내부적으로 ANR 문제 해결. 개발자가 ANR 문제 고려X
ⓑ 오래 걸리는 작업 -> 별도의 개발자 ‘스레드’ 만들어서 위임
-이 경우, ANR 문제는 해결되지만, 화면 처리는 메인 스레드가 주관하기 때문에
결과 출력 시 다시 ‘메인 스레드’에 의뢰하여 처리.
-개발자 스레드 자체에서는 UI 변경 처리 불가하기 때문.
ⓒ [‘코루틴’으로 ANR 오류 해결]
- 코루틴(coroutine) : 코틀린 언어가 제공하는 ‘비동기 경량 스레드’ 기능
- 비동기적으로 여러 작업을 처리함.
<코루틴 장점> -경량 -메모리 누수 적다 -다양한 기능 지원 -多 제트팩 라이브러리에 이미 적용되어 있음 |
[안드로이드에서 ‘코루틴’ 이용]
① build.gradle 파일 dependencies 항목에 ‘코루틴’ 사용 등록
implementation ‘org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9’
② 코루틴 구동을 위해 ‘스코프(Scope)’ 준비 : 스코프에서 코루틴을 구동함
*스코프(scope) : 성격이 같은 여러 코루틴을 묶는 개념
*코루틴 스코프 :CoroutineScore 구현 클래스의 객체
③코루틴 만들면서 ‘디스패터 지정’
*디스패처 : 해당 스코프에서 구동한 코루틴이 어디에서 동작해야 하는지 나타냄
*Channel 클래스 : 코루틴 값 전달받을 수 있는 방법 제공
send(): 코루틴에 데이터 전달 | receive(), consumeEach() : 코루틴이 데이터 받음
Dispatchers.Main | 메인 스레드에서 동작. (UI 변경작업도 여기서 가능) |
Dispatchers.IO | 파일 읽기/쓰기/네트워크 작업에서 동작 |
Dispatchers.Default | 복잡하고 시간 잡아먹는 작업 실행 |
<코루틴으로 작성한 소스>
val channel = Channel<Int> () //Chennl 은 코루틴 값 전달받을 수 있는 방법 제공 클래스.
//스코프 (backgroundScope) 만듬 -> 동시에 지정한 디스패처 (.Default) 즉. 복잡한 작업 여기에 묶음
val backgroundScope = CoroutineScope(Dispatchers.Default + Job() )
backgroundScope.launch {
var sum = 0L
var time = measureTimeMillis {
for(i in 1..2_000_000_000) {
sum += I
}
}
Log.d(“kkang”, “time : $time”)
channel.send(sum.toInt())
}
//스코프 (mainScope) 만듬 -> 동시에 지정한 디스패처 (.Main) 즉, 메인 스레드 동작 여기에 묶음
val mainScope = GlobalScope.launch(Dispatchers.Main) {
channel.consumeEach {
binding.resultView.text = “sum: $it” //액티비티 출력 O
}
}
[13-6. 할 일 목록 만들기] : 실습
[참고] : Do It 안드로이드 앱 프로그래밍 with 코틀린 |
'App(앱)_관련 공부 모음 > [교재] Andorid App_Kotlin 기반' 카테고리의 다른 글
15. [서비스 컴포넌트] (0) | 2022.03.24 |
---|---|
14. [브로드캐스트 리시버 컴포넌트] (0) | 2022.03.23 |
12. [머티리얼 라이브러리] (0) | 2022.03.16 |
11. [제트팩 라이브러리] (0) | 2022.03.14 |
10. [다이얼로그와 알림 이용] (0) | 2022.03.10 |