このガイドでは、品質の高い堅牢なアプリを作成するためのおすすめの方法と推奨アーキテクチャを紹介します。
モバイルアプリのユーザー エクスペリエンス
標準的な Android アプリは、さまざまなアプリ コンポーネント(アクティビティ、フラグメント、サービス、コンテンツ プロバイダ、ブロードキャスト レシーバなど)で構成されています。これらのアプリ コンポーネントのほとんどを、アプリ マニフェストで宣言しておきます。Android OS ではこのアプリ マニフェスト ファイルを基に、デバイスの全体的なユーザー エクスペリエンスにアプリをどのように組み込むかを決定します。一般的な Android アプリには複数のコンポーネントが含まれており、ユーザーが短時間に複数のアプリを頻繁に操作することを考慮すると、アプリはユーザー主導のさまざまなワークフローとタスクに適応する必要があります。
また、モバイル デバイスはリソースに限りがあるため、オペレーティング システムは新たなアプリのリソースを確保するために、アプリプロセスを任意のタイミングで強制終了する場合があります。
このような環境の条件を考慮すると、アプリ コンポーネントは個別に順不同で起動され、オペレーティング システムやユーザーによって随時破棄される可能性があります。デベロッパーはこうしたイベントを制御できないため、アプリのデータや状態をアプリ コンポーネント内に保存したり、メモリ内に保持したりしないでください。また、アプリ コンポーネントは互いに依存してはなりません。
アーキテクチャに関する共通の原則
アプリのデータや状態の保存にアプリ コンポーネントを使用できないとなると、アプリをどのように設計すればよいのでしょうか。
Android アプリのサイズが大きくなるにつれ、アプリを拡張し、堅牢性を高め、アプリをテストしやすくするアーキテクチャを定義することが重要になります。
アプリ アーキテクチャは、アプリの各部分の境界と、各部分が担う役割を定義します。前述のニーズを満たすには、具体的な原則に沿ってアプリ アーキテクチャを設計する必要があります。
関心の分離
最も重要な原則は関心の分離です。すべてのコードを 1 つの Activity
または Fragment
に記述するのはよくある間違いです。これらの UI ベースのクラスには、UI やオペレーティング システムとのやり取りを処理するロジックのみを含めます。これらのクラスをできる限りシンプルに保つことで、コンポーネントのライフサイクルに関連する多くの問題を回避し、クラスのテストのしやすさを向上させることができます。
Activity
と Fragment
の実装はデベロッパーが管理するものではないことにご注意ください。これらのクラスは、Android OS とアプリの間のコントラクトを体現する単なる結合クラスです。Android OS は、ユーザーの操作に基づいて、またはシステムの状態(メモリ不足など)を理由として、いつでもこれらのクラスを破棄できます。十分なユーザー エクスペリエンスを実現し、アプリを管理しやすくするために、クラスへの依存を最小限に抑えることをおすすめします。
UI をデータモデルで操作する
もう 1 つの重要な原則は、UI をデータモデルで操作することです(永続モデルをおすすめします)。データモデルはアプリのデータを表し、アプリの UI 要素やその他のコンポーネントから独立しています。つまり、UI とアプリ コンポーネントのライフサイクルには関連付けられませんが、OS がアプリのプロセスをメモリから削除することを決定したときは、破棄されます。
永続モデルが望ましい理由として、次の点が挙げられます。
Android OS がアプリを破棄してリソースを解放してもデータが失われない。
ネットワーク接続が不安定または利用不可の場合でもアプリが動作し続ける。
アプリ アーキテクチャをデータモデル クラスに基づいて構築すると、アプリのテストのしやすさと堅牢性を高めることができます。
アプリの推奨アーキテクチャ
このセクションでは、推奨されるベスト プラクティスに沿ってアプリを構築する方法について説明します。
前のセクションで説明したアーキテクチャに関する一般的な原則を考慮すると、各アプリに少なくとも 2 つのレイヤが必要です。
- 画面にアプリデータを表示する UI レイヤ。
- アプリのビジネス ロジックを含み、アプリデータを公開するデータレイヤ。
ドメインレイヤというレイヤを追加することで、UI レイヤとデータレイヤの間のやり取りを簡素化でき、再利用できます。

UI レイヤ
UI レイヤ(またはプレゼンテーション レイヤ)の役割は、アプリデータを画面に表示することです。ユーザー操作(ボタンの押下など)または外部入力(ネットワーク レスポンスなど)によってデータが変更されるたびに、変更を反映するように UI を更新する必要があります。
UI レイヤは次の 2 つのもので構成されています。
- データを画面にレンダリングする UI 要素。これらの要素は、View または Jetpack Compose 関数を使用して作成します。
- データを保持して UI に公開し、ロジックを処理する状態ホルダー(ViewModel クラスなど)。

このレイヤについて詳しくは、UI レイヤのページをご覧ください。
データレイヤ
アプリのデータレイヤには、ビジネス ロジックが含まれています。ビジネス ロジックはアプリに価値をもたらすものであり、アプリがデータを作成、保存、変更する方法を決定するルールで構成されています。
データレイヤは、それぞれが 0 から多数のデータソースを含むことができるリポジトリで構成されています。アプリで処理するデータの種類ごとにリポジトリ クラスを作成する必要があります。たとえば、映画に関するデータであれば MoviesRepository
クラス、支払いに関するデータであれば PaymentsRepository
クラスを作成します。

