Android サービスの作成

このガイドでは、アクティブなユーザー インターフェイスなしで作業を実行できる Android コンポーネントである Xamarin.Android サービスについて説明します。 サービスは、時間のかかる計算、ファイルのダウンロード、音楽の再生など、バックグラウンドで実行されるタスクに非常に一般的に使用されます。 サービスが適しているさまざまなシナリオについて説明し、実行時間の長いバックグラウンド タスクの実行と、リモート プロシージャ コール用のインターフェイスの提供の両方について、それらを実装する方法を示します。

Android サービスの概要

モバイル アプリはデスクトップ アプリとは似ていません。 デスクトップには、画面の領域、メモリ、記憶域、接続された電源など、大量のリソースがありますが、モバイル デバイスにはありません。 これらの制約により、モバイル アプリでは異なる動作が強いられます。 たとえば、モバイル デバイスの小さな画面は、通常、一度に 1 つのアプリ (アクティビティ) しか表示できないことを意味します。 他のアクティビティはバックグラウンドに移動されて、どのような作業も実行できない一時停止状態にプッシュされます。 ただし、Android アプリケーションがバックグラウンドになっているからといって、アプリが動作し続けることが不可能なわけではありません。

Android アプリケーションは、"アクティビティ"、"ブロードキャスト レシーバー"、"コンテンツ プロバイダー"、"サービス" の 4 つの主要コンポーネントのうち少なくとも 1 つで構成されています。 アクティビティは、ユーザーがアプリケーションと対話できる UI を提供するため、多くの優れた Android アプリケーションの基礎になります。 ただし、同時実行またはバックグラウンドの作業の実行に関しては、アクティビティが常に最善の選択であるとは限りません。

Android のバックグラウンド作業での主要なメカニズムは、"サービス" です。 Android のサービスは、ユーザー インターフェイスなしで何らかの作業を行うように設計されたコンポーネントです。 サービスは、ファイルのダウンロード、音楽の再生、画像へのフィルターの適用などを行う場合があります。 サービスは、Android アプリケーション間のプロセス間通信 (IPC) にも使用できます。 たとえば、ある Android アプリが、別のアプリのものである音楽プレーヤー サービスを使ったり、あるアプリがサービスを介して他のアプリにデータ (個人の連絡先情報など) を公開したりする場合があります。

サービスと、バックグラウンド作業を実行するその機能は、スムーズで柔軟なユーザー インターフェイスを提供するために不可欠です。 すべての Android アプリケーションには、アクティビティが実行される "メイン スレッド" ("UI スレッド" とも呼ばれます) があります。 デバイスの応答性を維持するため、Android は 1 秒間に 60 フレームの速さでユーザー インターフェイスを更新できる必要があります。 Android アプリがメイン スレッドで実行する作業が多すぎる場合、Android でフレームがドロップし、それによって UI の表示がぎくしゃくします ("ジャンキー" とも呼ばれることもあります)。 これは、UI スレッドで実行されるすべての作業は、2 つのフレーム間の時間間隔、つまり約 16 ミリ秒 (60 フレームごとに 1 秒) で完了する必要があることを意味します。

この問題に対処するため、開発者はアクティビティでスレッドを使って、UI をブロックする作業を実行できます。 ただし、これにより問題が発生する可能性があります。 Android によってアクティビティの複数のインスタンスが破棄されて再作成される可能性がかなりあります。 ただし、Android ではスレッドが自動的に破棄されないため、メモリ リークが発生する可能性があります。 この主な例は、デバイスが回転されるときです。Android は、アクティビティのインスタンスを破棄してから、新しいものの再作成を試みます。

When device rotates, instance 1 is destroyed and instance 2 is created

これによりメモリ リークの可能性があります。アクティビティの最初のインスタンスによって作成されたスレッドが、まだ実行されています。 スレッドにアクティビティの最初のインスタンスへの参照がある場合、これにより Android はオブジェクトをガベージ コレクトできなくなります。 一方で、アクティビティの 2 番目のインスタンスは引き続き作成されます (これにより、新しいスレッドが作成される可能性があります)。 デバイスを立て続けに数回回転させると、すべての RAM が使い果たされ、Android はアプリケーション全体を終了してメモリを回収することを強いられる可能性があります。

