はじめに
こんにちは、matsunariです。今回はHanderクラスについて勉強しましたのでまとめたいと思います。
Handlerクラスを使う理由
Android アプリでスレッド処理を行いたい場合、注意が必要なのが別のスレッドから直接アプリのUIに関する操作を行うことはできません。
なぜこのような制限があるのでしょうか?
例えば、メインスレッドでないスレッドが並列で実行されているときに、メインスレッドとそれ以外のスレッドが同時に同じテキストビューにsetText()を試みる場合です。
このような場合、2つのうちどちらのスレッドのsetText()が適用されるかは予測できず、ユーザは2つのうち1つの値しか見ることができません。
このように2つ以上のスレッドを使用する際の同期問題を解消するためにLooperとHandlerを使います。
(ちなみに別のスレッドから直接UI操作の処理を行おうとすると、Called from Wrong Thread Exceptionというエラーが発生します。)
Handlerクラスの基本
Handlerクラスを使う前にどのような仕組みで動作しているのか確認してみましょう。
実はUIを管理するメインスレッドでは、内部的にLooper というオブジェクトを持っています。さらにその中にMessageQueueを含んでいます。MessageQueueとはコレクションフレームワークのQueueの一種のことです。(備考:Queueは入れた順番が古いものから処理されるFirst In First Outの性質があります)。
LooperはMessageやRunnableオブジェクトを順番に取り出しHandlerが処理するように伝えます。Messageはスレッド間通信する内容を盛り込んだオブジェクトのことです。
HandlerはLooperから受けたMessageを実行、処理したり、Looperの持つMessageQueueに送信するための装置のようなものだと考えると良いでしょう。
Handlerを使ってsetText()を実行してみる
実際にHandlerを使ったサンプルを確認してみましょう。
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | package com.example.handlersample; import android.os.Bundle; import android.os.Handler; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //UI部分の設定 LinearLayout layout = new LinearLayout(this); layout.setOrientation(LinearLayout.VERTICAL); setContentView(layout); final TextView tv = new TextView(this); tv.setText("通常は別スレッドから直接UIを変更できません"); Button button = new Button(this); button.setText("ボタンをおしてください"); layout.addView(tv); //Handlerインスタンスの生成 final Handler handler = new Handler(); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { new Thread(new Runnable() { @Override public void run() { //Handlerのpostメソッドを使用する。 handler.post(new Runnable() { @Override public void run() { tv.setText("Handlerを使って別スレッドからsetText()を行うことに成功しました。"); } }); } }).start(); } }); layout.addView(button); } } |
このサンプルを実行すると次のような結果を得ることができます。
重要なポイントは、post()メソッドはhandlerインスタンスを生成したスレッドにrunnableオブジェクトを送るというところです。今回のケースでは、メインスレッドでhandlerインスタンスが生成されているためメインスレッドのUIは問題なく変化しました。これを別スレッドで行なったとしても効果はありません。実行するスレッドに注意が必要です。
sendMessage()を実行してみる
今度は冒頭にも少し説明したMessageオブジェクトを使ったサンプルを見てみましょう。
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | package com.example.sendmessagesample; 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.LinearLayout; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { private static final String TAG = "Send Message Sample"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //UI部分の設定 LinearLayout layout = new LinearLayout(this); layout.setOrientation(LinearLayout.VERTICAL); Button button = new Button(this); setContentView(layout); final TextView tv = new TextView(this); tv.setText("What's Message"); layout.addView(tv); layout.addView(button); //Handlerインスタンスの生成 final Handler handler = new Handler() { public void handleMessage(Message msg) { tv.setText(String.format("what=%s", msg.what)); Log.v(TAG, String.format("what=%s\targ1=%d\targ2=%d\t%s", msg.what, msg.arg1, msg.arg2, msg.obj)); } }; button.setText("PUSH"); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { new Thread(new Runnable() { @Override public void run() { //Messageオブジェクトを使ってsendMessageを行う Message msg = new Message(); msg.what = 4645; msg.arg1 = 37458; msg.arg2 = 223606; msg.obj = "obj"; handler.sendMessage(msg); } }).start(); } }); } } |
上記サンプルコードではsendMessageメソッドを使用しています。sendMessageメソッドを使うことで、引数にMessageオブジェクトを指定して、情報をhandleMessageメソッドに渡す事ができます。Messageクラスのプロパティに関しては以下の通りです。
◼️int what
メッセージの識別子として使う事を目的とした、ユーザが勝手に定義して使う事のできる値のこと。
◼️int arg1,int arg2
whatの他に、arg1とarg2に指定した2つの整数値を渡す事ができる。
◼️Object obj
オブジェクトを渡したい場合は、この変数に代入する事ができる。
サンプルコードを実行するとこのような結果になりました。
出力したログ
さいごに
今回は前回投稿のスレッドに続いてHandlerについてまとめてみました。Handler自体の役割は比較的理解しやすい感じはありましたが、仕組みまで覗いてみると深い内容だったので調べ甲斐がありました。またこの他にもスレッドに関係する機能について深掘りを進めていきたいと思っています。