回答:
A Looper
はメッセージ処理ループです。A からアイテムを読み取って処理しますMessageQueue
。Looper
クラスは、通常と一緒に使用されるHandlerThread
(のサブクラスThread
)。
A Handler
は、主にLooper
メッセージとRunnable
オブジェクトをスレッドのに投稿することにより、との対話を容易にするユーティリティクラスですMessageQueue
。ときにHandler
作成され、それが特定にバインドされているLooper
(とそれに関連するスレッドとメッセージキュー)。
一般的な使用方法では、を作成して開始しHandlerThread
、次に、Handler
他のスレッドがHandlerThread
インスタンスと対話できる1つまたは複数のオブジェクトを作成します。Handler
上で実行中に作成する必要がありますHandlerThread
作成した後、スレッドが使用できるものに制限はありませんが、Handler
のスケジューリング方法(post(Runnable)
など)
Androidアプリケーションのメインスレッド(別名UIスレッド)は、アプリケーションインスタンスが作成される前にハンドラースレッドとして設定されます。
クラスのドキュメントとは別に、このすべてについてここで素晴らしい議論があります。
PS上記のすべてのクラスはパッケージに含まれていますandroid.os
。
MessageQueue
いる状態MessageQueue
である「から派遣されるメッセージのリストを保持している低レベルのクラスLooper
。」
Androidのメインスレッド以外のスレッドから直接UIコンポーネントを更新することは違法であることは広く知られています。このAndroidドキュメント(UIスレッドでの負荷の高い操作の処理)は、別のスレッドを開始して高価な作業を行い、完了後にUIを更新する必要がある場合の手順を示しています。このアイデアは、メインスレッドに関連付けられたHandlerオブジェクトを作成し、適切なときにRunnableをそれに送信することです。これはメインスレッドで呼び出されます。このメカニズムは、ルーパークラスとハンドラークラスで実装されます。Runnable
Looper
クラスは維持メッセージキューのリストが含まれ、メッセージを。Looperの重要な特徴は、が作成されるスレッドに関連付けられていることです。この関連付けは永久に保持され、解除したり変更したりすることはできません。また、スレッドがあることに注意してください以上に関連付けることはできません 1 。この関連付けを保証するために、はスレッドローカルストレージに保存され、コンストラクタを介して直接作成することはできません。これを作成する唯一の方法は、でprepare staticメソッドを呼び出すことです。prepareメソッドは最初にThreadLocalを調べますLooper
Looper
Looper
Looper
スレッドに関連付けられたルーパーがまだないことを確認するために、現在のスレッドの。試験後、新しいLooper
が作成され、に保存されThreadLocal
ます。を準備したら、その上でループメソッドをLooper
呼び出して新しいメッセージをチェックし、それらに対処する必要があります。Handler
名前が示すように、Handler
クラスは主に現在のスレッドのメッセージの処理(追加、削除、ディスパッチ)を担当しますMessageQueue
。Handler
インスタンスは、スレッドにバインドされています。ハンドラとスレッドの間のバインディングは、およびを介しLooper
て行われMessageQueue
ます。AがHandler
され、常ににバインドされLooper
、その後に結合された関連するスレッドでLooper
。とは異なりLooper
、複数のHandlerインスタンスを同じスレッドにバインドできます。でpostまたは任意のメソッドを呼び出すたびHandler
に、関連付けられたに新しいメッセージが追加されますMessageQueue
。メッセージのターゲットフィールドは現在のHandler
インスタンスに設定されます。ときLooper
このメッセージを受信すると、メッセージのターゲットフィールドでdispatchMessageを呼び出します。これにより、メッセージはHandlerインスタンスにルーティングされて処理されますが、正しいスレッド上にあります。間の関係Looper
、Handler
およびMessageQueue
以下に示します。
ルーパーから始めましょう。ルーパーとは何かを理解すると、ルーパー、ハンドラー、メッセージキューの関係をより簡単に理解できます。また、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フレームワークでもほぼ同じメカニズムを見つけることができます。
MessageQueue
:これは、によってディスパッチされるメッセージのリストを保持する低レベルのクラスLooper
です。メッセージは直接に追加されるのMessageQueue
ではなくHandler
、Looper
。[ 3 ]に関連付けられたオブジェクトを通じて追加されます。
Looper
:MessageQueue
ディスパッチされるメッセージを含むa をループします。キューを管理する実際のタスクはHandler
、メッセージキュー内のメッセージの処理(追加、削除、ディスパッチ)を担当するによって行われます。[ 2 ]
Handler
:スレッドに関連付けられているオブジェクトMessage
とRunnable
オブジェクトを送信および処理できますMessageQueue
。各Handlerインスタンスは単一のスレッドとそのスレッドのメッセージキューに関連付けられています。[ 4 ]
新しいを作成するとHandler
、それは、それを作成しているスレッドのスレッド/メッセージキューにバインドされます-その時点から、メッセージと実行可能ファイルをそのメッセージキューに配信し、メッセージキューから出たときに実行します。
理解を深めるために、下の画像[ 2 ]に目を通してください。
@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() ;