経験則として、アクティビティより長く作業を実行する必要がある場合は、その作業を実行するためにサービスを作成する必要があります。 一方、作業がアクティビティのコンテキストにのみ適用される場合は、スレッドを作成して作業を実行する方が適切な場合があります。 たとえば、フォト ギャラリー アプリに追加されたばかりの写真のサムネイルの作成は、おそらくサービスで行う必要があります。 一方、アクティビティがフォアグラウンドになっている間だけ聞こえる必要がある音楽を再生するには、スレッドの方が適している可能性があります。

バックグラウンドの作業は、大きく 2 つに分類できます。

  1. 長期タスク – これは、明示的に停止されるまで実行し続ける作業です。 "長期タスク" の例は、音楽をストリーミングするアプリや、センサーから収集されたデータを監視する必要があるアプリなどです。 これらのタスクは、アプリケーションに表示されるユーザー インターフェイスがない場合でも実行する必要があります。
  2. 定期タスク – ("ジョブ" とも呼ばれます) 定期タスクとは、継続時間が比較的短く (数秒)、スケジュールに従って実行される (たとえば、1 週間にわたって 1 日に 1 回、または次の 60 秒間に 1 回のみ) ものです。 その例は、インターネットからのファイルのダウンロードや、画像のサムネイルの生成などです。

Android のサービスには、4 つの種類があります。

  • バインドされたサービス: "バインドされたサービス" は、他のコンポーネント (通常はアクティビティ) がそれにバインドされているサービスです。 バインドされたサービスでは、バインドされたコンポーネントとサービスが相互に対話できるインターフェイスが提供されます。 サービスにバインドされたクライアントがなくなると、Android はサービスをシャットダウンします。

  • IntentService: IntentServiceService クラスの特殊なサブクラスであり、サービスの作成と使用を簡単にします。 IntentService は、個々の自律的な呼び出しを処理するためのものです。 複数の呼び出しを同時に処理できるサービスとは異なり、IntentService は "作業キュー プロセッサ" にいっそう似ています。作業はキューに入れられ、IntentService は 1 つのワーカー スレッドで各ジョブを一度に 1 つずつ処理します。 通常、IntentService はアクティビティやフラグメントにバインドされません。

  • 開始済みサービス: "開始済みサービス" とは、他の Android コンポーネント (アクティビティなど) によって開始されていて、何かが明示的にサービスに停止を指示するまでバックグラウンドで実行し続けるサービスです。 バインドされたサービスとは異なり、開始済みサービスにはそれに直接バインドされているクライアントはありません。 このため、開始済みサービスは、必要に応じて適切に再起動できるように設計することが重要です。

  • ハイブリッド サービス: "ハイブリッド サービス" とは、"開始済みサービス" と "バインドされたサービス" の特性を併せ持つサービスです。 ハイブリッド サービスは、コンポーネントがそれにバインドしたとき、または何らかのイベントによって、開始できます。 ハイブリッド サービスには、クライアント コンポーネントがバインドされている場合とバインドされていない場合があります。 ハイブリッド サービスは、停止を明示的に指示されるまで、またはバインドされたクライアントがなくなるまで、実行し続けます。

どの種類のサービスを使うかは、アプリケーションの要件に大きく依存します。 経験則として、Android アプリケーションで実行する必要があるほとんどのタスクには、IntentService またはバインドされたサービスで十分であるため、これら 2 種類のサービスのいずれかを優先する必要があります。 IntentService は、ファイルのダウンロードなどの "1 回限りの" タスクに適している一方、アクティビティやフラグメントとの頻繁な対話が必要なときは、バインドされたサービスが適しています。

