シームレス化のための設計

アプリが高速で応答性に優れていても、他のアプリやダイアログによる予定外の操作、不注意によるデータ消失、意図しないブロックなど、設計時に対策がなされていないと問題が起こることがあります。こうした問題を回避するうえで、アプリが実行されるコンテキストや、アプリに影響を及ぼす可能性があるシステムとのやり取りについて理解しておくと役に立ちます。要するに、システムや他のアプリとシームレスにやり取りするアプリを開発するように努める必要があります。

シームレス化で一般に問題になるのは、アプリのバックグラウンド プロセス(サービスやブロードキャスト レシーバなど)において、なんらかのイベントに対してダイアログがポップアップ表示される場合です。これは、アプリのビルドとテストをエミュレータ上で隔離された状態で行っている場合は特に、無害な動作のように見えるかもしれません。しかし、アプリが実際のデバイスで実行される際には、アプリにユーザー フォーカスがないときにアプリのバックグラウンド プロセスがダイアログを表示することがあります。そのため、アプリのダイアログがアクティブなアプリの背後に隠れたり、あるいは、ユーザーが行っていた操作(電話の発信操作など)に関係なく、現在のアプリからフォーカスを奪ってダイアログを前面に表示したりすることがあります。これは、アプリやユーザーにとって望ましい動作ではありません。

このような問題を回避するために、ユーザーに通知するための適切なシステム機能(Notification クラス)をアプリで使用する必要があります。通知を使用すると、アプリがフォーカスを奪ってユーザーの操作を中断させるのではなく、ステータスバーにアイコンを表示することによってイベントが発生したことをアプリからユーザーに知らせることができます。

シームレス化で問題になるもう 1 つの例は、onPause() などのライフサイクル メソッドが正しく実装されていないことが原因で、アクティビティによって状態データやユーザーデータが誤って失われてしまうケースです。他にも、他のアプリで使用するデータをアプリで公開する場合、たとえば、誰でも読み取り可能な未加工のファイルやデータベースで公開するのではなく、ContentProvider を介して公開する必要があります。

これらの例に共通することは、アプリはシステムや他のアプリと適切に連携する必要があるということです。Android システムは、アプリをブラックボックス コードの塊として扱うのではなく、ゆるやかに結合されたコンポーネントの連合体のように扱うように設計されています。このためデベロッパーは、システム全体をこうしたコンポーネントのさらに大規模な連合体とみなすことができます。このメリットを享受するには、他のアプリとクリーンかつシームレスに統合できるようにします。そのためには、コードをそのように設計する必要があります。

このドキュメントでは、シームレス化に関する一般的な問題とその回避方法について説明します。

データを削除しない

Android がモバイル プラットフォームであることを常に頭に入れておいてください。当たり前のことに思えるかもしれませんが、現在のアクティビティの上に別のアクティビティ(「通話着信」アプリなど)がいつでもポップアップ表示される可能性があることを覚えておくことは重要です。その場合、onSaveInstanceState() メソッドと onPause() メソッドが呼び出され、結果としてアプリが強制終了される可能性があります。

ユーザーがアプリ内でデータを編集していたときに他のアクティビティが表示された場合、アプリが強制終了されると編集中のデータが失われる可能性があります。もちろん、編集中のデータを先に保存した場合は除きます。そしてこれこそが Android 流の対処法になります。つまり、入力の受け付けや編集を行う Android アプリでは、onSaveInstanceState() メソッドをオーバーライドして、なんらかの適切な方法でその状態を保存し、ユーザーがアプリに再度アクセスしたときにデータを取得できる必要があります。

この動作をうまく活用している典型的な例として、メールアプリが挙げられます。ユーザーがメールを作成しているときに別のアクティビティが開始された場合、アプリは作成中のメールを下書きとして保存する必要があります。

未加工のデータを公開しない

あなたが下着姿で街を歩くことがないのなら、データもそうすべきではありません。特定の種類のデータを公開して誰でも読めるようにすることは可能ですが、通常はおすすめできません。未加工のデータを公開する場合、他のアプリがそのデータ形式を理解できる必要があります。データ形式を変更した場合、同じように更新されていない他のアプリは機能しなくなります。

