하나의 프로세스는 하나의 실행 중인 프로그램에 해당합니다. 프로세스 내부에서는 스레드가 프로그램의 명령을 실행시키게 되는데 필요한 경우 여러 스레드를 통해 명령어를 수행시키는 다중 스레드를 가질 수 있습니다. 프로세스는 각각의 독립된 메모리 공간을 할당받지만 스레드는 하나의 프로세스 안에서 동작하므로 프로세스가 차지하는 메모리를 서로 공유할 수 있습니다.
Android에서도 실행되는 하나의 앱을 개별적인 프로세스로 취급해 실행하고 이 프로세스 역시 내부 명령어를 실행시키는 스레드를 가질 수 있습니다.
1. 스레드
프로세스는 기본적으로 하나의 스레드를 가집니다. 이 스레드는 메인스레드로서 화면에 UI를 구성하고 사용자와 UI 간 이벤트 처리를 담당하는 것이 주 목적인 스레드입니다. 그런데 이 메인 스레드만 가지고는 경우에 따라서 앱을 온전히 만들 수 없는 경우도 있습니다.
예를 들어 사용자가 앱 안에서 어떤 파일을 다운로드하기 위해 '다운로드'버튼을 눌렀는데 파일이 매우 큰 경우라면 다운로드하는데 시간이 꽤 걸릴 수 있을 것입니다. 그런데 메인 스레드에 다운로드 작업을 맡겨 버리면 파일 다운로드가 완료될 때까지 앱 전체가 응답하지 못하는 상황이 발생하게 됩니다.
바로 위와 같은 문제점을 에방하고 앱이 좀 더 유연성을 가지고 동작하도록 하기 위해 별도의 스레드를 생성하여 시간이 오래 걸리는 특정 작업을 스레드에 넘겨 처리하는 방식을 취하곤 합니다. (화면에 보이는 형태가 아닌 앱의 뒷단에서 보이지 않게 동작하는 스레드를 백그라운드 스레드라고 합니다.)
다만 주의해야할 점이 하나 있는데 원칙적으로 메인 UI스레드 외에 다른 스레드는 UI 요소에 접근할 수 없습니다.
Kotlin에서 별도의 스레드를 생성하는 방법은 Thead 클래스를 상속하는 새로운 클래스를 작성하고 내부에 스레드에서 동작할 run() 메서드를 오버라이드 하는 것입니다. 그리고 이 run() 메서드 내부에 스레드가 수행할 코드를 작성하면 됩니다.
class AppThread1 : Thread() {
override fun run()
{
}
}
위와 같이 생성된 스레드는 start() 메서드를 호출하여 실행합니다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var t = AppThread1()
t.start()
}
}
class AppThread1 : Thread() {
override fun run()
{
}
}
Thread 클래스대신 다음과 같이 Runnable 인터페이스를 사용할 수도 있습니다. Runnable은 상속관계에 있는 클래스를 지원하기 위한 인터페이스입니다.
class AppThread1 : Runnable {
override fun run()
{
}
}
Thread 클래스를 사용하는 경우와 동일하지만 스레드를 실행시키는 방법은 객체 자체를 Thread 클래스의 생성자로 전달하여 실행시켜야 합니다.
var t = Thread(AppThread1())
t.start()
Runnable은 실행메서드가 하나만 존재하는 경우 간단하게 람다식으로 구현될 수도 있습니다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Thread {
//처리로직
}.start()
}
}
혹은 다음과 같이 전용의 스레드처리 메서드를 구현하는 경우도 있습니다.
thread(start=true) {
//처리로직
}
2. 핸들러와 루퍼
핸들러와 루퍼는 스레드간 통신을 위한 장치입니다. 여기서 루퍼는 MainActivity가 실행되면 자동으로 같이 실행되는데 중단 없이 무한루프를 계속 도는 스레드라고 생각하면 됩니다. 루퍼의 역할은 무한루프를 돌면서 큐의 메시지 상태를 확인하고 큐에 메시지가 저장되면 이 메시지를 핸들러로 전달하는 역할을 수행합니다.
핸들러는 루퍼에서 전달해준 메시지를 받아 처리하면서 메인 스레드와 별도로 생성된 스레드 간 직접적으로 통신을 담당하는 역할을 수행합니다. 루퍼는 자동으로 생성되지만 핸들러의 경우에는 필요한 로직으로 직접 구현해야 합니다.
위에서 메인 스레드가 아닌 다른 스레드에서는 사용자의 UI에 직접적으로 접근할 수 없다는 점을 설명했는데 이 한계를 핸들러와 루퍼를 통해서 극복하곤 합니다.
그럼 1초마다 숫자를 늘려 카운트하는 예제를 통해서 직접 핸들러를 구현해 보도록 하겠습니다. 우선 아래와 같이 화면을 구성합니다. 버튼 2개와 TextView하나를 배치하였습니다. 버튼의 ID는 카운터는 button 리셋은 button2이며, TextView의 ID는 textView입니다.
우선 핸들러를 작성하기 위에서 배치한 위젯에 접근하기 위한 선행작업을 먼저 작성합니다. findViewById() 메서드를 사용해 MainActivity에 있는 위젯을 가져올 수 있도록 하고, 정수를 담을 수 있는 count라는 변수를 선언합니다.
var count = 0;
class MainActivity : AppCompatActivity() {
private lateinit var mybtnCnt: Button
private lateinit var mybtnRet: Button
private lateinit var mytv: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mybtnCnt = findViewById(R.id.button)
mybtnRet = findViewById(R.id.button2)
mytv = findViewById(R.id.textView)
}
}
핸들러는 간단하게 count변수를 담아서 표시할 수 있도록 다음과 같이 작성합니다.
var count = 0
val handler = object: Handler() {
override fun handleMessage(msg: Message) {
mytv.text = "${count.toString()}"
}
}
class MainActivity : AppCompatActivity() {
private lateinit var mybtnCnt: Button
private lateinit var mybtnRet: Button
private lateinit var mytv: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mybtnCnt = findViewById(R.id.button)
mybtnRet = findViewById(R.id.button2)
mytv = findViewById(R.id.textView)
}
}
카운트 버튼 클릭이벤트를 생성하여 새로운 스레드를 생성하고 count변수를 늘려주도록 합니다. Thead의 sleep() 메서드를 통해 1초의 지연시간을 발생시켜 1초마다 값을 증가시키도록 하는데 1회성으로 늘려줄게 아니라 1초마다 count변수를 늘려주는 것이므로 while문을 통해 지속적으로 반복하도록 합니다.
무엇보다 중요한 건 handle의 sendEmptyMessage() 메서드를 통해 핸들러의 메시지를 전송하는 것입니다. 전송된 메시지는 루퍼를 통해 핸들러에 전달될 것이고 위에서 생성한 핸들러가 동작하면서 textView에 count의 숫자를 표시하게 될 것입니다.
mybtnCnt.setOnClickListener() {
thread (start = true) {
while (true) {
Thread.sleep(1000)
count = count +1
handler ?. sendEmptyMessage (0)
}
}
}
리셋 버튼은 단순히 count변수를 0으로 초기화하면 됩니다.
mybtnRet.setOnClickListener() {
count = 0
}
'Mobile > Kotlin' 카테고리의 다른 글
[Kotlin] 서비스(Service) (2) | 2021.01.07 |
---|---|
[Kotlin] AsyncTask (0) | 2021.01.06 |
[Kotlin] 사진저장하고 가져오기 (4) | 2021.01.05 |
[Kotlin] 카메라 사용하기 (0) | 2020.12.31 |
[kotlin] ORM라이브러리 Room (0) | 2020.12.30 |