カテゴリー: Android

【Java】Handlerクラスについてまとめてみました

はじめに

こんにちは、matsunariです。今回はHanderクラスについて勉強しましたのでまとめたいと思います。

Handlerクラスを使う理由

Android アプリでスレッド処理を行いたい場合、注意が必要なのが別のスレッドから直接アプリのUIに関する操作を行うことはできません。
なぜこのような制限があるのでしょうか?

例えば、メインスレッドでないスレッドが並列で実行されているときに、メインスレッドとそれ以外のスレッドが同時に同じテキストビューにsetText()を試みる場合です。

このような場合、2つのうちどちらのスレッドのsetText()が適用されるかは予測できず、ユーザは2つのうち1つの値しか見ることができません。

このように2つ以上のスレッドを使用する際の同期問題を解消するためにLooperHandlerを使います。
(ちなみに別のスレッドから直接UI操作の処理を行おうとすると、Called from Wrong Thread Exceptionというエラーが発生します。)

Handlerクラスの基本

Handlerクラスを使う前にどのような仕組みで動作しているのか確認してみましょう。

実はUIを管理するメインスレッドでは、内部的にLooper というオブジェクトを持っています。さらにその中にMessageQueueを含んでいます。MessageQueueとはコレクションフレームワークのQueueの一種のことです。(備考:Queueは入れた順番が古いものから処理されるFirst In First Outの性質があります)。
LooperMessageRunnableオブジェクトを順番に取り出しHandlerが処理するように伝えます。Messageはスレッド間通信する内容を盛り込んだオブジェクトのことです。

HandlerはLooperから受けたMessageを実行、処理したり、Looperの持つMessageQueueに送信するための装置のようなものだと考えると良いでしょう。

Handlerを使ってsetText()を実行してみる

実際にHandlerを使ったサンプルを確認してみましょう。

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オブジェクトを使ったサンプルを見てみましょう。

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自体の役割は比較的理解しやすい感じはありましたが、仕組みまで覗いてみると深い内容だったので調べ甲斐がありました。またこの他にもスレッドに関係する機能について深掘りを進めていきたいと思っています。

おすすめ書籍

narimatsutetsuya

シェア
執筆者:
narimatsutetsuya
タグ: AndroidJava

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

2週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

4週間 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前