ほとんどのサービスはバックグラウンドで実行しますが、"フォアグラウンド サービス" と呼ばれる特別なサブカテゴリがあります。 これは、ユーザー向けの何らかの作業 (音楽の再生など) を実行するために、(通常のサービスと比較して) 高い優先順位が与えられるサービスです。

また、同じデバイス上の独自のプロセスでサービスを実行することもできます。これは、"リモート サービス" または "アウトプロセス サービス" と呼ばれることがあります。 これを作成するにはより多くの処理が必要ですが、アプリケーションが他のアプリケーションと機能を共有する必要がある場合に役立ち、場合によっては、アプリケーションのユーザー エクスペリエンスを向上させることができます。

Android 8.0 でのバックグラウンド実行の制限

Android 8.0 (API レベル 26) 以降では、Android アプリケーションはバックグラウンドで自由に実行できなくなりました。 フォアグラウンド時のアプリは、制限なしにサービスを開始して実行できます。 アプリケーションがバックグラウンドに移行すると、Android は一定の時間だけアプリにサービスの開始と使用を許可します。 その時間が経過すると、アプリはサービスを開始できなくなり、開始されたサービスはすべて終了されます。 この時点で、アプリはどのような作業も実行できなくなります。 次のいずれかの条件が満たされている場合、Android はアプリケーションがフォアグラウンド状態であると見なします。

  • 目に見えるアクティビティがあります (開始済みまたは一時停止中)。
  • アプリがフォアグラウンド サービスを既に開始しています。
  • 他の状況であればバックグラウンドになっているアプリのコンポーネントを、フォアグラウンド状態の別のアプリが使っています。 たとえば、フォアグラウンド状態のアプリケーション A が、アプリケーション B によって提供されるサービスにバインドされている場合です。その場合、アプリケーション B もフォアグラウンド状態であると見なされ、バックグラウンドであるとして Android により終了されることはありません。

アプリがバックグラウンド状態であっても、Android がアプリをウェイクアップし、これらの制限を数分間緩和して、アプリによる処理の実行を許可する状況がいくつかあります。

  • 優先度の高い Firebase Cloud Message をアプリが受信する。
  • アプリがブロードキャストを受信する。
  • アプリケーションが、通知を受信して応答で PendingIntent を実行する。

既存の Xamarin.Android アプリケーションでは、Android 8.0 で発生する可能性がある問題を回避するため、バックグラウンド処理の実行方法を変更することが必要になる場合があります。 Android サービスの実用的な代替手段を次に示します。

  • Android ジョブ スケジューラまたは Firebase ジョブ ディスパッチャーを使って、バックグラウンドで実行する作業をスケジュールする: これら 2 つのライブラリでは、アプリケーションがバックグラウンド作業を個別の作業単位である "ジョブ" に分離するためのフレームワークが提供されます。 その場合、アプリは、ジョブを実行できるタイミングに関するいくつかの条件を使って、オペレーティング システムでジョブをスケジュールできます。
  • フォアグラウンドでサービスを開始する: アプリはバックグラウンドで何らかのタスクを実行する必要があり、ユーザーはそのタスクと定期的に対話する必要がある場合は、フォアグラウンド サービスが便利です。 フォアグラウンド サービスでは、アプリがバックグラウンド タスクを実行していることがユーザーにわかるように永続的な通知を表示し、タスクを監視または操作する方法も提供します。 ポッドキャストをユーザーに再生している、または後で楽しめるようにポッドキャスト エピソードをダウンロードしているポッドキャスト アプリは、これの例です。
  • 優先度の高い Firebase Cloud Message (FCM) を使う: Android は、アプリに対する優先度の高い FCM を受信すると、短時間だけ、そのアプリがバックグラウンドでサービスを実行することを許可します。 これは、バックグラウンドでアプリをポーリングするバックグラウンド サービスを使うことに対する、よい代替手段です。
  • アプリがフォアグラウンドになるまで作業を延期する: 前のどの解決策も実行できない場合は、作業を一時停止してアプリがフォアグラウンドになったら再開する独自の方法を、アプリで開発する必要があります。