Androidのルーパー、ハンドラー、メッセージキューの関係は何ですか?


95

私は公式のAndroidドキュメント/ガイドをチェックしてLooperHandlerMessageQueue。しかし、私はそれを得ることができませんでした。私はAndroidを初めて使用するので、これらの概念と非常に混乱しました。

回答:


103

A Looperはメッセージ処理ループです。A からアイテムを読み取って処理しますMessageQueueLooperクラスは、通常と一緒に使用されるHandlerThread(のサブクラスThread)。

A Handlerは、主にLooperメッセージとRunnableオブジェクトをスレッドのに投稿することにより、との対話を容易にするユーティリティクラスですMessageQueue。ときにHandler作成され、それが特定にバインドされているLooper(とそれに関連するスレッドとメッセージキュー)。

一般的な使用方法では、を作成して開始しHandlerThread、次に、Handler他のスレッドがHandlerThreadインスタンスと対話できる1つまたは複数のオブジェクトを作成します。Handler上で実行中に作成する必要がありますHandlerThread作成した後、スレッドが使用できるものに制限はありませんが、Handlerのスケジューリング方法(post(Runnable)など)

Androidアプリケーションのメインスレッド(別名UIスレッド)は、アプリケーションインスタンスが作成される前にハンドラースレッドとして設定されます。

クラスのドキュメントとは別に、このすべてについてここで素晴らしい議論があります

PS上記のすべてのクラスはパッケージに含まれていますandroid.os


@Ted Hopp-Looperのメッセージキューはスレッドのメッセージキューとは異なりますか?
CopsOnRoad 2018年

2
@ジャック-彼らは同じものです。AndroidのためのAPIドキュメントMessageQueueいる状態MessageQueueである「から派遣されるメッセージのリストを保持している低レベルのクラスLooper
テッドのHopp

95

Androidのメインスレッド以外のスレッドから直接UIコンポーネント更新することは違法であることは広く知られています。このAndroidドキュメント(UIスレッドでの負荷の高い操作の処理)は、別のスレッドを開始して高価な作業を行い、完了後にUIを更新する必要がある場合の手順を示しています。このアイデアは、メインスレッドに関連付けられたHandlerオブジェクトを作成し、適切なときにRunnableをそれに送信することです。これはメインスレッドで呼び出されます。このメカニズムは、ルーパークラスとハンドラークラスで実装されます。Runnable

Looperクラスは維持メッセージキューのリストが含まれ、メッセージを。Looperの重要な特徴はが作成されるスレッドに関連付けられていることです。この関連付けは永久に保持され、解除したり変更したりすることはできません。また、スレッドがあることに注意してください以上に関連付けることはできません 1 。この関連付けを保証するために、はスレッドローカルストレージに保存され、コンストラクタを介して直接作成することはできません。これを作成する唯一の方法は、でprepare staticメソッドを呼び出すことです。prepareメソッドは最初にThreadLocalを調べますLooperLooperLooperLooperスレッドに関連付けられたルーパーがまだないことを確認するために、現在のスレッドの。試験後、新しいLooperが作成され、に保存されThreadLocalます。を準備したら、その上でループメソッドをLooper呼び出して新しいメッセージをチェックし、それらに対処する必要があります。Handler

名前が示すように、Handlerクラスは主に現在のスレッドのメッセージの処理(追加、削除、ディスパッチ)を担当しますMessageQueueHandlerインスタンスは、スレッドにバインドされています。ハンドラとスレッドの間バインディングは、およびを介しLooperて行われMessageQueueます。AがHandlerされ、常ににバインドされLooper、その後に結合された関連するスレッドLooper。とは異なりLooper、複数のHandlerインスタンスを同じスレッドにバインドできます。でpostまたは任意のメソッドを呼び出すたびHandlerに、関連付けられたに新しいメッセージが追加されますMessageQueue。メッセージのターゲットフィールドは現在のHandlerインスタンスに設定されます。ときLooperこのメッセージを受信すると、メッセージのターゲットフィールドでdispatchMessageを呼び出します。これにより、メッセージはHandlerインスタンスにルーティングされて処理されますが、正しいスレッド上にあります。間の関係LooperHandlerおよびMessageQueue以下に示します。

