// 메시지 객체를 전달하면서 메인 스레드에게 작업을 의뢰하는 코드
var message = Message()
message.what = 1
message.arg1 = count
handler.sendMessage(message)
// 메인 스레드에서는 핸들러 객체의 handleMessage 함수 오버라이딩
var handler: Handler = object: Handler() {
override fun handleMessage(msg: Message) {
if(msg.what === 1) {
textView.text = msg.arg1.toString()
}else if(msg.what === 2) {
textView.text = msg.obj as String
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#1a237e">
<TextView
android:id="@+id/main_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="10"
android:textSize="80dp"
android:textStyle="bold"
android:textColor="@android:color/white"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="16dp">
<ImageView
android:id="@+id/main_startBtn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:clickable="true"
android:src="@drawable/ic_start" />
<ImageView
android:id="@+id/main_pauseBtn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:src="@drawable/ic_pause"
android:clickable="true"/>
</LinearLayout>
</RelativeLayout>
package com.tutorial.c47
import android.os.Bundle
import android.os.Handler
import android.os.Message
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
lateinit var startView: ImageView
lateinit var pauseView: ImageView
lateinit var textView: TextView
var loopFlag = true
var isFirst = true
var isRun = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startView = findViewById(R.id.main_startBtn)
pauseView = findViewById(R.id.main_pauseBtn)
textView = findViewById(R.id.main_textView)
startView.setOnClickListener {
if(isFirst){
isFirst = false
isRun = true
thread.start() // 개발자가 정의한 별도의 스레드 시작
}else{
isRun = true
}
}
// 정지 버튼 누르면 count가 감소하지 않음.
pauseView.setOnClickListener {
isRun = false
}
}
// 메인 스레드에서는 핸들러 객체의 handleMessage 함수 오버라이딩
var handler: Handler = object: Handler(){
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
if(msg.what === 1){
textView.text = msg.arg1.toString() // count 값 보여주기
}else if(msg.what === 2){
textView.text = msg.obj as String // Finish 문자열 보여주기
}
}
}
var thread: Thread = object: Thread(){
override fun run() {
try{
var count = 10
while(loopFlag){
sleep(1000) // 1초씩 숫자가 감소하도록
if(isRun){
count--
var message = Message() // 메시지 객체 생성
message.what = 1
message.arg1 = count
handler.sendMessage(message) // 핸들러에게 작업 의뢰
if(count == 0){
message = Message()
message.what = 2
message.obj = "Finish!!"
handler.sendMessage(message) // 핸들러에게 작업 의뢰
loopFlag = false // 반복문 종료
}
}
}
}catch (e: Exception){
e.printStackTrace()
}
}
}
}
이 예제에서 별도의 스레드를 만들지 않고 메인 스레드에서 모든 작업을 처리했다면, 숫자가 줄어드는 중간에 유저의 클릭 이벤트가 발생했을 때 ANR 문제가 일어났을 수도 있다.
override fun doInBackground(vararg p0: Void?): String {
// ...
}
override fun onProgressUpdate(vararg values: Int?) {
// ...
}
override fun onPostExecute(result: String?) {
// ...
}
class MyAsyncTask: AsyncTask<Void?, Int?, String>()
package com.tutorial.c47
import android.os.AsyncTask
import android.os.Bundle
import android.os.SystemClock
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
lateinit var startView: ImageView
lateinit var pauseView: ImageView
lateinit var textView: TextView
var isFirst = true
lateinit var asyncTask: MyAsyncTask
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startView = findViewById(R.id.main_startBtn)
pauseView = findViewById(R.id.main_pauseBtn)
textView = findViewById(R.id.main_textView)
startView.setOnClickListener {
if(isFirst){
asyncTask.isRun = true
asyncTask.execute() // 스레드 실행
isFirst = false
}else{
asyncTask.isRun = true
}
}
pauseView.setOnClickListener {
asyncTask.isRun = false
}
asyncTask = MyAsyncTask() // 객체 생성
}
inner class MyAsyncTask: AsyncTask<Void?, Int?, String>(){
var loopFlag = true
var isRun = false
// 스레드에 의해 백그라운드에서 처리될 내용을 담는다.
override fun doInBackground(vararg p0: Void?): String {
var count = 10
while(loopFlag){
SystemClock.sleep(1000)
if(isRun){
count--
publishProgress(count) // onProgressUpdate
if(count == 0){
loopFlag = false
}
}
}
return "Finish!!" // onPostExecute
}
// doInBackground에서 publishProgress 함수로 넘긴 값이 전달됨.
override fun onProgressUpdate(vararg values: Int?) {
super.onProgressUpdate(*values)
textView.text = values[0].toString() // count 값
}
// doInBackground 함수의 최종 결과 값을 받기 위해 사용함.
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
textView.text = result // Finish 문자열
}
}
}
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:version'
val backgroundScope = CoroutineScope(Dispatchers.Default + Job())
backgroundScope.launch {
// 시간이 오래 걸리는 작업은 Default 또는 IO 디스패처
// 뷰를 건드리는 작업은 Main 디스패처
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical"
android:gravity="center">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="click" />
<TextView
android:id="@+id/resultView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20dp"
android:textStyle="bold"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
package com.tutorial.c49
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*
import kotlin.system.measureTimeMillis
class MainActivity : AppCompatActivity() {
val backgroundScope = CoroutineScope(Dispatchers.Default + Job())
lateinit var button: Button
lateinit var resultView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button = findViewById(R.id.button)
resultView = findViewById(R.id.resultView)
// 버튼 클릭하고 나서도 다른 뷰 (EditText)를 건드릴 수 있음.
button.setOnClickListener {
backgroundScope.launch {
var sum = 0L
// 시간이 오래 걸리는 작업
var time = measureTimeMillis {
for(i in 1..2_000_000_000){
sum += i
}
}
// UI 작업은 메인 디스패처가 처리
withContext(Dispatchers.Main){
resultView.text = "sum: $sum"
}
}
}
}
}
위의 예제를 코루틴으로 처리해주지 않으면, sum 연산이 진행 중일 때 뷰를 건드리면 ANR 문제가 발생할 수 있다.