プロセスとスレッドの概要

アプリケーション コンポーネントが起動され、アプリケーションに他のコンポーネントが実行されていない場合、Android システムは単一の実行スレッドでアプリケーション用の新しい Linux プロセスを開始します。デフォルトでは、同じアプリのすべてのコンポーネントは、同じプロセスとスレッド(メインスレッド)内で実行されます。

アプリ コンポーネントが起動し、そのアプリのプロセスがすでに存在する場合(アプリの別のコンポーネントがすでに開始されている場合)、コンポーネントはそのプロセス内で起動し、同じ実行スレッドを使用します。ただし、アプリ内のさまざまなコンポーネントを別のプロセスで実行するように調整したり、任意のプロセス用に追加のスレッドを作成したりできます。

このドキュメントでは、Android アプリでのプロセスとスレッドの仕組みについて説明します。

プロセス

デフォルトでは、アプリのすべてのコンポーネントが同じプロセス内で実行されます。ほとんどのアプリはこの値を変更しません。ただし、特定のコンポーネントが属するプロセスを制御する必要がある場合は、マニフェスト ファイルで制御できます。

コンポーネント要素の各タイプ(<activity><service><receiver><provider>)のマニフェスト エントリは、コンポーネントの実行プロセスを指定できる android:process 属性をサポートしています。この属性を設定して、各コンポーネントが独自のプロセスで実行されるようにしたり、一部のコンポーネントがプロセスを共有し、他のコンポーネントが共有しないように設定できます。

また、異なるアプリのコンポーネントが同じプロセスで実行されるように android:process を設定することもできます。ただし、それらのアプリが同じ Linux ユーザー ID を共有し、同じ証明書で署名されている必要があります。

<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 つのルールがあります。

  1. UI スレッドをブロックしない。
  2. UI スレッドの外部から Android UI ツールキットにアクセスしないでください。

ワーカー スレッド

このシングル スレッド モデルのため、アプリの UI の応答性には、UI スレッドをブロックしないことが不可欠です。即座に実行されるオペレーションがある場合は、別々のバックグラウンド スレッドまたはワーカー スレッドで行ってください。UI(メインスレッド)以外のスレッドから UI を更新することはできません。

こうしたルールに沿えるように、Android には他のスレッドから UI スレッドにアクセスする方法がいくつか用意されています。便利な方法を以下にご紹介します。

次の例では、View.post(Runnable) を使用しています。

Kotlin

fun onClick(v: View) {
    Thread(Runnable {
        // A potentially time consuming task.
        val bitmap = processBitMap("image.png")
        imageView.post {
            imageView.setImageBitmap(bitmap)
        }
    }).start()
}

Java

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            // A potentially time consuming task.
            final Bitmap bitmap =
                    processBitMap("image.png");
            imageView.post(new Runnable() {
                public void run() {
                    imageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

ImageView は常に UI スレッドから操作されるのに対し、バックグラウンド処理は別のスレッドから行われるため、この実装はスレッドセーフです。

ただし、オペレーションが複雑化すると、この種のコードは複雑になり、保守が困難になる可能性があります。ワーカー スレッドとのより複雑なインタラクションを処理するには、ワーカー スレッドで Handler を使用して、UI スレッドから配信されたメッセージを処理することを検討してください。バックグラウンド スレッドでの処理をスケジュールし、UI スレッドに返す方法については、バックグラウンド処理の概要をご覧ください。

スレッドセーフなメソッド

状況によっては、実装するメソッドが複数のスレッドから呼び出されることがあります。そのため、スレッドセーフになるように記述する必要があります。

これは、主にバインドされたサービスのメソッドなど、リモートで呼び出せるメソッドに当てはまります。IBinder に実装されたメソッドの呼び出しが、IBinder が実行されているプロセスと同じプロセスで開始される場合、メソッドは呼び出し元のスレッドで実行されます。ただし、呼び出しが別のプロセスから開始されると、システムは IBinder と同じプロセスで維持しているスレッドのプールから選択されたスレッドで、メソッドが実行されます。プロセスの UI スレッドでは実行されません。

たとえば、サービスの onBind() メソッドはサービスのプロセスの UI スレッドから呼び出されますが、onBind() が返すオブジェクトに実装されているメソッド(リモート プロシージャ コール(RPC)のメソッドを実装するサブクラスなど)は、プール内のスレッドから呼び出されます。サービスには複数のクライアントが含まれる場合があるため、複数のプールスレッドが同じ IBinder メソッドを同時に使用する可能性があるため、IBinder メソッドはスレッドセーフになるように実装する必要があります。

同様に、コンテンツ プロバイダは他のプロセスからのデータ リクエストを受信できます。ContentResolver クラスと ContentProvider クラスはプロセス間通信(IPC)の管理方法の詳細を隠しますが、こうしたリクエストに応答する ContentProvider メソッド(メソッド query()insert()delete()update()getType())は、プロセスの UI スレッドではなく、コンテンツ プロバイダのプロセス内のスレッドプールから呼び出されます。これらのメソッドは任意の数のスレッドから同時に呼び出される可能性があるため、スレッドセーフになるように実装する必要があります。

プロセス間通信(IPC)

Android には、RPC を使用する IPC のメカニズムが用意されています。このメカニズムでは、メソッドはアクティビティや他のアプリ コンポーネントによって呼び出されますが、別のプロセスでリモートで実行され、すべての結果が呼び出し元に返されます。これには、メソッド呼び出しとそのデータをオペレーティング システムが理解できるレベルに分解し、ローカル プロセスとアドレス空間からリモート プロセスとアドレス空間に送信し、そこで呼び出しを再アセンブルして再作成します。

戻り値は逆方向に送信されます。Android には、これらの IPC トランザクションを実行するためのコードがすべて用意されているため、ユーザーは RPC プログラミング インターフェースの定義と実装に集中できます。

IPC を実行するには、アプリケーションが bindService() を使用してサービスにバインドする必要があります。詳細については、サービスの概要をご覧ください。