새로운 프로젝트를 만들면 자동으로 생성되는 메인 액티비티는 앱이 실행될 때 하나의 프로세스에서 처리된다
따라서 메인 액티비티 내에서 이벤트를 처리하거나 특정 메소드를 정의하여 기능을 구현할 대도 같은 프로세스 안에서 실행된다
같은 프로세스 안에서 일련의 기능이 순서대로 실행될 때 대부분은 큰 문제가 없지만,
대기 시간이 길어지는 네트워크 요청 등의 기능을 수행할 때는 화면에 보이는 UI도 멈춤 상태로 있게 되는 문제가 생길 수 있다
이런 문제를 해결하려면 하나의 프로세스 안에서 여러 개의 작업이 동시 수행되는 멀티 스레드 방식을 사용하게 된다
스레드는 동시 수행이 가능한 작업 단위이며, 현재 수행 중인 작업 이외의 기능을 동시에 처리할 때 새로운 스레드를 만들어 처리한다
이런 멀티 스레드 방식은 같은 프로세스 안에 들어 있으면서 메모리 리스를 공유하므로 효율적인 처리가 가능하다
하지만 동시에 리소스에 접근할 때 데드락이 발생하여 시스템이 비정상적으로 동작할 수도 있다
여러 개의 스레드가 동시에 공통 메모리 리소스에 접근할 때 데드락이 발생한다
데드락이랑 동시에 두 곳 이상에서 요청이 생겼을 때 어떤 것을 먼저 처리할지 판단할 수 없어 발생하는 시스템상의 문제이다
이런 경우는 런타임 시의 예외 상황이므로 디버깅하기 쉽지 않은 경우가 많다
지연시간이 길어질 수 있는 앱이라면 오랜 시간 작업을 수행하는 코드를 별도로 분리한 다음, UI에 응답을 보내는 방식을 사용한다
이를 위해 안드로이드가 제공하는 두 가지 시나리오가 있다
1. 서비스 이용하기 - 백그라운드 작업은 서비스로 실행하고, 사용자에게는 알림 서비스로 알려준다
만약 메인 액티비티로 결과 값을 전달하고 이를 이용해서 다른 작업을 수행하고자 한다면 브로드캐스팅으로 결과 값을 전달할 수 있다
2. 스레드 사용하기 - 스레드는 같은 프로세스 안에 있기 때문에 작업 수행의 결과를 바로 처리할 수 있다
그러나 UI 객체는 직접 접근할 수 없으므로 핸들러 객체를 사용한다
안드로이드에서 UI처리할 때 사용되는 기본 스레드를 메인 스레드라고 부른다
메인 스레드에서 이미 UI에 접근하고 있으므로 새로 생성한 다른 스레드에서는 핸들러 객체를 사용해서 메세지를 전달함으로써 메인 스레드에서 처리할 수 있도록 만든다
activity_main
<?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">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="스레드 시작"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity
package com.example.a52_thread;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
int value = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
BackgroundThread thread = new BackgroundThread();
thread.start();
}
});
}
class BackgroundThread extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1000);
} catch (Exception e) {
}
value += 1;
Log.d("Thread", "value : " + value);
}
}
}
}
여기까지만 하면 로그에 value 값이 출력되면서 문제 없이 잘된다
하지만 화면에 value 값을 출력하게 하려면 문제가 생긴다
activity_main
<?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">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="스레드 시작"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="240dp"
android:hint="value"
android:textSize="30sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity
package com.example.a52_thread;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
int value = 0;
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.textView);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
BackgroundThread thread = new BackgroundThread();
thread.start();
}
});
}
class BackgroundThread extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1000);
} catch (Exception e) {
}
value += 1;
Log.d("Thread", "value : " + value);
textView.setText("value 값 : " + value);
}
}
}
}
직접 만든 BackgroundThread 객체에서 UI 객체를 직접 접근했다는 것을 알려주는 오류가 뜬다
W/System: A resource failed to call close.
D/Thread: value : 1
E/AndroidRuntime: FATAL EXCEPTION: Thread-2
Process: com.example.a52_thread, PID: 23076
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8798)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1606)
at android.view.View.requestLayout(View.java:25390)
at android.view.View.requestLayout(View.java:25390)
at android.view.View.requestLayout(View.java:25390)
at android.view.View.requestLayout(View.java:25390)
at android.view.View.requestLayout(View.java:25390)
at android.view.View.requestLayout(View.java:25390)
at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3593)
at android.view.View.requestLayout(View.java:25390)
at android.widget.TextView.checkForRelayout(TextView.java:9719)
at android.widget.TextView.setText(TextView.java:6311)
at android.widget.TextView.setText(TextView.java:6139)
at android.widget.TextView.setText(TextView.java:6091)
at com.example.a52_thread.MainActivity$BackgroundThread.run(MainActivity.java:46)
I/Process: Sending signal. PID: 23076 SIG: 9
메인 스레드에서 관리하는 UI 객체는 직접 만든 스레드 객체에서는 접근 할수 없다는 의미이다
이를 해결하려면 핸들러를 사용해야 한다
앱을 실행할 때 프로세스가 만들어지면, 그 안에 메인 스레드가 함께 만들어진다
그리고 최상위에서 관리되는 앱 구성 요소인 액티비티, 브로드캐스트 수신자 등과 새로 만들어지는 윈도우를 관리하기 위한 메세지 큐(Message Queue)를 실행한다
메세지 큐를 사용하면 순차적으로 코드를 수행할 수 있는데, 이렇게 메세지 큐로 메인 스레드에서 처리할 메세지를 전달하는 역할을 핸들러 클래스가 담당한다
핸들러는 실행하려는 특정 기능이 있을 때 핸들러가 포함되어있는 스레드에서 순차적으로 실행시킬 때 사용된다
핸들러를 이용하면 특정 메세지가 미래의 어던 시점에 실행되도록 스케줄링할 수도 있다
새로 만든 스레드(스레드 #1)가 수행하려는 정보를 메인 스레드로 전달하기 위해서는,
먼저 핸들러가 관리하는 메시지 큐에서 처리할 수 있는 메세지 객체 하나를 참조해야 한다
이 첫 번째 과정에서는 obtainMessage 메소드를 이용할 수 있으며, 호출의 결과로 메세지 객체를 반환받게 된다
이 메세지 객체에 필요한 정보를 넣은 후 sendMessage 메소드를 이용해 메세지 큐에 넣을 수 있다
메세지 큐에 들어간 메세지는 순서대로 핸들러가 처리하게 되며, 이때 handleMessage 메소드에 정의된 기능이 수행된다
이 때 handleMessage에 들어 있는 코드가 수행되는 위치는 새로 만든 스레드가 아닌 메인 스레드가 된다
MainActivity
package com.example.a52_thread;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
int value = 0;
TextView textView;
MainHandler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.textView);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
BackgroundThread thread = new BackgroundThread();
thread.start();
}
});
handler = new MainHandler();
}
class BackgroundThread extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1000);
} catch (Exception e) {
}
value += 1;
Log.d("Thread", "value : " + value);
Message message = handler.obtainMessage();
Bundle bundle = new Bundle();
bundle.putInt("value", value);
message.setData(bundle);
// 핸들러로 메세지 객체 보내기
handler.sendMessage(message);
}
}
}
class MainHandler extends Handler {
// 핸들러 안에서 전달받은 메세지 객체 처리하기
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Bundle bundle = msg.getData();
int value = bundle.getInt("value");
textView.setText("value 값 : " + value);
}
}
}
좀 더 간단한 방법으로 메인 스레드에서 실행시킬 수 있도록, 핸들러 클래스는 메세지 전송 방법 이외에
Runnable 객체를 실행시킬 수 있는 방법을 제공한다
새로 만든 Runnable 객체를 핸들러의 post 메소드로 전달해주면 이 객체에 정의된 run 메소드 안의 코드들은 메인 스레드에서 실행된다
MainActivity
package com.example.a52_thread;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// int value = 0;
TextView textView;
// MainHandler handler;
// API의 기본 핸들러 객체 생성
Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.textView);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
BackgroundThread thread = new BackgroundThread();
thread.start();
}
});
// handler = new MainHandler();
}
class BackgroundThread extends Thread {
int value = 0;
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1000);
} catch (Exception e) {
}
value += 1;
Log.d("Thread", "value : " + value);
// 핸들러의 post 메소드 호출
handler.post(new Runnable() {
@Override
public void run() {
textView.setText("value 값 : " + value);
}
});
// Message message = handler.obtainMessage();
// Bundle bundle = new Bundle();
// bundle.putInt("value", value);
// message.setData(bundle);
//
//// 핸들러로 메세지 객체 보내기
// handler.sendMessage(message);
}
}
}
// class MainHandler extends Handler {
//
// // 핸들러 안에서 전달받은 메세지 객체 처리하기
// @Override
// public void handleMessage(Message msg) {
// super.handleMessage(msg);
//
// Bundle bundle = msg.getData();
// int value = bundle.getInt("value");
// textView.setText("value 값 : " + value);
// }
// }
}
runOnUiThread 메소드를 사용하면 핸들러 객체를 만들지 않고도 메인 스레드에서 동작하게 만들 수 있다
MainActivity
package com.example.a52_thread;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// int value = 0;
TextView textView;
// MainHandler handler;
// API의 기본 핸들러 객체 생성
// Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.textView);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
BackgroundThread thread = new BackgroundThread();
thread.start();
}
});
// handler = new MainHandler();
}
class BackgroundThread extends Thread {
int value = 0;
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1000);
} catch (Exception e) {
}
value += 1;
Log.d("Thread", "value : " + value);
runOnUiThread(new Runnable() {
@Override
public void run() {
textView.setText("value 값 : " + value);
}
});
//// 핸들러의 post 메소드 호출
// handler.post(new Runnable() {
// @Override
// public void run() {
// textView.setText("value 값 : " + value);
// }
// });
// Message message = handler.obtainMessage();
// Bundle bundle = new Bundle();
// bundle.putInt("value", value);
// message.setData(bundle);
//
//// 핸들러로 메세지 객체 보내기
// handler.sendMessage(message);
}
}
}
// class MainHandler extends Handler {
//
// // 핸들러 안에서 전달받은 메세지 객체 처리하기
// @Override
// public void handleMessage(Message msg) {
// super.handleMessage(msg);
//
// Bundle bundle = msg.getData();
// int value = bundle.getInt("value");
// textView.setText("value 값 : " + value);
// }
// }
}
'안드로이드' 카테고리의 다른 글
스레드로 애니메이션 만들기 (0) | 2021.10.27 |
---|---|
일정 시간 후에 실행하기 (0) | 2021.10.27 |
키패드 제어 (0) | 2021.10.27 |
시크바 (0) | 2021.10.27 |
웹뷰 (0) | 2021.10.27 |