Concepts of ANR
1. ANR
- Activity Not Response의 약어, 에러 상황을 지칭
- ANR 문제가 발생하는 경우
- 유저 이벤트에 액티비티가 5초 이내에 반응하지 못하는 경우 발생하는 에러
- 유저가 Event를 가하는 순간 Event가 EventQueue에 담기고, 시스템에서 EventQueue를 추출해서 이벤트 업무를 진행
- EventQueue에 담기는 순간부터 5초 이내에 이벤트 업무가 진행되지 않으면 시스템에서 강제로 액티비티를 종료
- ANR 문제가 발생하는 이유
- 메인 스레드 : 시스템에서 액티비티를 실행시키는 수행 흐름
- 하나의 스레드만으로 처리를 하다 보니까 시간이 오래 걸리는 업무가 있으면, 이 업무가 끝나지 않는 한 이벤트 처리가 안되고 ANR 문제가 생길 수 있다
- ANR 문제의 대표적인 예제 : Network 프로그래밍
- 해결 방법
- Thread-Handler로 해결 - Android 11에서 deprecated
- AsyncTask로 해결 - Android 11에서 deprecated
- Coroutine으로 해결 - Android 11에서 권장
Handler
1. Handler
- ANR 문제 해결 방법 : ANR 문제가 스레드가 1개이다 보니 발생하는 에러여서 멀티스레드를 사용
- Main Thread : Activity를 실행시키는 원래 시스템의 스레드로 화면 출력 및 유저 이벤트 처리
- Thread : 시간이 오래 걸리는 업무를 처리할 개발자 스레드
- 개발자 스레드에서 View에 접근하면 에러가 발생, 따라서 개발자 스레드에서는 View 접근이 안되도록 프로그램을 개발해야 함
- 하지만 Activity에서 작동하는 것이기 때문에 화면과 완전히 독립적일 수 없음
- 그럼 어떻게 하냐? : 개발자 스레드에서 View에 접근할 일이 있으면 핸들러 클래스를 이용해서 메인 스레드에 의뢰해서 메인 스레드에 의해 View가 관리되도록 함(Thread-Handler)
2. Thread-Handler 사용 방법
- Handler로 메인 스레드에게 작업을 의뢰하는 방법
- sendMessage(Message msg) : Message 객체로 메인 스레드에게 작업을 의뢰
- sendMessageAtFrontOfQueue(Message msg) : View 작업에 대한 의뢰가 반복적으로 발생한 경우 UI Thread에서 순차적으로 처리하는데 이번 의뢰를 가장 먼저 처리하라는 요청
- sendMessageAtTime(Message msg, long uptimeMillis) : 의뢰를 지정한 시간에 수행
- sendMessageDelayed(Message msg, long delayMillis) : 지연시간 후에 수행 시켜 달라는 요청
- sendEmptyMessage(int what) : 데이터 전달 없이 의뢰하는 경우
- Message 객체 : 메인 스레드에게 넘기는 데이터(아래는 Message 객체의 프로퍼티)
- what : int형 변수로 구분자, 개발자가 임의 숫자값으로 요청을 구분하기 위해 사용
- obj : UI Thread에게 넘길 데이터, Object 타입의 변수
- arg1, arg2 : UI Thread에게 넘길 데이터, int 타입으로 간단한 숫자값은 arg1, arg2 변수에 담아 전달
- 개발자 스레드 코드(메인 스레드에 데이터 넘기기)
var message = Message()
message.what = 1
message.arg1 = count
handler.sendMessage(message)
- 메인 스레드 코드(Handler 객체의 handleMessage 호출)
var handler: Handler = objedt: Handler() {
override fun handleMessage(msg: Message) {
if(msg.what === 1) {
textView.setText(String.valueOf(msg.arg1))
} else if(msg.what === 2) {
textView.text = msg.obj as String
}
}
}
AsyncTask
1. AsyncTask
- ANR 문제 해결 방법 : 원론적으로는 Thread-Handler 기법이지만 추상화시켜 조금 더 쉽게 개발할 수 있게 함
- AsyncTask 클래스를 상속 받아서 개발자 코드 작성(아래는 오버라이드 받아야 하는 함수)
- doInBackground(Params... params) : Threada에 의해 처리될 내용을 담기 위한 함수
- onPreExecute() : AsyncTask의 작업을 시작하기 전에 호출, AsyncTask에서 가장 먼저 한 번 호출
- onPostExecute(Result result) : AsyncTask의 모든 작업이 완료된 후 가장 마지막에 한 번 호출, doInBackground 함수의 최종 값을 받기 위해 사용
- onProgressUpdate(Progress... values) : doInBackground에 의해 처리되는 중간중간 값을 받아 처리하기 위해서 호출, doInBackground에서 publishProgress 함수로 넘긴 값이 전달
class MyAsyncTask: AsyncTask<Void?, Int?, String>()
- 스레드에 의해 업무 처리가 되고 그 결과값이 어떤 함수에 넘어갈 때 데이터 타입과 관련있는 것을 제네릭 타입으로 지정해줘야 함
- 첫번째 타입 : Background 작업을 위한 doInBackground의 매개변수 타입과 동일, AsyncTask에 의해 Background 작업을 의뢰할 때 넘길 데이터의 타입, 없으면 void로 지정
- 두번째 타입 : doInBackground 함수 수행에 의해 발생한 데이터를 publichProgress() 함수를 이용해 전달하는데 이 때의 전달 데이터 타입, 데이터를 전달 받을 onProgressUpdate 함수의 매개변수 타입과 동일하게 지정
- 세번째 타입 : onPostExecute의 매개변수 타입과 동일하게 지정, doInBackground 함수의 리턴 타입이며 리턴된 데이터가 onPostExecute 함수의 매개변수로 전달됨
Coroutine
1. Coroutine
- Non-Blocking, lightweight thread로 일반 스레드처럼 특정 업무를 별개 수행 흐름을 실행시킴
- thread에 비해 경량이다
- 메모리 누수가 작다
- 취소 등 다양한 기법을 지원한다
- 많은 JetPack 라이브러리에 적용되어 있다
2. Scope
- 코루틴이 실행되는 영역, 스코프를 만들고 스코프 내에서 코루틴을 시킴
- 한 스코프 안에 여러 코루틴을 실행시킬 수 있고 각각 또는 전체 코루틴을 제어할 수 있음
- CoroutineScope의 구현 클래스(인터페이스)
- 안드로이드에서 GlobalScope, ActorScope, ProducerScope 등 제공
val backgroundScope = CoroutineScope(Dispatchers.Default + Job())
- Dispatchers : 코루틴을 구동시킬 때 어떤 스레드로 실행시킬 지를 스코프에서 지정하는 것
- Dispatchers.Main : UI를 출력할 수 있는 액티비티의 메인 스레드에서 동작하는 코루틴을 만들기 위한 디스패처
- Dispatchers.IO : 파일을 읽기 혹은 쓰기 또는 네트워크 작업 등에 최적화 된 디스패처
- Dispatchers.Default : CPU를 많이 사용하는 작업을 백그라운드에서 실행할 목적의 디스패처
3. Coroutine 구동 방법
- Coroutine은 launch, async 등의 함수에 의해 실행
- launch, async는 Coroutine Builder
backgroundScope.launch{
}