ここに画像の説明を入力してください


5
ありがとう!しかし、ハンドラーが最初にメッセージキューにメッセージを投稿し、次に同じキューからのメッセージを処理する場合のポイントは何ですか?なぜメッセージを直接処理しないのですか?
Blake

4
@Blake b / c 1つのスレッド(非ルーパースレッド)から投稿していますが、別のスレッド(ルーパースレッド)でメッセージを処理しています
numan salati

developer.android.comに記載さている内容よりもはるかに優れていますが、提供した図のコードを確認すると便利です。
tfmontague 2017

@numansalati-ルーパースレッドからのメッセージをハンドラーが投稿できませんか?
CopsOnRoad 2018年

78

ルーパーから始めましょう。ルーパーとは何かを理解すると、ルーパー、ハンドラー、メッセージキューの関係をより簡単に理解できます。また、GUIフレームワークのコンテキストでLooperが何であるかをよりよく理解できます。ルーパーは2つのことをするように作られています。

1)Looper は、メソッドが戻ると終了する通常のスレッドをAndroidアプリが実行されるまで継続的に実行されるものに変換しrun()ます。これは、GUIフレームワークで必要です(技術的には、メソッドが戻ると終了します。未満)。run()

2)Looper は、実行するジョブがキューに入れられるキューを提供します。これは、GUIフレームワークでも必要です。

ご存知かもしれませんが、アプリケーションが起動すると、システムは「メイン」と呼ばれるアプリケーションの実行スレッドを作成します。Androidアプリケーションは通常、デフォルトで「メインスレッド」という単一のスレッドで完全に実行されます。しかし、メインスレッドは秘密ではなく、特別なスレッドです。これは通常のスレッドであり、new Thread()コードで作成することもできrun()ます。つまり、メソッドが戻ると終了します。以下の例を考えてください。

public class HelloRunnable implements Runnable {
    public void run() {
        System.out.println("Hello from a thread!");
    }

    public static void main(String args[]) {
        (new Thread(new HelloRunnable())).start();
    }
}

それでは、この簡単な原則をAndroidアプリに適用してみましょう。Androidアプリを通常のスレッドで実行するとどうなりますか?「メイン」または「UI」と呼ばれるスレッド、またはアプリケーションを起動するもので、すべてのUIを描画します。したがって、最初の画面がユーザーに表示されます。ならどうしよう?メインスレッドは終了しますか?いいえ、必要ありません。ユーザーが何かをするまで待つべきですよね?しかし、どうすればこの動作を実現できますか?まあ、Object.wait()またはで試すことができますThread.sleep()。たとえば、メインスレッドは最初のジョブを終了して最初の画面を表示し、スリープします。実行する新しいジョブがフェッチされると、それは目覚めます。つまり、中断されます。これまでのところ良好ですが、現時点では、複数のジョブを保持するためのキューのようなデータ構造が必要です。ユーザーが連続して画面をタッチし、タスクの完了に時間がかかる場合を考えてみます。したがって、先入れ先出しで実行されるジョブを保持するためのデータ構造が必要です。また、ご想像のとおり、割り込みを使用して実行中および処理中のジョブが到着したときにスレッドを実装することは容易ではなく、複雑でしばしばメンテナンス不可能なコードにつながります。私たちはそのような目的のために新しいメカニズムを作成したいと思っています。それがルーパーのすべてですルーパークラス公式ドキュメント「デフォルトでは、スレッドにはメッセージループが関連付けられていない」とあり、ルーパーは「スレッドのメッセージループを実行するために使用される」クラスです。これで、その意味を理解できます。

