Android Dev Summit, October 23-24: two days of technical content, directly from the Android team. Sign-up for livestream updates.

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

アプリ コンポーネントが開始され、アプリに他に実行中のコンポーネントがない場合、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 スレッドが数秒以上(現時点では約 5 秒)ブロックされると、ユーザーに「アプリが応答していません」(ANR)というダイアログが表示されます。ユーザーはアプリを停止するか、気に入らない場合はアンインストールしてしまう可能性があります。

また、Android UI ツールキットはスレッドセーフではありません。したがって、ワーカー スレッドから UI を操作するべきではありません。ユーザー インターフェースに対するすべての操作は、UI スレッドから行う必要があります。そのため、Android のシングル スレッド モデルには 2 つの明快なルールがあります。

  1. UI スレッドをブロックしない
  2. UI スレッド以外から Android UI ツールキットにアクセスしない

ワーカー スレッド

上記で説明したシングル スレッド モデルにより、アプリの UI の応答性のためにも UI スレッドをブロックしないことが不可欠です。即座に実行する必要のない操作の場合は、別のスレッド(「バックグラウンド」スレッドや「ワーカー」スレッド)で実行するようにする必要があります。

ただし、UI スレッドまたは「メイン」スレッド以外のスレッドから UI を更新することはできません。

この問題を修正するために、Android には、他のスレッドから UI スレッドにアクセスする方法がいくつか用意されています。使用できるメソッドは次のとおりです。

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 スレッドから配信されたメッセージを処理できます。最善なのは AsyncTask クラスを拡張することであり、これにより UI を操作する必要のあるワーカー スレッドのタスクの実行を簡素化できます。

AsyncTask を使用する

AsyncTask では、ユーザー インターフェースに非同期の処理を実行できます。これは、ワーカー スレッドでブロック操作を実行し、結果を UI スレッドに発行します。スレッドやハンドラを自身で処理する必要はありません。

これを使用するには、AsyncTask をサブクラス化し、バックグラウンド スレッドのプール内で実行される doInBackground() コールバック メソッドを実装する必要があります。UI を更新するには、onPostExecute() を実装します。これは doInBackground() からの結果を配信し、UI スレッド内で実行されるため、UI を安全に更新できます。その後、UI スレッドから execute() を呼び出してタスクを実行できます。

このクラスの使用方法の詳細については、AsyncTask のリファレンスをご覧ください。

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

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

主に、バインドされたサービスのメソッドなど、リモートで呼び出されるメソッドなどがこれに該当します。IBinder に実装されたメソッドに対する呼び出しが、IBinder が実行されているプロセスと同じプロセスで発生した場合、メソッドは呼び出し側のスレッドで実行されます。ただし、呼び出しが別のプロセスで発生した場合は、システムが IBinder と同じプロセスに保持するスレッドのプールから選ばれたスレッドでメソッドが実行されます(プロセスの UI スレッドでは実行されません)。たとえば、サービスの onBind() メソッドがサービスのプロセスの UI スレッドから呼び出されるのに対して、onBind() が返すオブジェクト(リモート プロシージャ コール メソッドを実装するサブクラスなど)に実装されたメソッドは、プール内のスレッドから呼び出されます。サービスは複数のクライアントを持つことができるため、複数のプールスレッドが同じ IBinder メソッドを同時に動かすことができます。このため、IBinder メソッドはスレッドセーフになるように実装する必要があります。

同様に、コンテンツ プロバイダは、他のプロセスで発生したデータ リクエストを受け取ることができます。ContentResolver クラスと ContentProvider クラスによってプロセス間通信がどのように管理されているかが見えなくなりますが、それらのリクエストに応答する ContentProvider メソッド(query()insert()delete()update()getType())は、プロセスの UI スレッドではなく、コンテンツ プロバイダのプロセスにあるスレッドのプールから呼び出されます。これらのメソッドは同時に複数のスレッドから呼び出される可能性があるため、先ほどと同様にスレッドセーフになるように実装する必要があります。

プロセス間通信(IPC)

Android は、リモート プロシージャ コール(RPC)を使ったプロセス間通信(IPC)のメカニズムを備えており、メソッドはアクティビティや他のアプリ コンポーネントから呼び出された後に、リモート(別のプロセス)で実行され、結果を呼び出し側に返します。これにより、メソッドの呼び出しとそのデータをオペレーティング システムが理解できるレベルまで分解し、ローカル プロセスとアドレス空間からリモート プロセスとアドレス空間にそれを送信して、そこで呼び出しを再度組み立てて、再現します。その後、戻り値が逆方向に伝達されます。Android ではこれらの IPC トランザクションを実行するためのすべてのコードが用意されているため、開発者はリモート プロシージャ コールのプログラミング インターフェースの定義と実装に集中できます。

IPC を実行するには、アプリが bindService() を使ってサービスにバインドされている必要があります。詳細については、デベロッパー ガイドのサービスをご覧ください。