티스토리 뷰

Mobile/Android

[Android] Thread & Handler

춘햄 2022. 4. 5. 08:31

 Android 에서 하나의 액티비티는 하나의 프로세스에서 처리되기 때문에 메인 액티비티 내에서 어떤 이벤트를 처리하거나 특정 메서드를 정의하여 기능을 구현할 때도 같은 프로세스 안에서 동작을 한다. 

그렇기 때문에 대기 시간이 길어지는 동작을 수행할 때는 해당 액티비티가 멈춰버리는 문제가 생길 수 있다.

 

이런 경우 사용할 수 있는 2개의 시나리오는 다음과 같다.

 

구분 시나리오
서비스 사용 백그라운드 작업은 서비스로 실행하고 사용자에게는 알림 서비스로 알려준다. 만약 메인 액티비티로 결과 값을 전달하고 이를 이용해서 다른 작업을 수행하고자 한다면, 브드드캐스팅으로 결과 값을 전달해야 한다.
스레드 사용 스레드는 같은 프로세스 안에 있기 때문에 작업 수행의 결과를 바로 처리할 수 있다.
그러나  UI 객체는 직접 접근할 수 없으므로 핸들러 객체를 사용한다.

 

Android가 UI를 처리할 때 사용하는 기본 스레드를 메인 스레드라고 부른다. 메인 스레드에서 이미 UI에 접근하고 있으므로 새로 생성한 다른 스레드에서 어떤 값을 처리하여 UI에 전달해야 한다면, 다른 스레드에서 핸들러 객체를 사용하여 데이터를 전달한다.

 

바로 예제를 확인해보자.


▶ Thread 사용하기

 

◎MainActivity.java

package com.example.samplethread;

import android.util.Log;
import android.view.View;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

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 ++;
                
                Log.d("Thread", "value : " + value);
            }
        }
    }
}

 

위 코드와 같이 Android에서도 표준 Java의 스레드를 그대로 사용할 수 있다.

하지만 1개의 UI 객체에는 1개의 프로세스만 접근할 수 있기 때문에 아래와 같이 TextView를 추가하여 BackgroundThread에서 해당 TextView를 직접 수정하게 되면

 

◎MainActivity.java

package com.example.samplethread;

import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

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 ++;

                Log.d("Thread", "value : " + value);
                textView.setText("value: " + value);
            }
        }
    }
}

CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

에러가 뜰 것이다. 

 

결국 메인 스레드에서 관리하는 UI 객체는 새로 생성된 다른 스레드에서 접근할 수 없다는 뜻이다.

 

이런 문제는 핸들러를 사용하여 해결할 수 있다.

 


▶ Handler 

앱을 실행할 때 프로세스가 만들어지면 그 안에 메인 스레드가 함께 만들어진다. 그리고 최상위에서 관리되는 앱 구성 요소인 액티비티, 브로드캐스트 수신자 등과 새로 만들어지는 윈도우를 관리하기 위한 메시지 큐를 실행한다. 

메시지 큐를 사용하면 순차적으로 코드를 수행할 수 있는데, 이렇게 메시지 큐로 메인 스레드에서 처리할 메시지를 전달하는 역할을 핸들러 클래스가 담당한다. 

 

결국 핸들러는 실행하려는 특정 기능이 있을 떄 핸들러가 포함되어 있는 스레드에서 순차적으로 실행시킬 때 사용한다는 것이다.

 

위 그림처럼 새로 만든 스레드(스레드 #1)가 수행하려는 정보를 메인 스레드로 전달하기 위해서는 핸들러가 관리하는 메시지 큐에서 처리할 수 있는 메시지 객체를 하나 참조해야 한다. 이 때, 스레드 #1은  obtainMessage() 메서드를 사용하여 메시지 큐 객체를 가져오고, sendMessage() 메서드를 통해 데이터를 메시지 큐에 넘길 수 있다. 

 

오류가 났던 위 코드를 아래와 같이 핸들러를 이용하여 수정해보자.

 

◎MainActivity

package com.example.samplethread;

import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

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.textView2);

        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 ++;

                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 {
    
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            Bundle bundle = msg.getData();
            int value = bundle.getInt("value");

            textView.setText("value 값: " + value);
        }
    }
}

우선 Handler 객체를 상속받는 MainHandler객체를 작성하고, 새 스레드에서 sendMessage로 데이터를 보냈을 때 호출될 handleMessage() 메서드를 작성해준다. 

 

새 스레드에서는 obtainMessage를 이용하여 메시지 큐를 참조한 뒤, 번들 객체 하나를 전달할 데이터에 넣어 핸들러로 보내는 코드를 작성하면 된다.

 

'Mobile > Android' 카테고리의 다른 글

[Android] Runnable 2: postDelayed  (0) 2022.04.06
[Android] Runnable  (0) 2022.04.06
[Android] 선택 위젯 4: Spinner  (0) 2022.04.03
[Android] 선택 위젯 3: Recycler View  (0) 2022.04.03
[Android] 선택 위젯 2: CardView  (0) 2022.04.02
Comments