Android とアーキテクチャ
Android オペレーティング システムは、さまざまな端末やフォーム ファクタで動作するアプリを構築するための強力な基盤を提供します。しかし、私たちはデベロッパーのフィードバックに耳を傾けています。堅牢なアプリを作成する際に難題として立ちはだかるのは、複雑なライフサイクルや、推奨されるアプリのアーキテクチャがないといった問題です。
堅牢なアプリの記述をもっと簡単に、もっと楽しめるようにして、デベロッパーにイノベーションを実現できる分野へと目を向けてもらわなくてはなりません。本日は、Android アプリのアーキテクチャ ガイドについてお知らせするとともに、アーキテクチャ コンポーネントの一部を紹介いたします。なお、車輪を再発明するのではなく、有名な Android ライブラリで実現されているものも取り入れています。
指示ではなくあくまでも意見
Android アプリを書く方法は 1 つだけではありません。 ここで紹介するのは、独特な Android の動作と最大限うまく連携する Android アプリを設計するために役立つ、一連のガイドラインです。Android フレームワークには、アクティビティなどの OS との接点を扱うことができる綿密に定義された API があります。しかし、それはアプリのエントリ ポイントであり、アプリのアーキテクチャの構成要素ではありません。フレームワークのコンポーネントは、データモデルと UI コンポーネントとの分割を強制するものではなく、またライフサイクルから独立してデータを永続化する明確な方法を提供するものでもありません。
構成要素
Android のアーキテクチャ コンポーネントは、連携して健全なアプリのアーキテクチャを実現しつつ、デベロッパーの課題にも個別に対処します。最初のコンポーネント集は、次のように皆さんをサポートします。
- アクティビティとフラグメントのライフサイクルを自動的に管理し、メモリやリソースのリークを防ぐ
- Java データ オブジェクトを SQLite データベースに永続化する
新しい Lifecycle-aware コンポーネントは、アプリのコア コンポーネントとライフサイクル イベントを結びつける構造を提供し、明示的な依存性パスを取り除きます。
典型的な Android の監視モデルは、
onStart() で監視を始め、
onStop() で終了します。 これは十分シンプルに思えますが、多くの場合、いくつかの非同期呼び出しが同時に実行され、そのすべてでコンポーネントのライフサイクルが管理されます。 そのため、エッジケースを見逃してしまう可能性が高くなります。 ライフサイクル コンポーネントを使うと、こういった点が便利になります。
これらすべてのコアクラスとなっているのが
Lifecycle です。このクラスは、
現在のライフサイクルの状態と、関連するコンポーネントのライフサイクルの状態をトラッキングするライフサイクル イベントを列挙型で表しています。
ライフサイクルの状態とイベント
LifecycleOwner インターフェースの
getLifecycle() メソッドは、Lifecycle オブジェクトを返します。一方の
LifecycleObserver は、メソッドにアノテーションを追加してコンポーネントのライフサイクル イベントを監視できるクラスです。以上のことをすべて合わせると、監視ライフサイクル イベントと現在のライフサイクルの状態の両方を問い合わせるライフサイクルを意識したコンポーネントを作成できます。
public class MyObserver implements LifecycleObserver {
public MyObserver(Lifecycle lifecycle) {
// Starts lifecycle observation
lifecycle.addObserver(this);
...
}
public void startFragmentTransaction() {
// Queries lifecycle state
if (lifecycle.getState.isAtLeast(STARTED)) {
// perform transaction
}
}
// Annotated methods called when the associated lifecycle goes through these events
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void onResume() {
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
public void onPause() {
}
}
MyObserver observer = new MyObserver(aLifecycleOwner.getLifecycle());
LiveData は、ライフサイクルを意識した監視可能なデータホルダー クラスです。UI のコードで
LifecycleOwner と結びついたベースデータの変更をサブスクライブすると、
LiveData によってオブザーバーの以下の動作が保証されます。
- ライフサイクルがアクティブ状態(STARTED または RESUMED)である間、データのアップデートを取得する
- LifecycleOwner が破棄されると削除される
- LifecycleOwner が設定の変更によって再起動したり、バックスタックから再起動した際に、最新のデータを取得する
これにより、停止したアクティビティへのアップデートが回避されるため、メモリリークに至る多くの経路がふさがれ、クラッシュが減少します。
LiveData は、フラグメントやアクティビティなどのライフサイクルの所有権と結びついた複数のリスナーから監視することもできます。
ViewModel は、アクティビティまたはフラグメントの UI データを含むヘルパークラスで、ビューデータの所有権と UI コントローラ ロジックを分離します。ViewModel は、設定の変更によってアクティビティやフラグメントが破棄されて再作成される場合も含め、アクティビティやフラグメントのスコープが有効である限り保持されます。そのため、ViewModel を使うと再作成されたアクティビティやフラグメントのインスタンスが UI データを利用できるようになります。ViewModel 内に格納されている UI データを LiveData でラッピングすると、そのデータにライフサイクルを意識した監視可能なホームを提供できます。LiveData は通知側の処理を行いますが、ViewModel はデータが適切に保持されることを保証します。
Android アーキテクチャ コンポーネントでは、データ永続性も
Room ライブラリによって簡略化されています。Room は、SQLite のすべてのパワーを活用しつつ柔軟にデータベースにアクセスできるオブジェクト マッピング抽象化レイヤーを提供します。コア フレームワークには、ロー SQL コンテンツを操作するためのサポートが組み込まれています。この API は強力ですが、かなり低レベルであるため、使うにはかなりの時間と労力が必要です。
- ロー SQL クエリはコンパイル時に検証されません。
- スキーマが変更されると、影響を受ける SQL クエリを手動でアップデートする必要があります。これは、時間がかかり、エラーも発生しやすいプロセスです。
- SQL クエリと Java データ オブジェクトを変換するために、たくさんのボイラープレート コードを書く必要があります。
Room は SQLite に抽象化レイヤーを提供し、こういった懸念に対処します。
Room には、次の 3 つの主要コンセプトがあります。
- Entity は、データベースの 1 行のデータを示します。アノテーションを付加した Java データ オブジェクトを使って作成します。各 Entity は、それぞれの表に永続化されます。
- DAO(データ アクセス オブジェクト)は、データベースにアクセスするメソッドを定義します。各メソッドに SQL をバインドするためにアノテーションを使います。
- Database は、アノテーションを使ってエンティティのリストとデータベースのバージョンを定義するホルダークラスです。このクラスのコンテンツでは、DAO のリストが定義されています。また、このクラスは基盤となるデータベースに接続するための主なアクセス ポイントでもあります。
Room を使うには、エンティティとして永続化する Java データ オブジェクトにアノテーションを追加し、そのエンティティを含むデータベースを作成し、データベースのアクセスや変更を行う SQL を含む DAO クラスを定義します。
@Entity
public class User {
@PrimaryKey
private int uid;
private String name;
// Getters and Setters - required for Room
public int getUid() { return uid; }
public String getName() { return name; }
public void setUid(int uid) { this.uid = uid; }
public void setName(String name) { this.name = name; }
}
@Dao
public interface UserDao {
@Query("SELECT * FROM user")
List getAll();
@Insert
void insertAll(User... users);
}
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
アーキテクチャ コンポーネントは、スタンドアロンで動作するように設計されていますが、効率的なアプリ アーキテクチャに組み込まれたときに最大の効果を発揮します。本日、
アプリ アーキテクチャ ガイドがリリースされました。このガイドでは、アーキテクチャ コンポーネントを使用して、堅牢でモジュール化やテストが可能なアプリを構築する方法を説明しています。このガイドには、次の 3 つの目的があります。
- Android アプリ開発に適用される原則の定義
- その原則を適用できるアプリ アーキテクチャの説明
- アーキテクチャ コンポーネントを使ったアーキテクチャの実装方法の説明
こういった問題に対処しなければならないすべてのデベロッパーは、このガイドを一読することをおすすめします。既存のアプリ アーキテクチャに満足しているという方にとっても、ガイドから有用な設計の原則やアイディアを得られることでしょう。
まだ始まったばかり
これからも新たなアーキテクチャ コンポーネントの導入を続けて、Android デベロッパーがアプリを設計する際に十分な情報に基づく選択を簡単に行えるようにしたいと考えています。ぜひ私たちが行っていることを確認してフィードバックをお送りください。私たちも皆さんも、堅牢な Android アプリの開発をもっと簡単かつ楽しいものにしたいという立場で一致しています。Android アーキテクチャの詳細については、以下をご覧ください。
Posted by