アプリケーション コンポーネントが起動し、アプリに実行中の他のコンポーネントがない場合、Android システムは、1 つの実行スレッドでアプリの新しい Linux プロセスを開始します。デフォルトでは、同じアプリのすべてのコンポーネントは、メインスレッドと呼ばれる同じプロセスとスレッドで実行されます。
アプリケーション コンポーネントが起動し、そのアプリケーションのプロセスがすでに存在する場合(アプリケーションの別のコンポーネントがすでに起動しているため)、コンポーネントはそのプロセス内で起動し、同じ実行スレッドを使用します。ただし、アプリケーション内のさまざまなコンポーネントを別々のプロセスで実行するように設定したり、任意のプロセスに追加のスレッドを作成したりすることはできます。
このドキュメントでは、Android アプリケーションでのプロセスとスレッドの仕組みについて説明します。
プロセス
デフォルトでは、アプリのすべてのコンポーネントは同じプロセスで実行されます。ほとんどのアプリでは、この設定は変更されません。ただし、特定のコンポーネントが属するプロセスを制御する必要がある場合は、マニフェスト ファイルで制御できます。
各タイプのコンポーネント要素(<activity>、<service>、<receiver>、<provider>)のマニフェスト エントリは、コンポーネントが実行されるプロセスを指定できる android:process 属性をサポートしています。この属性を設定して、各コンポーネントが独自のプロセスで実行されるようにしたり、一部のコンポーネントがプロセスを共有し、他のコンポーネントが共有しないようにしたりできます。
アプリが同じ Linux ユーザー ID を共有し、同じ証明書で署名されている場合、異なるアプリのコンポーネントが同じプロセスで実行されるように android:process を設定することもできます。
<application> 要素は android:process 属性もサポートしています。この属性を使用すると、すべてのコンポーネントに適用されるデフォルト値を設定できます。
Android は、ユーザーに直接サービスを提供している他のプロセスでリソースが必要になった場合、ある時点でプロセスをシャットダウンすることがあります。シャットダウンされたプロセスで実行されているアプリケーション コンポーネントは、結果として破棄されます。これらのコンポーネントに処理すべき作業がある場合、プロセスが再度開始されます。
Android システムは、シャットダウンするプロセスを決定する際に、ユーザーにとっての相対的な重要度を考慮します。たとえば、画面に表示されなくなったアクティビティをホストするプロセスは、表示されているアクティビティをホストするプロセスよりも、容易にシャットダウンされます。したがって、プロセスを終了するかどうかの判断は、そのプロセスで実行されているコンポーネントの状態によって異なります。
プロセスのライフサイクルの詳細と、アプリの状態との関係については、プロセスとアプリのライフサイクルで説明しています。
スレッド
アプリが起動されると、システムはアプリの実行スレッド(メインスレッド)を作成します。このスレッドは、描画イベントなど、適切なユーザー インターフェース ウィジェットへのイベントのディスパッチを担当するため、非常に重要です。また、通常は、アプリケーションが Android UI ツールキットの android.widget パッケージと android.view パッケージのコンポーネントとやり取りするスレッドでもあります。そのため、メインスレッドは UI スレッドと呼ばれることもあります。ただし、特別な状況では、アプリのメインスレッドが UI スレッドではない場合があります。詳細については、スレッドのアノテーションをご覧ください。
システムは、コンポーネントのインスタンスごとに個別のスレッドを作成しません。同じプロセスで実行されるすべてのコンポーネントは UI スレッドでインスタンス化され、各コンポーネントへのシステムコールはそのスレッドからディスパッチされます。そのため、システム コールバックに応答するメソッド(ユーザー アクションをレポートする onKeyDown() やライフサイクル コールバック メソッドなど)は、常にプロセスの UI スレッドで実行されます。
たとえば、ユーザーが画面上のボタンに触れると、アプリの UI スレッドがタッチイベントをウィジェットにディスパッチします。ウィジェットは、押下状態を設定し、イベントキューに無効化リクエストを投稿します。UI スレッドがリクエストをデキューし、ウィジェットに再描画を通知します。
アプリを適切に実装しないと、このシングル スレッド モデルでは、ユーザー操作に応じてアプリが負荷の高い処理を実行するときにパフォーマンスが低下する可能性があります。ネットワーク アクセスやデータベース クエリなどの長時間処理を UI スレッドで行うと、UI 全体がブロックされます。スレッドがブロックされると、描画イベントを含め、イベントをディスパッチできなくなります。
ユーザーの視点から見ると、アプリケーションが応答を停止したように見えます。さらに、UI スレッドが数秒以上ブロックされると、ユーザーに「アプリケーション応答なし」(ANR)ダイアログが表示されます。ユーザーは、アプリを終了したり、アンインストールしたりする可能性があります。
Android UI ツールキットはスレッドセーフではありません。そのため、ワーカー スレッドから UI を操作しないでください。ユーザー インターフェースに対する操作はすべて UI スレッドから行います。Android のシングル スレッド モデルには次の 2 つのルールがあります。
- UI スレッドをブロックしないでください。
- UI スレッドの外部から Android UI ツールキットにアクセスしないでください。
ワーカー スレッド
このシングル スレッド モデルのため、アプリの UI の応答性を確保するには、UI スレッドをブロックしないことが重要です。瞬時に完了しないオペレーションがある場合は、別のバックグラウンド スレッドまたはワーカー スレッドで実行するようにしてください。UI スレッド(メインスレッド)以外のスレッドから UI を更新することはできません。
これらのルールを遵守するため、Android では他のスレッドから UI スレッドにアクセスする方法がいくつか用意されています。以下に、役立つ可能性のある方法をいくつかご紹介します。
次の例は、タスクをバックグラウンド スレッドにオフロードし、タスクが完了したら UI スレッドを更新する方法を示しています。
Kotlin
// Kotlin coroutines implementation. fun onClick(v: View) { // Launch a coroutine in the lifecycle scope (e.g., in an Activity or Fragment). lifecycleScope.launch { // Run the blocking task on the IO dispatcher. val bitmap = withContext(Dispatchers.IO) { BitmapFactory.decodeFile("image.png") } // Back on the main thread, update the UI. imageView.setImageBitmap(bitmap) } }
Java
// Java Executor implementation. // (executorService is assumed to be defined elsewhere). public void onClick(View v) { executorService.execute(() -> { // Run the heavy task on a background thread. Bitmap bitmap = BitmapFactory.decodeFile("image.png"); // Update the View on the UI thread. imageView.post(() -> imageView.setImageBitmap(bitmap)); }); }
この実装はスレッドセーフです。これは、ImageView が常に UI スレッドから操作される一方で、バックグラウンド オペレーションが別のスレッドから実行されるためです。
ただし、オペレーションの複雑さが増すにつれて、このようなコードは複雑になり、保守が難しくなる可能性があります。ワーカー スレッドとのより複雑なやり取りを処理するには、ワーカー スレッドで Handler を使用して UI スレッドから配信されたメッセージを処理することを検討してください。バックグラウンド スレッドで処理をスケジュール設定し、UI スレッドに通信する方法について詳しくは、バックグラウンド処理の概要をご覧ください。
スレッドセーフなメソッド
実装するメソッドが複数のスレッドから呼び出される場合があるため、スレッドセーフになるように記述する必要があります。
これは主に、バインドされたサービスのメソッドなど、リモートで呼び出すことができるメソッドに当てはまります。IBinder で実装されたメソッドの呼び出しが、IBinder が実行されているのと同じプロセスで発生した場合、メソッドは呼び出し元のスレッドで実行されます。ただし、呼び出しが別のプロセスで発生した場合、メソッドは IBinder と同じプロセスでシステムが維持するスレッドのプールから選択されたスレッドで実行されます。プロセスの UI スレッドで実行されません。
たとえば、サービスの onBind() メソッドはサービス プロセスの UI スレッドから呼び出されますが、リモート プロシージャ コール(RPC)メソッドを実装するサブクラスなど、onBind() が返すオブジェクトで実装されたメソッドは、プール内のスレッドから呼び出されます。サービスには複数のクライアントが存在する可能性があるため、複数のプールスレッドが同時に同じ IBinder メソッドを使用する可能性があります。そのため、IBinder メソッドはスレッドセーフになるように実装する必要があります。
同様に、コンテンツ プロバイダは他のプロセスから発信されたデータ リクエストを受信できます。ContentResolver クラスと ContentProvider クラスは、プロセス間通信(IPC)の管理方法の詳細を隠蔽しますが、これらのリクエストに応答する ContentProvider メソッド(query()、insert()、delete()、update()、getType())は、プロセスの UI スレッドではなく、コンテンツ プロバイダのプロセスのスレッド プールから呼び出されます。これらのメソッドは同時に複数のスレッドから呼び出される可能性があるため、スレッドセーフになるように実装する必要があります。
プロセス間通信(IPC)
Android は、RPC を使用した IPC のメカニズムを提供しています。このメカニズムでは、アクティビティや他のアプリ コンポーネントによってメソッドが呼び出されますが、別のプロセスでリモート実行され、結果は呼び出し元に返されます。これには、メソッド呼び出しとそのデータをオペレーティング システムが理解できるレベルまで分解し、ローカル プロセスとアドレス空間からリモート プロセスとアドレス空間に送信し、そこで呼び出しを再構成して再実行することが含まれます。
戻り値は反対方向に送信されます。Android はこれらの IPC トランザクションを実行するすべてのコードを提供するため、デベロッパーは RPC プログラミング インターフェースの定義と実装に集中できます。
IPC を実行するには、アプリケーションが bindService() を使用してサービスにバインドする必要があります。詳細については、サービスの概要をご覧ください。