web-dev-qa-db-ja.com

ハンドラー、MessageQueue、ルーパー、それらはすべてUIスレッドで実行されますか?

私はスレッドに頭を回そうとしていますが、Handlerを使用してメッセージ/実行可能ファイルをMessageQueueに投稿し、Looperとして処理のためにハンドラーに送り返されます。

アクティビティのハンドラーに投稿した場合、ActivityHandlerMessageQueueLooperはすべてUIスレッドで実行されていますか?そうでない場合、誰かがこれがすべて一緒になる方法を説明できますか? :)

39
rogerkk

短い答え:それらはすべて同じスレッドで実行されます。 Activityライフサイクルコールバックからインスタンス化される場合、それらはすべてメインUIスレッドで実行されます。

長い答え:

スレッドには、Looperを含むMessageQueueがある場合があります。この機能を使用するには、現在のスレッドにLooperを呼び出す必要があります。 (静的)Looper.prepare()を呼び出し、次に(これも静的)Looper.loop()を呼び出してループを開始します。スレッドごとにLooperが1つしかないため、これらは静的です。

loop()の呼び出しは、通常、しばらくは戻りませんが、メッセージ( "タスク"、 "コマンド"など)を受け取り続けますMessageQueueからそれらを呼び出す)ようにして、それらを個別に処理します(たとえば、メッセージに含まれるRunnableを呼び出すことによって)。キューにメッセージが残っていない場合、スレッドは新しいメッセージがあるまでブロックされます。 Looperを停止するには、quit()を呼び出す必要があります(ループをすぐに停止するのではなく、ループから定期的にチェックされるプライベートフラグを設定し、停止するように通知します)。

ただし、メッセージをキューに直接追加することはできません。代わりに、MessageQueue.IdleHandlerを登録してqueueIdle()コールバックを待機します。コールバックでは、何かをしたいかどうかを決定できます。すべてのハンドラが順番に呼び出されます。 (つまり「キュー」は実際にはキューではなく、定期的に呼び出されるコールバックのコレクションです。)

前の段落に関する注意:これは私が実際に推測したことです。それに関するドキュメントは見つかりませんでしたが、それは理にかなっています。

update:参照してください ahcox 'コメント および 彼の答え

これは多くの作業なので、フレームワークはHandlerクラスを提供して、物事を簡略化しますHandlerインスタンスを作成すると、(デフォルトで)現在のスレッドにすでにアタッチされているLooperにバインドされます。 (Handlerは、先にprepare()を呼び出したため、Looperに何をアタッチするかを認識しています。これにより、Looperへの参照がThreadLocalに格納された可能性があります。)

Handlerを使用すると、post()を呼び出すだけで、「スレッドのメッセージキューにメッセージを書き込む」ことができます。 HandlerはすべてのIdleHandlerコールバックを処理し、ポストされたRunnableが実行されることを確認します。 (遅延して投稿した場合、タイミングが既に正しいかどうかもチェックする場合があります。)

明確にする必要があります。実際にループスレッドdoを作成する唯一の方法は、メッセージをループに投稿することです。これは、ルーパーでquit()を呼び出すまで有効です


Android UIスレッドについて:について(おそらくアクティビティなどが作成される前に)フレームワークはLooperMessageQueueを含む)を設定して開始します。この時点から、UIスレッドで発生するすべてのことはそのループを介して行われます。これにはアクティビティのライフサイクル管理などが含まれます。オーバーライドするすべてのコールバック(onCreate()onDestroy() ...)は少なくともそのループから間接的にディスパッチされます。たとえば、例外のスタックトレースでそれを確認できます([int a = 1 / 0;]をonCreate() ...)


これが理にかなっているといいのですが。以前は不明確だったため申し訳ありません。

66
user634618

質問の「すべてがどのように統合されるか」の部分のフォローアップ。 user634618が書いたように、ルーパーはスレッド(アプリケーションのメインLooperの場合はメインUIスレッド)を引き継ぎます。

  • Looper.loop()は、メッセージキューからメッセージを引き出します。各メッセージには、それが(ターゲットメンバー)に返される関連付けられたハンドラへの参照があります。
  • Looper.loop()の内部キューから取得した各メッセージ:
    • loop()は、メッセージに格納されているハンドラーをターゲットメンバーとして使用してpublic void Handler.dispatchMessage(Message msg)を呼び出します。
    • メッセージにRunnableコールバックメンバーがある場合、それが実行されます。
    • それ以外の場合、ハンドラーに共有のコールバックセットがある場合、それが実行されます。
    • そうでない場合、ハンドラのhandleMessage()は、メッセージを引数として呼び出されます。 (AsyncTaskのようにHandlerをサブクラス化する場合は、handleMessage()をオーバーライドすることができます)。

