티스토리 뷰

Android/Android Multi Threading

8. 핸들러 스레드

안드로이드용용 2016. 8. 3. 10:47

이번장부터 본격적으로 안드로이드에서 제공하는 스레드 관리 클래스에 대해서 알아보고 활용하는 시간을 갖도록 하자.


오늘은 그 중 핸들러 스레드(메시지 큐) 메커니즘에 대해 알아보자

  • 핸들러 스레드를 사용하는 방법
  • 수동으로 전달 메커니즘 설정 vs 핸들러 스레드 사용
  • 핸들러 스레드 사용 사례

8.1 기본사항
핸들러 스레드는 메시지 큐를 소유한 스레드다. (Thread.Looper.MessageQueue를 포함)
핸들러 스레드가 시작되면 루퍼와 메시지 큐를 설정하고 처리될 메시지가 들어오는 것을 기다린다.
HandlerThread handlerThread = new HandlerThread("HandlerThread");
handlerThread.start();

mHandler = new Handler(handlerThread.getLooper()) {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg)
    }
};
순차적 실행이 보장되어 스레드 안전하지만, 큐에 대기될 수 있기 때문에 성능이 낮다.

8.2 생명주기
실행 중인 HandlerThread 인스턴스는 종료되기 전까지 수신하는 메시지를 처리한다. 종료된 핸들러 스레드는 재사용할 수 없다. 

  1. 생성 : HandlerThread의 생성자는 스레드의 name, priority를 받는다

    HnadlerThread(String name)
    HandlerThread(String name, int priority)

    name 은 디버깅을 쉽게 한다. 로그에서 스레드 분석 시 해당 name으로 찾으면 됨.
    priority는 optional이며, 스레드 우선순위를 설정하여 중요도를 체크할 수 있다.

  2. 실행 : 핸들러 스레드의 루퍼가 스레드로 메시지를 전달 할 수 있는 동안 활성화 된다.
    HandlerThread.start 를 통해 설정되고, HandlerThread.getLooper 가 결과를 반환하거나 onLooperPrepared 콜백이 호출될 때 준비 상태가 된다.

  3. 재 설정 : 메시지 큐 안에 메시지가 더 이상 처리되지 않도록 재설정 할 수 있다.

    큐에 있는 모든 메시지를 제거한다.
    public void resetHandlerThread() {
       mHandler.removeCallbacksAndMessages(null);
    }

  4. 종료 : quit 또는 quitSafely 중 하나로 종료된다.

    public void stopHandlerThread(HandlerThread handlerThread) {
        handlerThread.quit();
        handlerThread.interrupt();
    }

    quit시에는 현재 실행 중인 스레드가 종료되는게 아니라 현재 돌아가는 스레느는 실행되며 큐에 있는 메시지를 실행하지 않는다. 그러나 interrupt는 현재 돌고 있는 스레드를 중단할 수 있다.

    핸들러에 마무리 테스크를 보냄으로써 종료될 수 있다.
    hnadler.post(new Runnable() {
        @Overrirde
        public void run() {
            Looper.myLooper().quit();
        }
    });


8.3 사용 사례
핸들러 스레드는 순차적 실행 및 메시지 큐의 통제가 필요한 많은 백그라운드 실행 사용 사례에 적용할 수 있다.

8.3.1 반복되는 태스크 실행
많은 안드로이드 구성요소는 백그라운드 스레드에서 태스크를 실행하여 UI 스레드의 부담을 덜어준다. 여러개 스레드 동시 실행이 필요하지 않을 경우 핸들러 스레드에서 차례대로 실행되는 테스크를 정의하는 간단하고 효율적인 방법을 제공한다.

핸들러 스레드에서는 Message와 Runnable 인스턴스를 받아 처리한다.

8.3.2 관련 테스크
파일시스템처럼 공유 자원에 접근하는 상호 의존적 테스크는 동시에 실행될 수 있지만, 데이터 안전을 위해서라면 동기화가 필요하다. 이 때 핸들러 스레드를 사용하면 여러 스레드의 동시 접근보다 안전하게 접근 가능하다. 즉, 순차적 실행을 통해서 비 독립적 태스크를 실행하는데 유용하다.

예제: SharedPreferencees와 데이터 지속성
ShearedPreference는 파일시스템에 있는 영구 저장소로서, 백그라운드 스레드에서 접근 가능하다. 파일 시스템 접근 스레드는 안전하지 않기 때문에 핸들러 스레드를 사용하면 안전하다.
package hyunyong.handlerthread;

import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.Process;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class SheredPreferencesActivity extends AppCompatActivity {

    TextView mTextValue;

    private Handler mUiHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 0) {
                Integer i = (Integer) msg.obj;
                mTextValue.setText(Integer.toString(i));
            }
        }
    };

    private class SharedPreferenceThread extends HandlerThread {
        private static final String KEY = "key";
        private SharedPreferences mPref;
        private static final int READ = 1;
        private static final int WRITE = 2;
        private Handler mHandler;

        public SharedPreferenceThread() {
            super("SharedPreferenceThread", Process.THREAD_PRIORITY_BACKGROUND);
            mPref = getSharedPreferences("LocalPrefs", MODE_PRIVATE);
        }

        @Override
        protected void onLooperPrepared() {
            super.onLooperPrepared();
            mHandler = new Handler(getLooper()) {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case READ:
                            mUiHandler.sendMessage(mUiHandler.obtainMessage(0,mPref.getInt(KEY,0)));
                            break;
                        case WRITE:
                            SharedPreferences.Editor editor = mPref.edit();
                            editor.putInt(KEY, (Integer)msg.obj);
                            editor.commit();
                            break;
                    }
                }
            };
        }

        public void read() {
            mHandler.sendEmptyMessage(READ);
        }

        public void write(int i) {
            mHandler.sendMessage(Message.obtain(Message.obtain(mHandler, WRITE,i)));
        }
    }

    private int mCount;
    private SharedPreferenceThread mThread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_shered_preferences);
        mTextValue = (TextView) findViewById(R.id.textview);
        mThread = new SharedPreferenceThread();
        mThread.start();
    }

    public void onButtonClickWrite(View v) {
        mThread.write(mCount++);
    }

    public void onButtonClickRead(View v) {
        mThread.read();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mThread.quit();
    }
}