リポジトリ クラスは、次のタスクを行います。
- アプリの他の部分にデータを公開する。
- データの変更を一元管理する。
- 複数のデータソース間の競合を解決する。
- アプリの他の部分からデータソースを抽象化する。
- ビジネス ロジックを含む。
各データソース クラスは、ファイル、ネットワーク ソース、ローカル データベースなど、1 つのデータソースのみを処理する役割を担う必要があります。データソース クラスは、データ オペレーションのためにアプリとシステムの橋渡しをします。
このレイヤについて詳しくは、データレイヤのページをご覧ください。
ドメインレイヤ
ドメインレイヤは、UI レイヤとデータレイヤの間に位置するオプションのレイヤです。
ドメインレイヤは、複雑なビジネス ロジック、または複数の ViewModel で再利用される単純なビジネス ロジックをカプセル化します。すべてのアプリにこのような要件があるわけではないため、このレイヤはオプションです。複雑さに対処する場合や再利用性を優先する場合など、必要な場合にのみ使用してください。

通常、このレイヤのクラスを「ユースケース」または「インタラクタ」と呼びます。各ユースケースは 1 つの機能を担うべきです。たとえば、複数の ViewModel がタイムゾーンに基づいて適切なメッセージを画面に表示する場合、アプリに GetTimeZoneUseCase
クラスを持たせることができます。
このレイヤについて詳しくは、ドメインレイヤのページをご覧ください。
コンポーネント間の依存関係を管理する
アプリのクラスは、適切に機能するために他のクラスに依存しています。次のいずれかのデザイン パターンを使用して、特定のクラスの依存関係を収集できます。
- 依存関係の注入(DI): 依存関係の注入を利用すると、クラスの依存関係を構築することなく定義できます。ランタイムには、別のクラスがこの依存関係を提供します。
- サービス ロケータ: サービス ロケータ パターンでは、クラスが依存関係を作成せずに取得できるレジストリが提供されます。
こうしたパターンでは、コードが重複して煩雑になることなく依存関係を明確に管理できるため、コードの拡張が可能になります。さらに、テスト版と製品版の実装を簡単に切り替えることができます。
依存関係の注入のパターンに沿って、Android アプリで Hilt ライブラリを使用することをおすすめします。Hilt では、依存関係ツリーをたどって自動的にオブジェクトが構築され、コンパイル時の依存関係が保証され、Android フレームワーク クラスの依存関係コンテナが作成されます。
一般的なベスト プラクティス
プログラミングは創造的な活動であり、Android アプリの作成も例外ではありません。問題の解決方法は数多くあります。複数のアクティビティやフラグメント間でデータをやり取りする、リモートデータを取得してオフライン モード用にローカルで永続化するなど、重要なアプリで対処する一般的なシナリオにはさまざまなものがあります。
以下の推奨事項は必須ではありませんが、ほとんどの場合これに沿うことで、コードベースの堅牢性を高め、テストとメンテナンスを長期にわたって容易に実施できるようになります。
アプリのコンポーネントにデータを格納しないでください。
アプリのエントリ ポイント(アクティビティ、サービス、ブロードキャスト レシーバなど)をデータソースとして指定しないでください。その代わり、そのエントリ ポイントに関連するデータのサブセットを取得する他のコンポーネントとの調整のみを行う必要があります。ユーザーによるデバイスの操作や、システムの現在の全体的な稼働状態によっては、各アプリ コンポーネントの生存期間がかなり短くなります。
Android クラスへの依存を減らします。
アプリ コンポーネントは、Context
や Toast
など、Android フレームワーク SDK API に依存する唯一のクラスにする必要があります。そこからアプリの他のクラスを抽象化すると、テストがしやすくなり、アプリ内の結合を軽減できます。
アプリの各種モジュール間の役割の境界を明確に定義します。
たとえば、ネットワークからデータを読み込むコードを、コードベース内の複数のクラスやパッケージに散在させないでください。同様に、関連のない複数の処理(データ キャッシングとデータ バインディングなど)を同じクラスで定義しないでください。アプリの推奨アーキテクチャに沿うと便利です。
各モジュールからの公開はできるだけ行わないでください。
たとえば、モジュールの内部実装の詳細を公開するショートカットを作成しようとしないでください。短期的には時間を少し節約できるかもしれませんが、コードベースが発展するにつれて何倍もの技術的負債を負うことになる可能性があります。
アプリの特別な部分に焦点を当てて、他のアプリとの差別化を図ります。
同じボイラープレート コードを何度も書いてすでにあるものを作り直すのではなく、アプリを特別なものにすることに時間とエネルギーを集中させましょう。繰り返しのボイラープレート コードの記述には Jetpack ライブラリやその他の推奨ライブラリを利用してください。
アプリの各部分を個別にテストできるようにする方法を検討します。
たとえば、ネットワークからデータを取得するための明確に定義された API を用意することで、そのデータをローカル データベースに永続化するモジュールを簡単にテストできるようになります。そうせずに、2 つのモジュールのロジックを 1 か所に混在させたり、ネットワーク用のコードをコードベース全体に分散させたりすると、不可能ではないにしても、テストが極めて困難になります。
データの関連性と新鮮さをできる限り維持します。
こうすることで、デバイスがオフライン モードのときでも、ユーザーがアプリの機能を利用できるようになります。すべてのユーザーが常時高速接続を利用できるわけではなく、たとえ利用できるとしても、混雑した場所では受信不良が起きる可能性があることに留意してください。
サンプル
以下の Google サンプルは、優れたアプリ アーキテクチャを実証するものです。このガイダンスを実践するためにご利用ください。
- iosched(Google I/O アプリ)
- Sunflower
- Trackr
- Jetnews Compose(Jetpack Compose で実装)
- アーキテクチャのサンプル