액티비티를 관리하는 스택, 가장 나중에 적재되는 액티비티가 가장 먼저 사용.
최초로 적재된 액티비티를 root activity, 마지막에 적재된 액티비티를 top activity.
Flag로 액티비티들의 흐름을 제어
테스트 주도 개발 방법론
테스트 코드를 먼저 작성 후, 짧은 개발 사이클을 반복하는 방법
목표: 잘 작동하는 깔끔한 코드를 작성하는 것
software development kit 소프트웨어 개발 도구 모음
개발에 도움이 될 개발 도구 프로그램, 디버깅 프로그램, 문서, API 등이 있다.
application programming interface
응용프로그램들 사이에 데이터를 주고 받는 방법.
어떤 특정 사이트에서 특정 데이터를 제공할 때 이 정보들을 어떠한 방식으로 요청해야 하는지, 어떤 정보를 제공하는지에 대한 규격
내부 동작을 알 필요 없이 간편하게 개발을 할 수 있음.
예) 네이버 지도 API, 기상청 API
둘 다 리스트를 보여주는 화면이라는 점에서 동일. 데이터가 저장되어 있는 아이템들을 리스트 형태로 화면에 출력하며, 스크롤 기능을 통해 모든 데이터들을 보여줍니다.
리스트뷰는 스크롤을 내려 새로운 아이템이 화면에 보여져야 될 때마다 새로운 객체를 생성하기 때문에 이로 인해 과부하가 온다는 단점이 있어 이를 보완하고자 만들어진 게 리사이클러뷰. 리사이클러뷰는 새로운 아이템이 화면에 보여져야 할 때 화면에 보여지지 않는 이전에 사용했던 객체를 재사용하는 방식
액티비티는 독립적으로 사용되고 전체 화면을 차지
프래그먼트는 액티비티에 종속되고 전체화면을 차지하지 않아도 되기 때문에 화면 디자인에 유연성을 가짐
하나의 액티비티에서 여러가지의 화면을 보여주고 싶을 때 사용되는게 프래그먼트
프래그먼트가 차지하는 공간은 재사용될 수 있는 장점
application not responding
메인 스레드가 일정시간 블로킹 되면 anr 오류를 띄웁니다.
메인 스레드를 어디선가 이미 점유하고 있다면 입력 이벤트를 전달하지 못하는데, 이 시간이 5초 이상 지연되면 anr을 발생
브로드캐스트 리시버 동작이 10초 이내에 완료하지 못했을 때
다이얼로그는 사용자에게 추가 정보를 입력 또는 결정을 내리도록 할 때 표시하는 작은 화면입니다.
토스트 메시지는 작은 팝업으로 메시지에 필요한 공간만 차지하고 사용자와의 상호작용이 유지됩니다.
일정시간이 지나면 자동으로 사라집니다.
스낵바는 토스트 메시지와 유사하지만 사용자가 메시지에 응답할 수 있습니다.
메시지 객체로 컴포넌트들 사이에서 정보를 주고받을 수 있습니다.
Intent types
IntentFilter
메니페스트에 선언해서 사용하고, 암시적 인텐트를 통해 사용자가 어떤 종류의 앱을 사용할지 선택하도록 할 때 필요합니다.
카메라 앱을 실행하려 했을때 디바이스에 설치된 모든 카메라 앱 중 하나를 선택할 수 있는 대화 상자가 표시되고, 사용자는 그 중 하나를 선택해서 앱을 실행할 수 있는 상황이 이 인텐트 필터의 예시입니다.
PendingIntent
인텐트를 가지고 있는 클래스로, 당장 수행하지 않고 특정한 시점에 수행
특정한 시점이란 보통 앱이 실행되고 있지 않는 상태를 의미
푸시알림을 탭하면 해당 앱으로 다시 복귀하는 동작이 pendingintent의 예시
다른 프로세스에게 권한을 부여해서 인텐트를 마친 자신의 앱에서 실행되는 것처럼 해줌.
멀티 스레드로 UI를 업데이트하면 동일한 UI 자원을 사용할 때 교착상태, 경쟁상태 등 문제가 발생할 수 있다.
메인 스레드에서만 UI 업데이트 허용
UI 스레드로 부르기도 함
앱이 시작되면 메인 스레드가 생성
Activity Thread가 메인 클래스, main() 앱의 시작지점
main() 함수에서 메인 루퍼가 생성되고 루프가 시작
루퍼의 loop() 무한 반복문을 가지고 있기 때문에 프로세스가 종료될 때까지 mian()는 종료되지 않습니다.
하나의 스레드에는 하나의 루퍼만 가지고, 루퍼도 하나의 스레드만 관리합니다.
루퍼의 내부에는 메시지 큐가 존재하는데, 해당 스레드가 처리해야 할 동작들이 메시지라는 형태로 큐에 FIFO 방식으로 쌓이게 됩니다. 무한 루프를 돌면서 큐에 쌓인 메시지들을 하나씩 꺼내서 핸들러에 전달하는 역할
메시지를 루퍼의 메시지 큐에 넣는 기능과 처리하는 기능을 제공
Handler 생성하면 자동으로 해당 스레드의 루퍼와 연결되고, 메시지를 전달하고 처리할 수 있게 됩니다.
애플리케이션 환경에 대한 전역 정보, 여러 컴포넌트들의 상위 클래스.
싱글턴 인스턴스, 애플리케이션 라이프사이클에 묶여있다.
액티비티 라이프사이클과 연결되어 있습니다.
액티비티 컨택스트는 액티비티와 함께 소멸해야 하는 경우에 사용합니다.
xml에 정의된 리소스를 view 객체로 반환
onCreate() setContentView() 또한 inflater 역할
이 함수 내부에서 LayoutInflater가 실행되어 view들을 객체화
inflate() 3가지 인자 (객체화 하고자 하는 xml 파일, 객체화 된 뷰를 붙일 부모의 레이아웃이나 컨테이너, boolean)
true -> 부모 view에 바로 붙인
flase -> 나중에 붙인(parent.addView() 처럼 부모 뷰에 현재 뷰를 붙이는 모든 코드)
프래그먼트 attachToParent를 true Exception 발생
보통의 구현에서는 프래그먼트 매니저가 프래그먼트를 자동으로 add() 해주기 때문에 이미 붙은 프래그먼트를 중복해서 붙이려고 하는 상황으로 Exception 발생
직접 뷰를 붙여야 한다는 확신이 없다면 true 해서는 안된다.
아니다.
디바이스의 메모리가 부족해 우선순위가 낮은 프로세스를 종료해야 한다면 onPause에서도 종료가 될 수 있기 때문에 무조건 onStop(), onDestroy()가 호출된다는 보장이 없다.
뷰는 화면의 구성요소, 화면에 보여지는 모든 것이 뷰
뷰에는 화면 어디에 배치 되어야 하는지에 대한 정보가 없기 때문에, 뷰만으로는 화면에 나타날 수 없음.
이때 뷰그룹이 필요함.
뷰그룹은 뷰를 상속한 클래스, 뷰들을 가지고 있고, 레이아웃이라고도 함.
뷰 그룹은 뷰를 배치하고, 뷰 그룹 또한 뷰이기 때문에 자식 뷰 그룹을 배치할 수 있음.
안드로이드에서 사용할 수 있는 데이터 저장소
데이터를 xml 파일로 저장, 파일이 앱 폴더 내에 저장되므로 앱이 삭제되면 같이 삭제됨.
key-value 형식으로 primitive 타입만 저장할 수 있음.
데이터의 양이 많거나 중요한 정보라면 서버나 DB를 이용해 저장하는 것이 좋겠지만, 간단한 설정 값이나 문자열 같은 데이터는 SharedPreferences로 저장하는게 더 효율적.
예) 로그인 여부 확인
Jetpack에서 지원하는 라이브러리로, LifeCycle을 통해 생명 주기를 인식하는 데이터 홀더 클래스.
LifeCycleObserver를 통해 LiveData는 UI Controller의 LifeCycle를 옵저브하고, UI Controller는 LiveData 값의 변경을 옵저브함.
data 값의 변경 알림은 LifeCycleOwner가 STARTED, RESUMED인 활성 상태에만 받음.
observeForever()는 항상 활성 상태로 간주하여 항상 알림을 받을 수 있도록 함.
removeObserver()로 옵저버를 제거함.
장점
변경 가능한 LiveData로 값을 setting하는 setValue(), postValue() 함수가 있음.
setValue()는 메인 스레드에서 동기적으로 데이터 값을 변경합니다.
순서를 보장하고, 변경 값이 즉시 반영되어야 한다면 setValue()를 사용.
postValue()는 백그라운드 스레드에서 비동기적으로 데이터 값을 변경.
순서를 보장할 필요가 없거나, 메인 스레드를 블로킹하지 않아야할 때 사용.
따라서 메인 스레드에 적용되기 전에 값에 접근을 한다면 반영이 되지 않을 수도 있음.
MutableLiveData는 read, write이 가능하고, LivaData는 read만 가능.
수정이 가능한 MutableLiveData는 private로 선언해 외부에서 변경할 수 없게 막고,
외부에서 값을 가져올때는 읽기만 가능한 LivaData에 접근해 사용.
액티비티나 프래그먼트에서 UI 데이터 값을 변경하는 것은 MVVM 아키텍쳐에 위배되는 행동
LiveData는 생명주기를 알고 있기 때문에, observer를 자동으로 관리.
ObservableField는 생명주기를 알지 못하기 때문에, 수동으로 관리해줘야 함.
따라서 메모리 누수면에서 LiveData가 더 좋다.
전역 앱 상태를 유지하기 위한 기본 클래스로, 애플리케이션 안에서 공동으로 멤버 변수나 메서드를 사용할 수 있도록 해주는 공유 클래스.
Application Class 또는 Application Class의 하위 클래스들은 앱이 실행될 때 가장 먼저 인스턴스화 됨.
Context를 이용해서 접근 가능
서버와 통신을 하기 직전 직후에 요청/응답을 가로채서 추가적인 작업을 한 후에 원래의 흐름으로 다시 되돌려주는 기능.
모니터링을 한다거나 원하는 값을 끼우는 등의 작업을 할 수 있음.
Application Interceptor는 Application 과 OkHttp Core 사이에서 요청/응답을 가로채는 역할
Network Interceptor는 서버와 OkHttp Core 사이에서 요청/응답을 가로채는 역할
Intent는 저장이 아닌 전달하는 수단의 객체, Bundle은 상태/값 등을 저장하기 위한 객체
프로그램에서 필수적.
코드를 실행하기 전에 시스템이 반드시 알아야 하는 애플리케이션의 정보를 포함하는 곳.
View.GONE: 어댑터의 getView()를 호출하지 않아 뷰를 그리지 않아, 레이아웃에 공간을 차지하지 않음.
View.INVISIBLE: 뷰를 그려놓고 보이지는 않지만 레이아웃에 공간을 차지함.
앱 실행 중 프로세스가 강제 종료 되었다고 했을 때, Service는 onStartCommand의 반환값에 따라서 강제 종료된 Service를 시스템이 다시 자동으로 시작하게 할 수 있음.
Thread는 시스템이 다시 복구시켜 주지 않음.
Thread는 하나의 프로세스 내에서 Thread간 메모리를 공유하지만 다른 프로세스에 접근할 수 없음.
Service는 Bind 방식으로 구현을 하면 다른 프로세스와의 통신도 가능.
Thread는 사용자와 상호작용하는 과정에서 메인 스레드를 블록 하지 않기 위한 작업을 하는 포그라운드 작업에 적합하고, Service는 사용자와 상호작용하지 않아도 계속 수행되어야 하는 백그라운드 작업에 적합.
화면 없이 백그라운드에서 동작하는 컴포넌트.
기본적으로 메인 스레드에서 동작하기 때문에 별도로 스레드를 만들어서 작업을 수행해야 함.
startService()
서비스가 한번 시작되면 무기한으로 백그라운드에서 동작.
생명주기는 onCreate() -> onStartCommand() 순으로, Service가 실행되는 도중에 다시 한 번 startService()가 호출되면 onStartCommand()부터 실행됨.
onStartCommand의 리턴타입 3가지
startService()를 통해 intent를 넘겨주는데, onStartCommand에서 이 intent를 받습니다.
bindService()
바인드 서비스를 실행할 때 호출하는 메서드
생명주기는 onCreate() -> onBind() -> onUnBind() -> onDestroy()
서비스 바인딩 객체를 생성하려면 onBind()를 구현해야 함.
onBind()는 IBinder를 반환하는데, 이 객체가 서비스와 클라이언트 사이의 인터페이스 역할.
클라이언트가 bindService() 호출하면 클라이언트가 서비스에 연결되면서 IBinder가 반환되고, 클라이언트가 IBinder를 통해 정보를 주고 받을 수 있음.
서비스의 서브 클래스로, Worker Thread를 사용하여 모든 시작 요청을 처리하되, 한 번에 하나씩 처리함. 요청한 작업이 완료되면 자동적으로 서비스를 중단.
모두 문자열을 저장하고 관리하는 클래스
String 객체는 한번 생성되면 할당된 공간이 변하지 않지만, StringBuffer와 StringBuilder의 경우 객체의 공간이 부족해지면 버퍼의 크기를 유연하게 늘려줌.
"hello" 값이 들어가있던 String 클래스의 참조변수 str이 "hello world"라는 값을 가지고 있는 새로운 메모리 영역을 가리키게 되고 hello 값이 할당되어 있던 메모리 영역을 가비지로 남아있다가 GC(가비지 콜렉션)에 의해 사라지게 된다. String 클래스는 불변하기 때문에, 문자열을 수정하는 시점에 새로운 String 인스턴스가 생성.
문자열 연산이 빈번하게 발생하는 경우 String 클래스를 사용하면 힙 메모리에 많은 가비지가 생성되어 메모리 부족으로 애플리케이션의 성능에 영향을 미칠 수 있음.
StringBuffer와 StringBuilder는 동일 객체내에서 문자열을 변경.
따라서 문자열 연산이 많은 경우라면 String 클래스가 아닌 StringBuffer/StringBuilder를 사용하는 것이 좋음.
동기화 지원의 유무 차이.
StringBuffer는 메소드에 synchronized 키워드가 존재하여 멀티 스레드 상태에서 동기화를 지원하고, StringBuilder는 동기화를 지원하지 않음.
String: 문자열 연산이 적고, 멀티 스레드 환경일 경우(항상 새로운 객체가 생성되기 때문에 동기화를 고려하지 않아도 됨.)
StringBuffer: 문자열 연산이 많고 멀티 스레드 환경이 경우
StringBuilder: 문자열 연산이 많고 단일 스레드거나 동기화를 고려하지 않아도 되는 경우
StringBuilder
FragmentManager란 앱의 프래그먼트를 더하고, 삭제하고, 교체하는 역할을 하는 클래스.
모든 FragmentActivity와 이것의 서브 클래스(액티비티)는 getSupportFragmentManager로 FragmentManager에 접근할 수 있음.
프래그먼트 내에서 자식 프래그먼트를 관리하는 FragmentManager에 접근하기 위해서 getChildFragmentManager()를 사용. 자식 프래그먼트에서 부모의 FragmentManager에 접근하기 위해서 getParentFragmentManager()를 사용.
프래그먼트는 FragmentActivity를 상속받지 않기 때문에 getSupportFragmentManager를 사용할 수 없음.
Fragment를 상속받아 getChildFragmentManager()와 getParentFragmentManager()만 있다.
FragmentActivity는 Activity의 서브 클래스로, 안드로이드의 오래된 버전과의 호환성을 보장하기 위해 추가적인 메서드를 제공
뷰가 화면에 붙여질 때 호출되는 함수.
드로잉 할 표면이 있는 상태, 리소스를 할당하거나 리스너를 설정할 수 있음.
뷰가 화면에 떨어질 때 호출되는 함수.
드로잉 할 표면이 없는 상태, 리소스를 해제하고 모든 작업을 중단해야함.
뷰의 전개가 모두 끝났을 때 호출됨.
모든 자식 뷰가 화면에 추가 된 후 호출된다.
뷰의 계층 구조는 자식 뷰와 뷰그룹으로 이루어진 트리 구조이기 때문에, 각 메서드는 부모에서 시작해서 마지막 노드까지 전위순회하면서 제약조건을 정의합니다.
뷰의 크기를 측정하는 단계
뷰 그룹의 경우 계속해서 각 자식 뷰에 대한 측정을 하고 그에 대한 결과로 자신의 사이즈를 측정한다.
MeasureSpec를 통해 크기를 측정하는데, 크기에 대한 정보와 mode에 대한 정보를 담고있다.
부모뷰에서 자식뷰로 MeasureSpec를 넘겨준다.
MeasureSpec.EXACTLY: 정확한 사이즈가 정해진 상태, 정해진 사이즈 안에서 원하는 크기를 가질 수 있음.
MeasureSpec.UNSPECIFIED: mode가 설정되지 않은 경우며, 원하는 크기를 가질 수 있음.
MeasureSpec.AT_MOST: 주어진 사이즈에서 원하는 크기를 가질 수 있음. wrap_contentd
뷰의 위치와 크기를 할당합니다.
앞에서 정해진 크기와 위치를 이용해서 뷰를 실제로 그리는 단계.
Canvas와 Paint 객체를 이용해서 필요한 내용을 그림.
이 메서든 빈헌하게 호출되고, 호출 시 많은 시간이 걸리기 때문에, 이 곳에서 객체를 생성하는 일은 피하고 한 번 할당한 객체를 재사용하는 것이 좋음.
뷰의 크기나 위치 변경 없이, 뷰의 그래픽적인 요소만 바뀌었을 때 사용.
onDraw()를 재호출하여 뷰를 업데이트함.
뷰의 크기, 위치가 변경되었을때 호출.
onMeasure()부터 시작해서 뷰를 다시 그림.
https://rkdxowhd98.tistory.com/164
https://beomseok95.tistory.com/249
https://gyubgyub.tistory.com/87
https://velog.io/@changhee09/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%BB%A4%EC%8A%A4%ED%85%80-%EB%B7%B0-%EC%9E%91%EC%84%B1
아니요
다이얼로그는 Activity가 아니기 때문에 Activity Stack에 쌓이지 않습니다. 그렇기 때문에 다이얼로그가 띄워진다고 현재 Activity를 정지시키지 않습니다.
2022-11-28 01:07:57.217 29613-29613/com.example.activitylifecycle D/MainActivity: onCreate
2022-11-28 01:07:57.443 29613-29613/com.example.activitylifecycle D/MainActivity: onStart
2022-11-28 01:07:57.448 29613-29613/com.example.activitylifecycle D/MainActivity: onResume
2022-11-28 01:07:59.416 29613-29613/com.example.activitylifecycle D/MainActivity: onPause
2022-11-28 01:08:08.059 29613-29613/com.example.activitylifecycle D/MainActivity: onResume
네.
권한을 요청 시 requestPermissions() 메소드를 호출해야 합니다.
권한을 허용하고 있지 않은 경우 권한 요청을 하는 UI를 띄운다고 되어있습니다.
이 메소드(requestPermission)는 사용자에게 어떠한 권한을 허용하고 거절할지에 대한 Activity를 띄울 수 있다고 공식문서에 명시되어 있음.
Activity로 실행이 되어야 onRequestPermissionResult 오버라이드 함수가 호출되어 권한이 허용되었는지 거절되었는지에 대한 결과 값을 받아 올 수 있습니다.
https://programmar.tistory.com/46
참고로 Head up notification 또한 onPause를 호출시키지 않습니다. 그러나 Dialog Theme으로 구현된 Activity는 액티비티를 상속받으므로 현재 액티비티 스택에 쌓이므로 현재 Activity의 onPause를 호출시키게 됩니다.
개발자가 더욱 더 편리하고, 쉽고, 빠르게 높은 퀄리티의 앱을 개발하도록 돕는 모음 도구.
https://velog.io/@eoqkrskfk94/android-Jetpack
jetpack에서 아키텍쳐에 해당하는 부분으로 테스트와 유지보수가 쉬운 안드로이드 앱을 디자인할 수 있도록 돕는 라이브러리 모음.
AAC를 활용하면 MVVM 구조로 앱 설계가 가능해짐.
https://velog.io/@twaun95/Android-JetPack-AAC
MVVM 패턴은 View, ViewModel, Model(Repository)을 분리해 뷰와 모델간의 의존성을 줄여준다.
일단 MVC의 경우에는 안드로이드에서 적용할 때 View와 Controller가 Activity에서 모두 처리되어야하기 때문에 Activity가 커지는 문제가 있어서 관심사의 분리가 비교적 원활하지 않다고 여겨졌다.
MVP는 Presenter가 뷰와 1대1로 동작하기 때문에 뷰와 프레젠터의 의존성이 강해지는 문제가 발생하고 이에 따라 종종 프레젠터의 로직이 비대해지는 문제가 발생하기도 했다.
뷰와 모델의 관심사를 충분히 분리할 수 있고, 화면회전 등의 동작으로 뷰가 다시 생성되어도 뷰모델을 통해 데이터를 유지할 수 있는 MVVM 방식을 채택하기로 결정했다.
View에 해당되며, 대부분 액티비티나 프래그먼트로 이루어짐.
UI 와 관련된 프레젠테이션 로직을 작성함.
viewModel에 해당되며, View가 요청한 데이터를 요청하고 받아오는 역할을 하고, 이와 관련된 비즈니스 로직을 작성한다.
ViewModel에서 View에게 일관성 있는 데이터를 제공하기 위해 사용.
액티비티나 프래그먼트에서 이를 옵저빙해 변화를 감지하여 동작을 수행할 수 있다.
xml에서 ViewModel의 LiveData를 바인딩하여 관찰하고, 데이터 변화에 따른 UI 동작을 가능하게 한다.
데이터와 뷰를 연결하는 작업을 레이아웃에서 처리 할 수 있게 해주는 라이브러리.
ViewModel에서 요청한 데이터를 내부 데이터베이스에서 불러오거나, 외부 서버와 통신을 통해 데이터를 불러오고 불러온 데이터를 저장하고 가공해서 ViewModel에게 전달함.
이로써 ViewModel에서는 데이터를 저장하고 관리할 필요가 없어지게 된다.
스레드 안에서 실행되는 일시 중단 가능한 작업의 단위로, 경량 스레드
코루틴은 컨텍스트 스위치가 발생하지 않아 스레드보다 비용이 덜 든다.
코루틴의 범위로 코루틴의 블록을 묶음으로 제어할 수 있는 단위
CoroutineScope의 한 종류로 Application 생명주기에 종속적.
Activity가 onDestroy되어도 동작하는 코루틴 Context
CoroutineContext의 주요 요소로 CoroutineContext를 상속받아 어떤 스레드에서 어떻게 동작할 것인가에 대한 정의
=> Main을 제외한 IO/Default는 백그라운드 작업
scope의 확장함수로서, 코루틴을 만들고 실행하는 코루틴 빌더
launch: job 반환, 반환값이 없음
async: Deffered<T> 반환(T는 반환값의 자료형), 반환값 존재
async처럼 결과값을 반환하는 빌더로 async와 유사하지만, withContext()는 Deferred<T>객체로 반환하지 않고, 결과(T)를 그 자리에서 반납한다
대기 - join(), joinAll()
중단 - cancle(): 부모(job)을 포함해 모두 종료하는 함수로 한번 cancle하면 재사용이 불가은
cancleChildern(): 부모를 제외한 하위 루틴들을 모두 종료
대기 - await() 로 대기하고 결과값을 전해 받음, awaitAll()
코루틴 빌더(launch/async)의 start 인자에 CoroutineStart.Lazy값을 할당하면 해당 코루틴을 호출하는 시점에 실행
코드 블럭이 작업이 완료될 때 까지 스레드를 점유하고 대기
// 예외 처리를 할 수 없어, 앱이 죽어버리는 방법
GlobalScope.launch(Dispatchers.IO) { // IO, 백그라운드 환경
launch {
throw Exception() // 임의로 Exception 발생
}
}
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
// 코루틴 선언 시 handler를 추가하여 예외 발생 시 콜백으로 처리하는 방법
GlobalScope.launch(Dispatchers.IO + handler) { // 1
launch {
throw Exception() // 2
}
}
val handler = CoroutineExceptionHandler { coroutineScope, exception -> // 3
Log.d(TAG, "$exception handled!")
}
// 예외 발생 시 handler에 콜백으로 처리하여 앱이 죽지 않는 방법
CoroutineExceptionHandler: 예외 발생 시 등록한 handler에 위임하여 콜백으로 예외 이벤트를 처리하는 방식
but, async() / withContext()는 핸들러를 등록하여 throw로 예외처리 방법이 불가능
GlobalScope.launch(Dispatchers.IO) {
try {
// withContext의 외부에 try~catch로 예외 처리 가능한 방법
val name = withContext(Dispatchers.Main) {
throw Exception()
}
} catch (e: java.lang.Exception) {
Log.d(TAG, "$e handled!")
}
}
-> withContext 코루틴 블럭의 외부에 try-catch를 선언하여 예외 발생 시 처리할 수 있는 방법, async도 동일
스레드의 sleep() 메서드는 스레드를 점유하고 대기하는 Blocking 함수
코루틴의 delay() 메서드는 스레드를 점유하지 않는 non-blocking 함수
코루틴을 어떻게 처리할 것인지에 대한 여러가지 정보들의 모음.
코루틴은 내부적으로 취소 동작을 위해 CancellationException을 사용
CancellationException 예외는 모든 핸들러들이 무시하므로 handler를 등록해도 아무런 효과가 없습니다.
코루틴 동작 중 취소 상황 시 처리를 해야할 일이 있다면 try-catch블록을 이용해서 예외처리용으로 사용이 가능
fun main(args: Array<String>) = runBlocking<Unit> {
val job = launch {
// child 코루틴 생성
val child = launch {
try {
delay(Long.MAX_VALUE) // Long자료형 최대값만큼 대기
} catch (e :CancellationException) { // CancellationException 예외 발생 처리
println("Child is cancelled")
}
}
yield() // Thread순서 양보, 생략 시 child코루틴이 시작도 전에 cancel실행 가능
println("Cancelling child")
child.cancelAndJoin() // child 코루틴 취소/대기
yield()
println("Parent is not cancelled")
}
job.join()
}
일반적인 코루틴은 취소예외 이외의 예외는 양방향 전파라는 점
- 양방향 전파 : 아래 방향(부모 -> 자식) + 위 방향 (자식 -> 부모) 모두 전파
부모 예외로 인한 취소 발생 시 모든 자식 종료
자식 예외로 인한 취소 발생 시 부모 / 모든 자식 종료
SupervisorJob은 일반적인 Job과 다르게 예외를 단방향전파
(예외로 인한 코루틴의 취소가 단방향/아래 방향 (부모 -> 자식)으로만 전파된다는 점)
- 단방향 전파 : 아래 방향(부모 -> 자식)만 전파되는 것
부모 예외로 인한 취소 발생 시 모든 자식 종료
자식 예외로 인한 취소 발생 시 해당 Job만 종료[supervisorScope]
블록 내부의 모든 코루틴에 SupervisorJob을 설정하고 싶을 때 사용.
액티비티가 생성될 때 호출되며, 사용자 인터페이스 초기화에 사용됨.
액티비티가 멈췄다가 다시 시작되기 전에 호출됨.
액티비티가 사용자에게 보여지기 바로 직전에 호출됨.
액티비티가 사용자와 상호작용하기 바로 전에 호출됨.
다른 액티비티가 보여질 때 호출됨. 데이터 저장 스레드 중지 등의 처리를 하기에 적당한 메서드
액티비티가 더이상 사용자에게 보여지지 않을 때 호출됨. 메모리가 부족할 경우에는 onStop() 메서드가 호출되지 않을 수도 있음.
액티비티가 소멸될 때 호출됨. finish() 메서드가 호출되거나 시스템이 메모리 확보를 위해 액티비티를 제거할 때 호출됨.
(최근 앱 목록 표시 중) – 액티비티 제거하기(밀어내기나 ‘X’ 버튼)
불릴때가 있고 안불릴때도 있음.
(액티비티 foreground 상태) – 뒤로가기 버튼 눌러 액티비티 나가기
(액티비티 foreground 상태) – 전원 버튼 누르기 – 화면 잠금
(액티비티 foreground 상태) – 홈 버튼 누르기 – 런쳐 홈으로 이동
2022-11-28 00:59:34.818 28307-28307/com.example.activitylifecycle D/MainActivity: onPause
2022-11-28 00:59:35.221 28307-28307/com.example.activitylifecycle D/MainActivity: onCreate2
2022-11-28 00:59:35.240 28307-28307/com.example.activitylifecycle D/MainActivity: onStart2
2022-11-28 00:59:35.241 28307-28307/com.example.activitylifecycle D/MainActivity: onResume2
2022-11-28 00:59:36.001 28307-28307/com.example.activitylifecycle D/MainActivity: onStop
순차적으로 값을 배출하는 비동기 데이터 스트림.
suspending 함수는 비동기적으로 하나의 값만 반환하고, flow는 여러개를 반환합니다.
콜드 스트림으로, 요청 측에서 collect를 호출해야 값을 발생하기 시작.
수집(Collect)되기 전까지 실행되지 않는다는 것
콜드 스트림 - 요청이 있는 경우에 보통 1:1로 값을 전달하기 시작.
핫 스트림 - 0개 이상의 상대를 향해 지속적으로 값을 전달.
코루틴에서 데이터 스트림을 구현하기 위해 필요한 구성요소는 다음과 같다.
Producer(생산자)
Intermediary(중간 연산자) - optional
Consumer(소비자)
flow {} 가장 기본적인 플로우 빌더.
emit() 이외에 asFlow()를 통해 Collection을 flow로 변환할 수 있음.
(1..10).asFlow().collect { value ->
println(value)
}
flowOf { } - 고정된 값들을 방출하는 플로우를 정의
.asFlow () - 다른 Collection/Sequence들을 -> Flow로 변환
ex) List / Map / Array -> Flow로 변환
flow {} 코루틴 빌더(블록)을 생성한 후 내부에서 emit()을 통해 데이터를 생성.
플로우 블록은 suspend 함수이기 때문에 delay를 호출할 수 있음.
생성된 데이터를 수정함.
map(데이터를 원하는 형태로 변환), filter(데이터 필터링), onEach(데이터를 변경 후 수행한 결과를 반환) 등이 있음.
collect()를 이용하여 전달된 데이터를 소비함.
플로우 수집을 시작하는 종단 함수
두 개의 플로우들의 값(요소)들을 병합하는 연산자
val nums = (1..3).asFlow() // numbers 1..3
val strs = flowOf("one", "two", "three") // strings
nums.zip(strs) { a, b -> "$a -> $b" } // compose a single string
.collect { println(it) } // collect and print
1 -> one
2 -> two
3 -> three
만약 두 개의 플로우 요소의 크기가 다르다면? : 두 개의 플로우 중 크기가 작은 플로우에 맞춰서 출력