8.3.3 태스크 연쇄
핸들러 스레드는 몇 가지 편리한 속성과 함께 태스크 연쇄를 위한 하부구조를 제공한다.
  • 설정의 용이함
  • 연쇄로 묶일 수 있는 독립적이고 재사용 가능한 태스크
  • 순차적 실행
  • 다음테스크로 연결할지 말지 결정해야 하는 지연 결정 지점
  • 현재 상태의 보고
  • 태스크 연쇄 내 하나의 태스크에서 다른 태스크로의 쉬운 데이터 전달.
모든 태스크 연쇄는 실행을 계속할 지 중지할지에 대한 자연 결정 지점이 포함된다.

예제 : 연쇄적 네트워크 호출

처음 네트워크 작업이 성공적으로 완료되면 두 번째 네트워크 자원의 호출이 이루어진다. 실패하면 연쇄를 멈추고 백그라운드 스레드를 종료한다.
package hyunyong.handlerthread;

import android.app.Dialog;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.Process;
import android.os.SystemClock;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class ChainedNetworkActivity extends AppCompatActivity {

    private static final int DIALOG_LOADING = 0;
    private static final int SHOW_LOADING = 1;
    private static final int DISMISS_LOADING = 2;

    Handler dialogHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case SHOW_LOADING :
                    showDialog(DIALOG_LOADING);
                    break;
                case DISMISS_LOADING :
                    dismissDialog(DIALOG_LOADING);
            }
        }
    };

    private class NetworkHandlerThread extends HandlerThread {
        private static final int STATE_A = 1;
        private static final int STATE_B = 2;
        private Handler mHandler;

        public NetworkHandlerThread() {
            super("NetworkHandlerThread", Process.THREAD_PRIORITY_BACKGROUND);
        }

        @Override
        protected void onLooperPrepared() {
            super.onLooperPrepared();
            mHandler = new Handler(getLooper()) {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    switch (msg.what) {
                        case STATE_A :
                            dialogHandler.sendEmptyMessage(SHOW_LOADING);
                            String result = networkOperation1();
                            if(result != null) {
                                sendMessage(obtainMessage(STATE_B, result));
                            } else {
                                dialogHandler.sendEmptyMessage(DISMISS_LOADING);
                            }
                            break;
                        case STATE_B:
                            networkOperation2((String) msg.obj);
                            dialogHandler.sendEmptyMessage(DISMISS_LOADING);
                            break;
                    }
                }
            };
            fetchDataFromNetwork();

        }

        private String networkOperation1() {
            SystemClock.sleep(2000);
            return "A String";
        }

        private void networkOperation2(String data) {
            SystemClock.sleep(2000);
        }

        // 공개적으로 노출된 네트워크 작업
        public void fetchDataFromNetwork() {
            mHandler.sendEmptyMessage(STATE_A);
        }
    }

    private NetworkHandlerThread mThread;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chained_network);
        mThread = new NetworkHandlerThread();
        mThread.start();
    }

    @Override
    protected Dialog onCreateDialog(int id) {
        Dialog dialog = null;
        switch (id) {
            case DIALOG_LOADING :
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setMessage("Loading...");
                dialog = builder.create();
                break;
        }
        return dialog;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mThread.quit();
    }
}




8.3.4 조건부 태스크 삽입


핸들러 스레드는 큐 안의 Message 인스턴스에 대해 훌륭한 제어 기능을 제공하고, Message 인스턴스의 상태를 관찰하도록 해준다. what 매개변수 및 선택적 태그를 통해 메시지를 식별하여 메시지 삽입을 제어할 수 있다.

handler.hasMessage(int what)

handler.hasMessage(int what, Object tag)


태스크 삽입 또한 다양한 밥법으로 사용되고 일반적으로는 큐 안에 이미 존재하는 메시지 유형을 보내지 안도록 하기와 같이 삽입하는것을 추천한다.

if (handler.hasMessage(MESSAGE_WHAT) == false) {

    handler.sendEmptyMessage(MESSAGE_WHAT);

}


8.4 마치며

핸들러 스레드는 세분화된 메시지 제어 기법으로 싱글 스레드, 순차적 테스크 실행자를 제공한다. 메시지 전달은 강력한 비동기 메커니즘을 제공하지만 테스크에 데이터를 제공하는 가장 간단한 방법은 아니다 다음 장에서는 프로그래머가 직접 메시지 전달을 추상화하고 비동기 문제를 해결하기 위한 고수준의 구성요소를 배워 보도록 하자.

댓글
댓글쓰기 폼
공지사항
최근에 달린 댓글
Total
65,392
Today
3
Yesterday
1
TAG
more
«   2022/10   »
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          
글 보관함