작업량이 큰 연산 등 처리에 긴 시간이 걸리는 작업을 메인스레드의 큐에 넣고 작업하면 한 작업을 처리가 완료될 때까지 다른 작업을 처리하지 못함
👉 "앱이 응답하지 않습니다" 메시지 반환됨
이 때 메인 스레드에서 너무 많은 일을 처리하지 않도록 백그라운드 스레드를 만들어 일을 덜어줌
❗주의
각 백그라운드 스레드가 언제 처리를 끝내고 UI에 접근할지 순서를 알 수 없기 때문에
UI 관련 작업은 절대 백그라운드 스레드에서 하면 안됨백그라운드 스레드에서 UI 자원을 사용하려면,
메인 스레드에 UI 자원 사용 메시지를 전달하는 방법 이용








👉 안드로이드 View.OnClickListener인터페이스 공식 문서
코드(각각의 기능 함수는 구현 전)
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.TextView
class MainActivity : AppCompatActivity(), View.OnClickListener {
var isRunning = false
private lateinit var start_btn: Button
private lateinit var refresh_btn: Button
private lateinit var minute_tv:TextView
private lateinit var second_tv: TextView
private lateinit var millisecond_tv: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
start_btn = findViewById(R.id.start_btn)
refresh_btn = findViewById(R.id.refresh_btn)
minute_tv = findViewById(R.id.minute_tv)
second_tv = findViewById(R.id.second_tv)
millisecond_tv = findViewById(R.id.millisecond_tv)
start_btn.setOnClickListener(this)
refresh_btn.setOnClickListener(this)
}
override fun onClick(v: View?) {
when(v?.id) {
R.id.start_btn -> {
if (isRunning) {
pause()
} else {
start()
}
}
R.id.refresh_btn -> {
refresh()
}
}
}
}
private fun start() {
start_btn.text = "일시정지"
start_btn.setBackgroundColor(getColor(R.color.red))
isRunning = true
timer = timer(period = 10) {
time++
val milli_second = time % 100
val second = (time % 6000) / 100
val minute = time / 6000
millisecond_tv.text =
if (milli_second < 10) {
".0${milli_second}"
} else {
".${milli_second}"
}
}
}
위과 같이 MainActiviy.kt의 start() 함수를 구현하면 이러한 에러가 뜨며 스톱워치 시작과 동시에 종료된다.
Only the original thread that created a view hierarchy can touch its views.
오류의 원인은 위에서 말했던 백그라운드 스레드에서 UI 작업을 했기 때문이다.
그럼 start() 함수 내에서 분, 초, 밀리초를 출력하는 텍스트뷰가 UI 스레드에서 실행되도록 다음과 같이 수정하면 된다.
private fun start() {
start_btn.text = "일시정지"
start_btn.setBackgroundColor(getColor(R.color.red))
isRunning = true
timer = timer(period = 10) {
time++
val milli_second = time % 100
val second = (time % 6000) / 100
val minute = time / 6000
runOnUiThread {
if (isRunning) {
millisecond_tv.text =
if (milli_second < 10)
".0${milli_second}"
else
".${milli_second}"
second_tv.text =
if (second < 10)
":0${second}"
else
":${second}"
minute_tv.text = "${minute}"
}
}
}
}
private fun pause() {
start_btn.text = "시작"
start_btn.setBackgroundColor(getColor(R.color.blue))
isRunning = false
timer?.cancel()
}
private fun refresh() {
timer?.cancel()
start_btn.text = "시작"
start_btn.setBackgroundColor(getColor(R.color.blue))
isRunning = false
time = 0
millisecond_tv.text = ".00"
second_tv.text = ":00"
minute_tv.text = "00"
}
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.TextView
import java.util.Timer
import kotlin.concurrent.timer
class MainActivity : AppCompatActivity(), View.OnClickListener {
var isRunning = false
var timer : Timer? = null
var time = 0
private lateinit var start_btn: Button
private lateinit var refresh_btn: Button
private lateinit var minute_tv:TextView
private lateinit var second_tv: TextView
private lateinit var millisecond_tv: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
start_btn = findViewById(R.id.start_btn)
refresh_btn = findViewById(R.id.refresh_btn)
minute_tv = findViewById(R.id.minute_tv)
second_tv = findViewById(R.id.second_tv)
millisecond_tv = findViewById(R.id.millisecond_tv)
start_btn.setOnClickListener(this)
refresh_btn.setOnClickListener(this)
}
override fun onClick(v: View?) {
when(v?.id) {
R.id.start_btn -> {
if (isRunning) {
pause()
} else {
start()
}
}
R.id.refresh_btn -> {
refresh()
}
}
}
private fun start() {
start_btn.text = "일시정지"
start_btn.setBackgroundColor(getColor(R.color.red))
isRunning = true
timer = timer(period = 10) {
time++
val milli_second = time % 100
val second = (time % 6000) / 100
val minute = time / 6000
runOnUiThread {
if (isRunning) {
millisecond_tv.text =
if (milli_second < 10)
".0${milli_second}"
else
".${milli_second}"
second_tv.text =
if (second < 10)
":0${second}"
else
":${second}"
minute_tv.text = "${minute}"
}
}
}
}
private fun pause() {
start_btn.text = "시작"
start_btn.setBackgroundColor(getColor(R.color.blue))
isRunning = false
timer?.cancel()
}
private fun refresh() {
timer?.cancel()
start_btn.text = "시작"
start_btn.setBackgroundColor(getColor(R.color.blue))
isRunning = false
time = 0
millisecond_tv.text = ".00"
second_tv.text = ":00"
minute_tv.text = "00"
}
}
<?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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/minute_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00"
android:textSize="45sp"
app:layout_constraintBaseline_toBaselineOf="@+id/second_tv"
app:layout_constraintEnd_toStartOf="@+id/second_tv"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/second_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=":00"
android:textSize="45sp"
app:layout_constraintBottom_toTopOf="@+id/refresh_btn"
app:layout_constraintEnd_toStartOf="@+id/millisecond_tv"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/minute_tv"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/millisecond_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=".00"
android:textSize="30sp"
app:layout_constraintBaseline_toBaselineOf="@+id/second_tv"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/second_tv" />
<Button
android:id="@+id/refresh_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="50dp"
android:backgroundTint="@color/yellow"
android:padding="20dp"
android:text="@string/refresh"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/start_btn"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/start_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="635dp"
android:layout_marginBottom="80dp"
android:backgroundTint="@color/blue"
android:padding="20dp"
android:text="@string/start"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
