この記事は Kateryna Semenova, Rahul Ravikumar, Chris Craik による Android Developers Blog の記事 " Improving App Performance with Baseline Profiles " を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
多くのアプリにおいて、アプリのパフォーマンスとユーザー エンゲージメントとの間に相関性があることがわかっています。ユーザーが期待するのは、応答性が高く、読み込みが速いアプリです。起動時間は、アプリのパフォーマンスと品質の重要な指標の 1 つになっています。
私たちのパートナーのいくつかは、アプリの起動を最適化するために、すでに多くの時間とリソースを費やしています。その一例として、Facebook のストーリーをご確認ください。
この記事では、ベースライン プロファイルと、それがどのようにアプリやライブラリのパフォーマンスを改善するかを紹介します。その結果、起動時間が最大 40% 短縮されることもあります。ここでは起動に注目しますが、ベースライン プロファイルはジャンクにも大変有効です。
Android 9 (API レベル 28) では、アプリの起動時間短縮を目的として、Play Cloud に ART 最適化プロファイル (英語) が導入されました。クラウド プロファイルが利用できる場合、アプリのコールド スタートは、さまざまなデバイスで少なくとも平均 15% 高速になることがわかっています。
アプリがインストール後やアップデート後に初めて起動する場合、JIT コンパイルが行われるまでの間、コードはインタープリタ モードで動作します。APK では、Java と Kotlin のコードは dex バイトコードにコンパイルされますが、完全にコンパイルしたアプリの保存や読み込みにかかるコストの関係で、完全にマシンコードにコンパイルされることはありません (Android 6 以降)。アプリのクラスやメソッドのうち、頻繁に使われるものやアプリの起動に使われるものは、プロファイルのファイルに記録されます。そしてデバイスがアイドルモードになると、ART がそのプロファイルに基づいてアプリをコンパイルします。これにより、その後のアプリ起動が高速になります。
Android 9 (API レベル 28) より、Google Play もクラウド プロファイルを提供するようになっています。デバイスでアプリを実行すると、ART が生成したプロファイルが Play Store アプリにアップロードされ、クラウドに集約されます。アプリに対して十分な数のプロファイルがアップロードされると、Play アプリは集約したプロファイルを利用して、その後のインストールを行います。
クラウド プロファイルが利用できる場合、それが大きな効果を発揮してくれますが、アプリをインストールするときに常に利用できるとは限りません。通常、プロファイルの収集と集約には数日かかるため、多くのアプリで毎週アップデートが行われると、この点が問題になります。クラウド プロファイルが利用できるようになる前に、多くのユーザーがアップデートをインストールすることになるからです。そこで、Google の Android チームは、プロファイルが用意できるまでの時間を短縮する別の方法を探し始めました。
ベースライン プロファイルは、プロファイルを提供する新たなメカニズムであり、Android 7 (API レベル 24) 以降で利用できます。ベースライン プロファイルは、Android Gradle プラグインが生成する ART プロファイルです。人間が読むことができるプロファイル形式になっており、アプリやライブラリによって提供されます。たとえば、次のようなものです。
HSPLandroidx/compose/runtime/ComposerImpl;->updateValue(Ljava/lang/Object;)VHSPLandroidx/compose/runtime/ComposerImpl;->updatedNodeCount(I)IHLandroidx/compose/runtime/ComposerImpl;->validateNodeExpected()VPLandroidx/compose/runtime/CompositionImpl;->applyChanges()VHLandroidx/compose/runtime/ComposerKt;->findLocation(Ljava/util/List;I)I
HSPLandroidx/compose/runtime/ComposerImpl;->updateValue(Ljava/lang/Object;)V
HSPLandroidx/compose/runtime/ComposerImpl;->updatedNodeCount(I)I
HLandroidx/compose/runtime/ComposerImpl;->validateNodeExpected()V
PLandroidx/compose/runtime/CompositionImpl;->applyChanges()V
HLandroidx/compose/runtime/ComposerKt;->findLocation(Ljava/util/List;I)I
Compose ライブラリの例
バイナリのプロファイルは、APK のアセット ディレクトリの特定の場所 (assets/dexopt/baseline.prof )に格納されます。
ベースライン プロファイルは、ビルド時に作成され、APK の一部として Play に提供されます。そして、ユーザーがアプリをダウンロードするときに、Play からユーザーに送信されます。クラウド プロファイルがまだ利用できない場合、ベースライン プロファイルが ART クラウド プロファイル パイプラインの足りない部分を補います。クラウド プロファイルが利用できる場合、ベースライン プロファイルは自動的にクラウド プロファイルと結合されます。
ベースライン プロファイルの作成からエンドユーザーへの配信までのワークフローを示した図
ベースライン プロファイルの特に大きなメリットは、ローカルで開発や評価が行える点です。そのため、デベロッパーは実際にエンドユーザーが体験するパフォーマンスの向上を確認できます。また、クラウド プロファイルは Android 9 以降でしか利用できませんが、ベースライン プロファイルはそれよりも古いバージョンの Android (7 以降) でもサポートされています。
2021 年の前半に、Google マップのリリース サイクルが 2 週間から 1 週間に変更されました。アップデートの頻度が高くなるということは、ローカルの作成済みプロファイルがより頻繁に破棄され、Play クラウド プロファイルが存在しないために起動が遅くなるユーザーが増えるということでもあります。しかし、ベースライン プロファイルを使うことで、Google マップの起動時間は平均 30% 短縮され、それに伴って検索が 2.4% 増加しました。このように既に定着したアプリにとっても、非常に大きな成果です。
ライブラリに含まれるコードも、アプリのコードと同じです。デフォルトでは完全にコンパイルされないので、起動時のクリティカル パスで大量の処理を行う場合、問題につながる可能性があります。
Jetpack Compose は、Android システム イメージには含まれない UI ライブラリです。そのため、多くの Android View ツールキットのコードとは異なり、インストール時に完全にコンパイルされることはありません。そのため、パフォーマンスの問題が発生することがありました。特にそれが顕著だったのが、最初の数回のコールド スタート時です。
この問題を解決するため、Compose はプロファイル インストーラを利用します。これがベースライン プロファイルのルールを提供してくれるので、Compose アプリの起動時間やジャンクが減少します。
Google Play ストアの検索結果ページは、Compose を使って書き直されています。Compose からベースライン プロファイル ルールを取り込んだ後では、イメージを含む最初の検索結果ページを表示するまでの時間が最大 40% 短縮されました。
Android チームも、関連する AndroidX ライブラリにベースライン プロファイルを追加しました。これは、対象のライブラリを使うすべての Android アプリにメリットがあります。Constraint Layout では、プロファイルのルールを提供 (英語) することで、アニメーションのフレーム時間が 1 ミリ秒以上短縮されました。
ベースライン プロファイルを含めると、アプリやライブラリのデベロッパーすべてがメリットを得られます。理想的な方法は、特に重要なユーザー操作について、デベロッパーが複数のプロファイルを作成することです。それにより、クラウド プロファイルが利用できるかどうかによらず、常にその操作のパフォーマンスが高速になります。アプリ デベロッパーとライブラリ デベロッパー向けのベースライン プロファイルの設定方法は、詳細なガイドをご覧ください。
すぐにはアプリのベースライン プロファイルを作成できないという方でも、依存関係を更新することで、ベースライン プロファイルによるメリットを享受できます。Android Gradle Plugin 7.1.0-alpha05 以降でビルドすれば、ライブラリ (Jetpack など) が提供するベースライン プロファイルを APK に含めることができます。Google Play は、インストール時にそのプロファイルを使ってアプリをコンパイルします。プロファイルの追加は、アプリのビルドの一環として行うことができます。
忘れずに改善の測定を行うようにしましょう。生成されたプロファイルを使ってローカルで起動を測定するには、こちらの手順に従います。
ぜひフィードバックを共有し、皆さんの体験をお知らせください! (英語)
アプリの起動時間短縮は簡単な作業ではなく、起動に影響するさまざまなことを深く理解する必要があります。今年、Google の Android チームと Facebook のアプリチームは、アプリの起動を高速化するために、共同で指標を検討したり、アプローチを共有したりしています。Google による Android の公式ドキュメントには、アプリ起動の最適化に関するたくさんの情報が掲載されています。ここではさらに、それがどのように Facebook アプリに当てはまり、何がアプリ起動の高速化に役立ったかをお伝えしたいと思います。
現在、毎月 29 億人以上の人々が Facebook を使っています。Facebook はコミュニティを形成する力や、世界をより身近にする力を人々に与えます。また、Facebook は人生の思い出の瞬間を共有したり、今起きていることを知って議論したり、つながりを作って育んだり、協力してビジネス チャンスを生み出したりする場所です。
Facebook アプリの開発チームは、どんなデバイスや国、ネットワーク環境であっても、シームレスにアプリを動作させて可能な限り最高の体験を提供できるように、努力を重ねています。Google の Android チームと Facebook のチームは、合同でアプリ起動に関する指標の定義とベスト プラクティスについて検討しました。この記事では、その内容について説明します。
まずは起動時間の測定です。そこから、ユーザーの起動時のエクスペリエンスがどのくらい優れているかがわかり、リグレッションが発生すれば追跡できます。さらに、改善にどのくらいの手間をかけるべきかもわかります。最終的には、作業の優先順位を決めるために、起動時間をユーザーの満足度やエンゲージメント、ユーザーベースの拡大の度合いと紐付ける必要があります。
Android では、アプリの起動時間を測定する指標として、完全表示までの時間(Time-To-Full-Display; TTFD)と初期表示までの時間(Time-To-Initial-Display; TTID)という 2 つの指標が定義されています。さらにコールド スタートとウォーム スタートに分けることができますが、この投稿では、この 2 つは明確に区別しません。Facebook のアプローチでは、アプリを使うすべてのユーザーの起動時間を測定して最適化します(その一部はコールド、その他はウォームです)。
TTFD は、アプリがレンダリングを終え、ユーザーが操作や閲覧を行える状態になるまでの時間を表します。このときレンダリングされる内容には、ディスクやネットワークにあるコンテンツが含まれているはずです。遅いネットワークの場合、この処理にしばらく時間がかかるかもしれません。また、ユーザーがどのページを開くかによって異なる可能性もあります。そのため、すぐに何かを表示して、処理が進行中であることをユーザーに知らせる方法も有効でしょう。そこで登場するのが TTID です。
TTID は、アプリが背景やナビゲーション、高速に読み込めるローカル コンテンツ、読み込みに時間がかかるローカル コンテンツに代わるプレースホルダ、またはネットワーク コンテンツを描画する時間を表します。TTID は、ユーザーがナビゲーションを行って目的の場所に行けるようになるタイミングである必要があります。
変更しすぎない: 注意すべき点の 1 つは、TTID と TTFD の間での、アプリのコンテンツの位置の視覚的な変化です。たとえば、キャッシュしたコンテンツを表示し、ネットワーク コンテンツを取得できたタイミングで入れ替えるような場合、視覚的な変化が発生することがあります。ユーザーはこれを不快に感じるかもしれません。そのため、TTID では、できる限り TTFD に近く、十分に意味のあるコンテンツを描画するようにします。
ユーザーはコンテンツを求めてアプリを開きます。そのコンテンツの読み込みには、しばらく時間がかかる可能性がありますが、皆さんはできるだけ早くコンテンツを提供したいと思うはずです。
Facebook アプリの開発チームは、すべてのコンテンツとイメージを含む完全表示までの時間(TTFD)に基づいた指標に注目しています。TTFD こそ、ユーザーがアプリを開いた目的である完全な体験を表すものだからです。コンテンツやイメージを取得するためのネットワーク呼び出しに時間がかかったり、失敗したりする場合は、デベロッパーがそれを把握することで、起動処理の全体を改善できます。
Facebook の起動指標は、「悪い」と見なされるアプリ起動の割合です。「悪い」と見なされるのは、TTFD が 2.5 秒を超えた場合、または起動処理のいずれかが失敗した場合(イメージの読み込み失敗、アプリのクラッシュなど)です。Facebook は、この「悪い」起動の割合を減らすことに重点的に取り組んでいます。具体的には、起動が成功したとしても時間が 2.5 秒を超えた場合はその処理を改善し、起動が失敗した場合はその原因を修正します。2.5 秒という数値は、この数値が Facebook ユーザーにとって有意義であるという調査結果に基づいて選ばれました(これは、ウェブサイト向けの Web Vitals 推奨事項の Largest Contentful Paint(LCP)指標とも一致します)。
すべての処理、とりわけ最新コンテンツを取得するネットワーク呼び出しを含めれば、TTFD 起動指標は TTID よりもかなり遅いと感じるかもしれません。これは、実際には好ましいことであり、ユーザーがアプリで実際に体験することを表しています。Facebook でそうであるように、これを改善することでアプリの利用が増加したり、ユーザーの間でアプリのパフォーマンスに関する認知度が高まったりする可能性があります。
アプリによっては、TTFD を測定するのが難しい場合もあります。難しすぎる場合は、初期表示までの時間(TTID)から始めても問題ありません。プレースホルダやイメージがある場合は、一部のコンテンツを読み込む際のパフォーマンスが見逃されてしまう可能性がありますが、たとえそれがアプリを毎日使うユーザーが見るものの一部でしかなくても、できるところから始めることが重要です。
Android 4.4(API レベル 19)以降の logcat では、プロセスが起動してから、対応するアクティビティの最初のフレームが画面に描画されるまでの経過時間が、“Displayed” 値として取得されます。
報告されるログの行は、次の例のようになります。
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms
TTFD のインストルメンテーションを行うには、画面にすべてのコンテンツが表示されてから、アクティビティで reportFullyDrawn() (英語) を呼びます。プレースホルダを置き換えるすべてのコンテンツと、レンダリングするすべてのイメージを含めるようにしてください(プレースホルダだけでなく、表示されるイメージ自体もカウントします)。reportFullyDrawn() を呼ぶと、logcat に次の内容が表示されます。
ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms
Facebook アプリ開発チームは、さまざまなデバイス、プラットフォーム、国の膨大なユーザーのために、長年にわたってアプリの最適化を行っています。このセクションでは、Facebook アプリ開発チームがアプリの起動を最適化するために活用したいくつかの重要な教訓について説明します。
Google の Android チームは、アプリの起動時間を測定して最適化する際の推奨事項を、アプリの起動時間というドキュメントで公開しています。このセクションには、一部の重要なポイントを記載します。その内容は上記の Facebook の推奨事項とも結びついており、すべての Android アプリ デベロッパーが考慮すべきことです。
この記事では、起動に関するいくつかの重要な測定項目に加えて、起動時のエクスペリエンスを高めてユーザー エンゲージメントを向上させたり、Facebook Android アプリを普及させたりするためのベスト プラクティスについて説明しました。さらに、Google の Android チームがおすすめする指標やライブラリ、ツールも紹介しました。ドキュメントに記載されている戦略を適用すれば、どんな Android アプリにでもメリットが生じるはずです。アプリを測定して、ユーザーにとって心地よい高速な起動を実現しましょう。