こんにちは。ヤフーでAndroid版 Yahoo!マップを担当しているエンジニアの福野です。
さっそくですが速さが足りません。私たちは情熱、思想、理念、頭脳、気品、優雅さ、勤勉さをもってアプリの開発に取り組んでいるつもりですが、競合の某マップアプリに比べ私たちのアプリは起動速度が足りていません。
その起動速度について、Android版 Yahoo!マップでは最近大きな改善がありました。この記事ではその手法を解説します。
左:改善前、右:改善後
※開発中の画面です。(C)Mapbox,(C)OpenStreetMap,(C)Yahoo Japan
起動速度について考えるべきこと
ユーザーにとってストレスのないレスポンスを実現するのは私たちエンジニアの仕事のひとつです。
Yahoo!オークション(現ヤフオク!)の高速化について14年前に投稿された「ウェブページの高速化に必要なもの」という記事があります。時がたっても、ロード時間から起動時間に話が変わっても、ウェブサイトからモバイルアプリに環境が変わってもその原則は変わりません。見ているのは人間ですので、起動全体にかかる時間よりも、人間の体感速度がどうであるかを考える必要があります。
起動速度の指標
そんなわけでYahoo!マップでは起動速度について以下の時間を定期的に測定し、日頃から改善に努めています。
- First Paint:アプリアイコンクリック直後に最初の画面が表示されるまでの時間
- First Contentful Paint:画面の見た目の要素(レイアウト)ができるまでの時間
- First Meaningful Paint:意味のある情報が表示されるまでの時間
- Event End:完全に画面の要素が表示されるまでの時間
今回の記事では1と2の改善(とくに2の改善)を取り上げます。
First Paint改善
Androidアプリの場合、アプリアイコンクリック直後に表示される最初の画面というのは上記画像のようなスプラッシュスクリーンです。
Android 12からはほとんどのアプリでこのスプラッシュスクリーンが表示されますが、Android版 Yahoo!マップアプリでは2023年2月頃のリリースからSplashScreen 互換性ライブラリを導入し、Android 11以前でも同様の画面が表示されるようにしました。
今まで一面真っ白だった画面の真ん中にロゴが表示されるようになったというだけの変化ではあるのですが、繰り返し重要なのは人間の体感速度です。真っ白であるよりなにかは表示されていたほうが安心できます。
First Contentful Paint改善
という感じにFirst Paintの改善に取り組みましたが、スプラッシュスクリーンがあってもその後の画面変化が遅ければ不安ですから起動速度改善の活動は終わりません。スプラッシュスクリーンの後はさっさと次の画面に移る必要があります。そのためにFirst Contentful Paintの改善にも取り組みました。
この改善のためにはせっかく表示したスプラッシュスクリーンを今度はすぐにでも消す必要があります。Android Developersによるとアプリが最初のフレームを描画すればスプラッシュスクリーンが閉じるとのことですから、とにかく最初の描画を早くしなければなりません。
地図UIの遅延読み込み対応
最初の描画を早くしたいなら、遅延読み込みを実装するのが良いでしょう。起動直後はレイアウトだけを描画し、中身は後から読み込みます。
Android版 Yahoo!マップでは2021年9月頃からコンパスや現在地のボタンを遅延読み込み化させています。これでFirst Contentful Paintが改善しましたが、某アプリにはまだわずかに及びません。速さが足りない。
何もかも遅延読み込み対応
速さが足りない。最速を目指そうと思ったとき、遅延読み込みといってもレイアウト自体は普通に描画させる従来のアプローチでは限界があると思いました。
<?xml version="1.0" encoding="utf-8"?>
<CoordinatorLayout xmlns:android="https://schemas.android.com/apk/res/android">
<!-- いろいろ -->
<include layout="@layout/layout"/>
<RelativeLayout android:layout_width="@dimen/width1" android:layout_height="@dimen/height1">
<!-- いろいろ -->
</RelativeLayout>
<LinearLayout android:layout_width="@dimen/width2" android:layout_height="@dimen/height2">
<!-- いろいろ -->
</LinearLayout>
<FragmentContainerView android:layout_width="@dimen/width3" android:layout_height="@dimen/height3"
android:name="jp.co.yahoo.ExampleFragment"/>
</CoordinatorLayout>
だいたいレイアウトのみ描画…といってもこのくらい歴史が長くて機能も多岐なアプリではそのレイアウトが結構な数です。Androidで画面を提供するのはActivityなのでUIを持つアプリはActivityがなければ起動できませんが、そのActivityと密結合しているPresenterに密結合しているViewのようなものがあって、それらは起動時点からViewが作られていることが前提の実装になっていたりするので一部分だけ遅延読み込みさせるような実装を難しくもしています。
<?xml version="1.0" encoding="utf-8"?>
<CoordinatorLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:background="@drawable/main_activity_loading_background">
<ViewStub android:layout_width="@dimen/width0" android:layout_height="@dimen/height0"/>
<ViewStub android:layout_width="@dimen/width1" android:layout_height="@dimen/height1"/>
<ViewStub android:layout_width="@dimen/width2" android:layout_height="@dimen/height2"/>
<FragmentContainerView android:layout_width="@dimen/width3"
android:layout_height="@dimen/height3"/> <!-- Fragmentは後から入れる -->
</CoordinatorLayout>
じゃあもう、ViewStubなどを使って、いっそのこと親のレイアウト以外何もかも全部遅延読み込みにしたらどうでしょうか。
しかし全部遅延読み込みということは、起動時点では何も読み込まれないので画面が真っ白になってしまいます。それだと良くないので背景画像だけは設定します。
設定する背景画像はロゴとかローディングスピナーとかいろいろ考えられますが、そういうシンプルなものだとスプラッシュスクリーンと代わり映えしません。
そこでスケルトンスクリーン風の背景画像を作ることにしました。
この背景画像が
画面サイズまで引き伸ばされるとこう見える
下記動画も参照
実のところこの記事を書く際に用語を調べるまで知らなかったのですが、上記画像のように画面要素の枠だけが出ている画面のことをスケルトンスクリーンと言います。
スケルトンスクリーンにおける「要素の枠」は、遅延読み込みによってそのまま本物の要素に置き換えられます。今回の場合だとスケルトンスクリーンに見せかけたただの背景画像なので実装上は置き換えも何もないのですが、起動完了後のレイアウトを真似て背景画像を作っておけば遅延読み込みによって要素が置き換えられていくように見えます。
改善の効果
さまざまな改善を行ってきましたが、個人的に印象に残っているのは最後に紹介したスケルトンスクリーン風背景画像です。冒頭でも紹介しましたが、これの導入前後で起動速度がどのように変化したか私の開発機で比較したものをもう一度動画に示します。全体的な起動速度も若干上がっていますが今回はFirst Contentful Paintまでの時間に注目ください。
左:改善前、右:改善後
※開発中の画面です。(C)Mapbox,(C)OpenStreetMap,(C)Yahoo Japan
この通り画面のレイアウトが描画される(ように見える)までの時間が大幅に短縮されています。実際の速さはOSバージョン・機種によりますが、評価用端末による定期的な起動速度測定では以下のような結果になりました。
- First Paintまでの時間は改善前と同等
- First Containtful Paintはおよそ5割削減
- First Meaningful Paintはおよそ2割削減
- Event Endは若干削減
First Contentful Paintまでの時間をおよそ半減させることができました! これは某アプリよりも早い数値です。
おわりに
という具合に起動速度が爆速になった、一部では競合より速くなったわけですが…改善したのはFirst PaintとFirst Contentful Paintです。
すなわちFirst Meaningful PaintやEvent Endまでの時間はあまり短縮が進んでいません。(一応遅延読み込み化によって若干良くなる傾向にはあるのですが)
しかしそれらの改善も私たちエンジニアの仕事のひとつです。私たちは現在も起動直後の通信削減やリファクタなどによってさらなる起動速度向上に取り組み続けています。
起動速度向上をはじめさまざまな改善を進めているYahoo!マップをぜひご活用ください。お読みいただきありがとうございました。
こちらの記事のご感想を聞かせください。
- 学びがある
- わかりやすい
- 新しい視点
ご感想ありがとうございました
- 福野 将人
- Yahoo!マップ エンジニア
- アプリの起動速度を上げることならできるのですが、かつて本気の追いかけっこを挑んできた実家の猫からはこいつノロいからやっぱり無理はさせないでやろうという慈しみを持った態度で介護されています。