Looper
、Handler
およびの公式Androidドキュメント/ガイドを確認しましたMessageQueue
。しかし、私はそれを得ることができませんでした。私はAndroidを初めて使用し、これらの概念に非常に混乱しました。
5 に答える
ALooper
はメッセージ処理ループです。からアイテムを読み取って処理しますMessageQueue
。このクラスは通常、 (のサブクラス)Looper
と組み合わせて使用されます。HandlerThread
Thread
Aは、主にスレッドのにメッセージとオブジェクトを投稿することによってHandler
、との対話を容易にするユーティリティクラスです。aが作成されると、特定の(および関連するスレッドとメッセージキュー)にバインドされます。Looper
Runnable
MessageQueue
Handler
Looper
通常の使用法では、を作成して開始してから、他のスレッドがインスタンスと対話できる1つまたは複数のオブジェクトHandlerThread
を作成します。で実行中に作成する必要がありますが、一度作成すると、のスケジューリング方法(など)を使用できるスレッドに制限はありません。Handler
HandlerThread
Handler
HandlerThread
Handler
post(Runnable)
Androidアプリケーションのメインスレッド(別名UIスレッド)は、アプリケーションインスタンスが作成される前に、ハンドラースレッドとして設定されます。
クラスのドキュメントとは別に、ここでこれらすべてについての素晴らしい議論があります。
PS上記のすべてのクラスはパッケージに含まれていますandroid.os
。
Androidのメインスレッド以外のスレッドからUIコンポーネントを直接更新することは違法であることが広く知られています。このAndroidドキュメント(UIスレッドでの高価な操作の処理)では、別のスレッドを開始して高価な作業を実行し、完了後にUIを更新する必要がある場合の手順を示しています。アイデアは、メインスレッドに関連付けられたHandlerオブジェクトを作成し、適切なタイミングでそれにRunnableを投稿することです。これはメインスレッドで呼び出されます。このメカニズムは、LooperクラスとHandlerクラスで実装されます。Runnable
このLooper
クラスは、リストメッセージを含むMessageQueueを維持します。Looperの重要な特徴は、が作成されたスレッドに関連付けられていることです。この関連付けは永久に保持され、壊したり変更したりすることはできません。また、スレッドを複数のスレッドに関連付けることはできないことに注意してください。この関連付けを保証するために、はスレッドローカルストレージに格納され、コンストラクターを介して直接作成することはできません。それを作成する唯一の方法は、でpreparestaticメソッドを呼び出すことです。prepareメソッドは最初にThreadLocalを調べますLooper
Looper
Looper
Looper
スレッドに関連付けられたルーパーがまだ存在しないことを確認するために、現在のスレッドの 検査後、新しいLooper
ものが作成され、に保存されThreadLocal
ます。を準備したら、ループLooper
メソッドを呼び出して新しいメッセージをチェックし、それらを処理する必要があります。Handler
名前が示すように、Handler
クラスは主に現在のスレッドのメッセージの処理(追加、削除、ディスパッチ)を担当しますMessageQueue
。Handler
インスタンスもスレッドにバインドされます。ハンドラーとスレッド間のバインディングは、とを介して実現さLooper
れMessageQueue
ます。AHandler
は常ににバインドされ、Looper
その後、に関連付けられたスレッドにバインドされますLooper
。とは異なりLooper
、複数のHandlerインスタンスを同じスレッドにバインドできます。でpostまたは同様のメソッドを呼び出すたびHandler
に、関連付けられたに新しいメッセージが追加されますMessageQueue
。メッセージのターゲットフィールドは現在のHandler
インスタンスに設定されます。いつLooper
このメッセージを受信すると、メッセージのターゲットフィールドでdispatchMessageが呼び出されるため、メッセージは処理対象のHandlerインスタンスに戻りますが、正しいスレッド上にあります。Looper
との間の関係をHandler
以下MessageQueue
に示します。
ルーパーから始めましょう。Looperとは何かを理解すると、Looper、Handler、MessageQueueの関係をより簡単に理解できます。また、GUIフレームワークのコンテキストでLooperが何であるかをよりよく理解できます。ルーパーは2つのことをするように作られています。
1)Looperは、メソッドが戻ると終了する通常のスレッドを、 Androidアプリが実行されるまで継続的に実行されるものに変換します。これはGUIフレームワークで必要です(技術的には、メソッドが戻ると終了します。しかし、私が何を意味するのかを明確にしましょう。下)。run()
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()
。たとえば、メインスレッドは最初の画面を表示するための最初のジョブを終了し、スリープします。新しいジョブがフェッチされると、起動します。つまり、中断されます。これまでのところ良好ですが、現時点では、複数のジョブを保持するためのキューのようなデータ構造が必要です。ユーザーが画面を連続してタッチし、タスクの完了に時間がかかる場合を考えてみてください。したがって、先入れ先出し方式で実行されるジョブを保持するためのデータ構造が必要です。また、割り込みを使用して、常に実行され、プロセスジョブが到着したときにスレッドを実装することは簡単ではなく、複雑で、多くの場合、保守不可能なコードにつながることを想像してみてください。そのような目的のために新しいメカニズムを作成したいのですが、それがLooperのすべてです。ルーパークラスの公式文書「スレッドにはデフォルトでメッセージループが関連付けられていません」と述べており、Looperは「スレッドのメッセージループを実行するために使用される」クラスです。これで、それが何を意味するのかを理解できます。
ハンドラーとメッセージキューに移動しましょう。まず、MessageQueueは前述のキューです。それはルーパーの中にあり、それだけです。Looperクラスのソースコードで確認できます。Looperクラスには、MessageQueueのメンバー変数があります。
では、ハンドラーとは何ですか?キューがある場合は、新しいタスクをキューにエンキューできるようにするメソッドがあるはずですよね?それがハンドラーが行うことです。さまざまな方法を使用して、新しいタスクをキュー(MessageQueue)にエンキューできpost(Runnable r)
ます。それでおしまい。これはすべて、ルーパー、ハンドラー、およびメッセージキューに関するものです。
私の最後の言葉は、基本的にLooperは、GUIフレームワークで発生する問題に対処するために作成されたクラスです。しかし、この種のニーズは他の状況でも発生する可能性があります。実際、これはマルチスレッドアプリケーションで非常に有名なパターンであり、Doug Leaによる「Javaでの同時プログラミング」で詳しく知ることができます(特に、4.1.4章「ワーカースレッド」が役立ちます)。また、この種のメカニズムはAndroidフレームワークに固有のものではないことを想像できますが、すべてのGUIフレームワークはこれにいくらか類似している必要があります。JavaSwingフレームワークにもほぼ同じメカニズムがあります。
MessageQueue
:これは、によってディスパッチされるメッセージのリストを保持する低レベルのクラスLooper
です。メッセージはに直接追加されるのMessageQueue
ではなく、 。[ 3 ]Handler
に関連付けられたオブジェクトを介して追加されます。Looper
Looper
MessageQueue
:ディスパッチされるメッセージを含むをループします。キューを管理する実際のタスクはHandler
、メッセージキュー内のメッセージの処理(追加、削除、ディスパッチ)を担当するによって行われます。[ 2 ]
Handler
:スレッドのに関連付けられたオブジェクトを送信および処理できMessage
ます。各Handlerインスタンスは、単一のスレッドとそのスレッドのメッセージキューに関連付けられています。[ 4 ]Runnable
MessageQueue
新しいを作成すると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:ビュー階層を作成した元のスレッドのみがそのビューにアクセスできます。
言い換えると、 ieまたはHandler
パスタスクへの参照を保持するを使用する必要があります。MainLooper
Main Thread
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() ;