Android 流の対処法は、綿密に練られた、クリーンでメンテナンスが簡単な API を介して他のアプリにデータを公開するための ContentProvider を作成することです。ContentProvider を使用することは、Java 言語インターフェースを挿入して、密接に結合された 2 つのコードを分割し、コンポーネント化することによく似ています。つまり、ContentProvider によって公開されたインターフェースを変更することなく、しかも他のアプリに影響を与えずに、データの内部形式を変更できるようになります。

ユーザーの操作を中断させない

ユーザーがアプリを実行している場合(電話アプリでの通話など)、それはほぼ間違いなく、ユーザーが意図的に行っていることです。現在のアクティビティからユーザー入力に直接応答する場合を除き、アクティビティを生成すべきでないのはこのためです。

つまり、バックグラウンドで実行されているブロードキャスト レシーバやサービスから startActivity() を呼び出さないでください。そのようにすると、現在実行されているアプリがすべて中断されるため、ユーザーをいらだたせることになります。さらに悪いことに、生成されたアクティビティが「キー入力泥棒」になって、ユーザーが前のアクティビティに対して行っていた入力の一部を受け取ってしまうかもしれません。アプリの処理によっては、これが問題を引き起こす可能性もあります。

これを避けるには、バックグラウンドからアクティビティの UI を直接生成する代わりに、NotificationManager を使用して通知を設定する必要があります。これらの通知はステータスバーに表示されます。ユーザーは都合のよいときに通知をクリックして、その内容を確認できます。

(これは、アクティビティがすでにフォアグラウンドで実行されている場合には当てはまりません。その場合ユーザーは、入力に対する次のアクティビティが表示されることを期待します。)

処理が多い場合はスレッドで実行する

アプリで高コストの計算や長時間かかる計算を実行する必要がある場合は、スレッドに移動することをおすすめします。そうすることで、「アプリケーション応答なし」のダイアログがユーザーに表示され、最終的にアプリがクラッシュするのを防ぐことができます。

デフォルトでは、アクティビティとそのすべてのビューのコードがすべて同じスレッドで実行されます。これは、UI イベントを処理するのと同じスレッドです。たとえば、ユーザーがあるキーを押すと、キー押下イベントがアクティビティのメインスレッドのキューに追加されます。イベント ハンドラ システムは、そのイベントをすばやくキューから取り除いて処理する必要があります。そうしないと数秒後には、アプリがハングして、ユーザーにアプリの強制終了を提案することになります。

実行に時間がかかるコードをアクティビティでインライン実行すると、コードがイベント ハンドラ スレッドで実行され、実質的にイベント ハンドラがブロックされます。これにより入力処理が遅延し、結果的に ANR ダイアログが表示されます。これを回避するには、計算をスレッドに移動します。その方法については、応答性向上のための設計のドキュメントをご覧ください。

1 つのアクティビティ画面に負荷をかけすぎない

ほとんどのアプリは複数の画面を持っています。UI の画面を設計する際には、必ずアクティビティ オブジェクトの複数のインスタンスを使用するようにしてください。

デベロッパーの開発経験にもよりますが、アクティビティがアプリのエントリ ポイントになるという点において、アクティビティを Java アプレットのようなものと解釈されることがあります。しかしこれは、あまり正確ではありません。アプレットのサブクラスは Java アプレットの単一のエントリ ポイントですが、アクティビティはアプリへの複数のエントリ ポイントの 1 つとみなす必要があります。「メイン」のアクティビティとそれ以外のアクティビティの唯一の違いは、「メイン」のアクティビティがたまたま、AndroidManifest..xml ファイルの「android.intent.action.MAIN」アクションへの関心を示した唯一のアクティビティであるという点です。

そのため、アプリを設計する際には、アプリをアクティビティ オブジェクトの連合体と考えます。これにより、長期的に見てコードのメンテナンスを大幅に簡素化することができます。また、よい副作用として、Android のアプリ履歴や「バックスタック」モデルを適切に操作できるようになります。

システムのテーマをベースにする

ユーザー インターフェースのデザインに関しては、うまく調和させることが重要です。ユーザーは、アプリのユーザー インターフェースが期待どおりでないと不快に感じます。UI を設計する際には、できる限り自作のものは使用しないようにしてください。代わりにテーマを使用します。テーマは必要に応じて、その一部をオーバーライドまたは拡張することができます。ただし、少なくとも初めは、他のアプリと同じ UI ベースを使用してください。詳しくは、スタイルとテーマをご覧ください。