ハンドラーとメッセージキューに移りましょう。まず、MessageQueueは、前述のキューです。それはルーパーの中にあります、そしてそれだけです。Looperクラスのソースコードで確認できます。Looperクラスには、MessageQueueのメンバー変数があります。

次に、ハンドラとは何ですか?キューがある場合、新しいタスクをキューにエンキューできるメソッドが必要ですよね?それがハンドラーが行うことです。さまざまなpost(Runnable r)メソッドを使用して、新しいタスクをキュー(MessageQueue)にエンキューできます。それでおしまい。これはすべてLooper、Handler、およびMessageQueueに関するものです。

私の最後の言葉は、基本的にLooperは、GUIフレームワークで発生する問題に対処するために作成されたクラスです。ただし、このようなニーズは他の状況でも発生する可能性があります。実際、これはマルチスレッドアプリケーションのかなり有名なパターンであり、Doug Leaによる「Javaでの並行プログラミング」で詳細を学ぶことができます(特に、4.1.4章「ワーカースレッド」が役立つでしょう)。また、この種類のメカニズムはAndroidフレームワークに固有のものではないことを想像できますが、すべてのGUIフレームワークはこれと多少似ている必要があるかもしれません。Java Swingフレームワークでもほぼ同じメカニズムを見つけることができます。


4
ベストアンサー。この詳細な説明からさらに学びました。より詳細なブログ記事があるかしら。
capt.swag 2017年

ハンドラーを使用せずにメッセージをMessageQueueに追加できますか?
CopsOnRoad 2018年

@CopsOnRoadいいえ、直接追加することはできません。
Faisal Naseer

私の一日を作った...あなたへの愛のたくさん:)
Rahul Matte

26

MessageQueue:これは、によってディスパッチされるメッセージのリストを保持する低レベルのクラスLooperです。メッセージは直接に追加されるのMessageQueueではなくHandlerLooper。[ 3 ]に関連付けられたオブジェクトを通じて追加されます。

LooperMessageQueueディスパッチされるメッセージを含むa をループします。キューを管理する実際のタスクはHandler、メッセージキュー内のメッセージの処理(追加、削除、ディスパッチ)を担当するによって行われます。[ 2 ]

Handler:スレッドに関連付けられているオブジェクトMessageRunnableオブジェクトを送信および処理できますMessageQueue。各Handlerインスタンスは単一のスレッドとそのスレッドのメッセージキューに関連付けられています。[ 4 ]

新しいを作成するとHandler、それは、それを作成しているスレッドのスレッド/メッセージキューにバインドされます-その時点から、メッセージと実行可能ファイルをそのメッセージキューに配信し、メッセージキューから出たときに実行します

理解を深めるために、下の画像[ 2 ]に目を通してください。

ここに画像の説明を入力してください


0

@K_Anasによる例を使用した回答の拡張

Androidのメインスレッド以外のスレッドから直接UIコンポーネントを更新することは違法であることは広く知られています。

たとえば、スレッドを使用してUIを更新しようとした場合などです。

    int count = 0;
    new Thread(new Runnable(){
        @Override
        public void run() {
            try {
                while(true) {
                    sleep(1000);
                    count++;
                    textView.setText(String.valueOf(count));
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


   ).start();

アプリは例外でクラッシュします。

android.view.ViewRoot $ CalledFromWrongThreadException:ビュー階層を作成した元のスレッドのみがそのビューにアクセスできます。

言い換えるHandlerと、MainLooper ie Main Threadまたはへの参照を保持するwhich を使用し、UI Threadタスクをとして渡す必要がありますRunnable

  Handler handler = new Handler(getApplicationContext().getMainLooper);
        int count = 0;
        new Thread(new Runnable(){
            @Override
            public void run() {
                try {
                    while(true) {
                        sleep(1000);
                        count++;
                        handler.post(new Runnable() {
                           @Override
                           public void run() {
                                 textView.setText(String.valueOf(count));
                           }
                         });

                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    ).start() ;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.