同じUIスレッド上にあるすべての共同オブジェクトについての質問では、Handlerは、メッセージの送信先であるLooperと同じスレッド上に作成する必要があります。そのコンストラクターは、現在のLooperを検索してメンバーとして格納し、HandlerをそのLooperに結び付けます。また、Looperのメッセージキューを自身のメンバーで直接参照します。 Handlerは、任意のスレッドからLooperに作業を送信するために使用できますが、メッセージキューのこのIDは、Looperのスレッドで実行される作業をルーティングします。

別のスレッドでいくつかのコードを実行していて、UIスレッドで実行されるRunnableを送信したい場合は、次のようにします。

// h is a Handler that we constructed on the UI thread.
public void run_on_ui_thread(final Handler h, final Runnable r)
{
   // Associate a Message with our Handler and set the Message's
   // callback member to our Runnable:
   final Message message = Message.obtain(h, r);

   // The target is the Handler, so this asks our Handler to put
   // the Message in its message queue, which is the exact same
   // message queue associated with the Looper on the thread on
   // which the Handler was created:
   message.sendToTarget();
}
11
ahcox

コンセプトを理解するために、私は自分でこれらのインターフェースを実装しようとしています。簡単に言うと、必要に応じてインターフェースを使うだけです。これが私のテストコードです:

import Java.util.concurrent.BlockingQueue;
import Java.util.concurrent.LinkedBlockingQueue;

public class TestLooper {

    public static void main(String[] args) {
        UIThread thread = new UIThread();
        thread.start();

        Handler mHandler = new Handler(thread.looper);
        new WorkThread(mHandler, "out thread").run();
    }
}

class Looper {
    private BlockingQueue<Message> message_list = new LinkedBlockingQueue<Message>();

    public void loop() {

        try {
            while (!Thread.interrupted()) {
                Message m = message_list.take();
                m.exeute();
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    public void insertMessage(Message msg) {
        message_list.add(msg);
    }

}

class Message {
    String data;
    Handler handler;

    public Message(Handler handler) {
        this.handler = handler;
    }

    public void setData(String data) {
        this.data = data;
    }

    public void exeute() {
        handler.handleMessage(this);
    }
}

class Handler {

    Looper looper;

    public Handler(Looper looper) {
        this.looper = looper;
    }

    public void dispatchMessage(Message msg) {
        System.out.println("Handler dispatchMessage" + Thread.currentThread());
        looper.insertMessage(msg);
    }

    public Message obtainMessage() {
        return new Message(this);
    }

    public void handleMessage(Message m) {
        System.out.println("handleMessage:" + m.data + Thread.currentThread());
    }
}

class WorkThread extends Thread {
    Handler handler;
    String tag;

    public WorkThread(Handler handler, String tag) {
        this.handler = handler;
        this.tag = tag;
    }

    public void run() {
        System.out.println("WorkThread run" + Thread.currentThread());
        Message m = handler.obtainMessage();
        m.setData("message " + tag);
        handler.dispatchMessage(m);
    }
}

class UIThread extends Thread {

    public Looper looper = new Looper();

    public void run() {

            //create handler in ui thread
        Handler mHandler = new Handler(looper);

        new WorkThread(mHandler, "inter thread").run();
        System.out.println("thead run" + Thread.currentThread());
        looper.loop();
    }

}
2
julianjing

アクティビティのハンドラーに投稿した場合、アクティビティ、ハンドラー、メッセージキュー、ルーパーはすべてUIスレッドで実行されていますか?そうでない場合、誰かがこれがすべて一緒になる方法を説明できますか? :)

作成方法によって異なります Handler

ケース1:

Handler()

デフォルトのコンストラクターは、このハンドラーを現在のスレッドの Looper に関連付けます。

UIスレッドでこのようにHandlerを作成すると、UIスレッドのHandlerLooperが関連付けられます。 MessageQueueは、UIスレッドでLooperにも関連付けられています。

ケース2:

Handler (Looper looper)

デフォルトのものの代わりに提供されたルーパーを使用してください。

HandlerThread を作成し、HandlerThreadのルーパーをハンドラーに渡すと、ハンドラーとルーパーはUIスレッドではなくHandlerThreadに関連付けられます。 HandlerMessageQueueおよびLooperHandlerThreadに関連付けられています。

ユースケース:ネットワークを実行したいOR IOオペレーション。UIスレッドでは実行できないため、HandlerThreadが便利あなたのために。

 HandlerThread handlerThread = new HandlerThread("NetworkOperation");
 handlerThread.start();
 Handler requestHandler = new Handler(handlerThread.getLooper());

HandlerThreadからUIスレッドにデータを返す場合は、UIスレッドからLooperを使用してハンドラーを1つ作成し(例:responseHandler)、sendMessageを呼び出します。 UIスレッドresponseHandlerhandleMessageをオーバーライドする必要があります

詳細については、これらの投稿を参照してください。

ルーパーの目的と使用方法は? (概念について)

Android:スレッドでトースト (これらの概念すべてをリンクするコード例)

1
Ravindra babu