複数の画面解像度に対応する UI を設計する

Android デバイスは、サポートする画面解像度がそれぞれ異なります。横表示への切り替えなどによって、瞬時に解像度を変更できるデバイスもあります。レイアウトとドローアブルは、さまざまなデバイスの画面で適切に表示できるように柔軟なものにすることが重要です。

幸い、このようにすることは非常に簡単です。つまり、主な解像度用の各種バージョンのアートワーク(使用する場合)を用意し、さまざまなサイズに対応できるようにレイアウトを設計するだけです(たとえば、ハードコードされた位置を使用せず、代わりに相対レイアウトを使用します)。ここまで自分でやっておけば、残りはシステムが対処し、その結果、どのデバイスでもアプリの見た目がよくなります。

ネットワークは低速であることを前提とする

Android デバイスにはさまざまなネットワーク接続オプションが用意されています。どのオプションもなんらかの形でデータアクセスを提供しますが、その速度はさまざまです。最もよく使われているのは GPRS(GSM ネットワーク向けの非 3G データサービス)です。3G 対応のデバイスでも主に非 3G ネットワークを利用しているケースもあり、低速なネットワークは今後も長い間存在し続けるでしょう。

これが、ネットワーク アクセスと帯域幅を常に最小限に抑えるようにアプリをコーディングする必要がある理由です。ネットワークが高速であると考えることはできません。常に、ネットワークは低速であることを前提として計画を立てる必要があります。ユーザーが利用しているネットワークがたまたま高速である場合は、ユーザー エクスペリエンスが向上するだけです。しかし、逆のケースは避けたいところです。ときには役に立つアプリでも、場所によっては常にイライラするほど低速なアプリは人気が出ないでしょう。

ネットワークに関して潜在的な罠が 1 つあります。エミュレータを使用している場合、デスクトップ パソコンのネットワーク接続を使用するため、いとも簡単にこの罠にはまってしまいます。デスクトップ パソコンのネットワーク接続はまず間違いなくモバイル ネットワークよりはるかに高速であるため、エミュレータで低速ネットワークをシミュレートする場合は設定の変更が必要になります。Android Studio では、エミュレータの起動時に AVD Manager またはコマンドライン オプションで設定を変更できます。

タッチスクリーンやキーボードがあることを前提としない

Android はさまざまなデバイス フォーム ファクタをサポートしています。たとえば、Android デバイスには完全な「QWERTY」キーボードを搭載しているものもあれば、40 キー、12 キーなどのキー構成のものもあります。同様に、タッチスクリーンを搭載しているデバイスもあれば、していないものもあります。

アプリを作成する際には、こうした点に注意してください。特定のキーボード レイアウトを想定しないでください。もちろん、特定のキーボード レイアウトのデバイスでのみ使用できるようにアプリを制限することを考えている場合は、その限りではありません。

デバイスの電池を節約する

モバイル デバイスは、常にコンセントに接続されている場合は、モバイルとは言えません。モバイル デバイスは電池駆動であり、電池の持続時間が長いほどユーザーの満足度は高くなります。電池を消耗させる主な要素は、プロセッサと無線通信の 2 つです。そのため、できる限り処理を少なくし、ネットワークの使用頻度をできる限り低くするようにアプリを作成することが重要です。

アプリで使用するプロセッサ時間を最小限に抑えることは、効率的なコードを記述することに他なりません。無線通信の使用による電池の消耗を最小限に抑えるには、エラー状態を適切に処理し、必要なものだけを取得するようにします。たとえば、ネットワーク操作が失敗しても、常に再試行しないでください。ネットワーク操作が失敗した場合、ユーザーが受信状態でないことが原因である可能性があるため、すぐに再試行してもおそらく再び失敗し、やることすべてが電池の無駄遣いになってしまいます。

ユーザーは非常に賢明です。プログラムが電力を大量に消費している場合、ユーザーはそのことに気付く可能性があります。その時点でデベロッパーが確信できることは、プログラムが近いうちにアンインストールされるだろうということだけです。