2016年3月7日

HandlerThread in Android

HandlerThread 是一種內建了訊息傳遞機制的 Thread,很適合用來當作循序任務處理器。其實也是內建了 Looper 與 MessageQueue,等待要處理的訊息。

HandlerThread handlerThread = new HandlerThread("HandlerThread");
handlerThread.start();

mHandler = new Handler(handlerThread.getLooper()) {
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
};

Handler 的存取能夠透過 HandlerThread 的子類別,設定為私有的,並確保 Looper 不可被存取,由該子類別提供給客戶端公用的存取 method。

public class MyHandlerThread extends HandlerThread {

    private Handler mHandler;

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

    @Override
    protected void onLooperPrepared() {
        super.onLooperPrepared();
        mHandler = new Handler(getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                switch(msg.what) {
                    case 1:
                        // Handle message
                        break;
                    case 2:
                        // Handle message
                        break;
                }
            }
        };
    }

    public void publishedMethod1() {
        mHandler.sendEmptyMessage(1);
    }
    public void publishedMethod2() {
        mHandler.sendEmptyMessage(2);
    }
}

生命週期

  • Creation

    預設 priority 為 Process.THREADPRIORITYDEFAULT ,跟 UI Thread 一樣,可以設定為 Process.THREADPRIORITYBACKGROUND,來執行非關鍵性任務。

    HandlerThread(String name);
    HandlerThread(String name, int priority);
  • Execution

    HandlerThread 永遠會準備好接收訊息。

  • Reset

    Message Queue 可被重設,不處理裡面的訊息,但該 thread 會繼續存活,可以處理新的訊息。重設時不會影響已經在處理中的,以及已經被派送的訊息。

    public void resetHandlerThread() {
        mHandler.removeCallbacksAndMessages(null);
    }
  • Termination

    HandlerThread 透過 quit 及 quitSafely 被終止,使用 quitSafely 可以將已經派送的訊息處理完成後,才結束 HandlerThread。

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

    也可以利用 finalization task 來結束。

    handler.post(new Runnable() {
        @Override
        public void run() {
            Looper.myLooper().quit();
        }
    });

使用案例1: SharedPreferences

public class SharedPreferencesActivity extends Activity {

    TextView mTextValue;

    /**
     * Show read value in a TextView.
     * UI 執行緒的 Handler
     */
    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));
            }
        }
    };

    // 讀寫 SharedPreference 的 background thread
    private class SharedPreferenceThread extends HandlerThread {

        private static final String KEY = "key";
        private SharedPreferences mPrefs;
        private static final int READ = 1;
        private static final int WRITE = 2;

        private Handler mHandler;

        public SharedPreferenceThread() {
            super("SharedPreferenceThread", Process.THREAD_PRIORITY_BACKGROUND);
            mPrefs = 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, mPrefs.getInt(KEY, 0)));
                            break;
                        case WRITE:
                            SharedPreferences.Editor editor = mPrefs.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_shared_preferences);
        mTextValue = (TextView) findViewById(R.id.text_value);
        mThread = new SharedPreferenceThread();
        // 建立 Activity 時,啟動 SharedPreferenceThread
        mThread.start();
    }

    /**
     * Write dummy value from the UI thread.
     */
    public void onButtonClickWrite(View v) {
        mThread.write(mCount++);
    }

    /**
     * Initiate a read from the UI thread.
     */
    public void onButtonClickRead(View v) {
        mThread.read();
    }

    /**
     * Ensure that the background thread is terminated with the Activity.
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mThread.quit();
    }
}

使用案例2: 鏈結的網路呼叫

當第一個網路資源時,就繼續處理第二個,如果失敗,就停止這個 background thread。

public class ChainedNetworkActivity extends Activity {

    private static final int DIALOG_LOADING = 0;

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

    // 在 UI thread 上處理 dialog
    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); // Dummy
            return "A string";
        }

        private void networkOperation2(String data) {
            // Pass data to network, e.g. with HttpPost.
            SystemClock.sleep(2000); // Dummy
        }

        /**
         * Publicly exposed network operation
         */
        public void fetchDataFromNetwork() {
            mHandler.sendEmptyMessage(STATE_A);
        }
    }

    private NetworkHandlerThread mThread;


    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        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;
    }

    /**
     * Ensure that the background thread is terminated with the Activity.
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mThread.quit();
    }
}

Reference

Android 高效能多執行緒