Upgrade to Pro — share decks privately, control downloads, hide ads and more …

いかにしてアプリの起動時間を改善するか

haru067
October 10, 2021

 いかにしてアプリの起動時間を改善するか

haru067

October 10, 2021
Tweet

More Decks by haru067

Other Decks in Programming

Transcript

  1. いかにしてアプリの

    起動時間を改善するか
    2021/10/21 DroidKaigi 2021 @haru067

    View Slide

  2. 自己紹介
    REALITY


    2021/09/30まで
    カンカク


    2021/10/01から
    @haru067
    アカウント しごと

    View Slide

  3. 起動時間 とは?

    View Slide

  4. ランチャーアイコンをタップ すべてのコンテンツが表示される
    ←この間にかかった時間→

    View Slide

  5. ランチャーアイコンをタップ すべてのコンテンツが表示される
    ←この間にかかった時間→
    何が起きているのか?

    View Slide

  6. アプリ起動時に起きること
    Android Developers - App startup time


    https://developer.android.com/topic/performance/vitals/launch-time
    時間

    View Slide

  7. アプリ起動時に起きること
    Android Developers - App startup time


    https://developer.android.com/topic/performance/vitals/launch-time
    Application

    onCreate
    時間

    View Slide

  8. アプリ起動時に起きること
    Android Developers - App startup time


    https://developer.android.com/topic/performance/vitals/launch-time
    Application

    onCreate
    Activity

    init
    時間

    View Slide

  9. アプリ起動時に起きること
    Android Developers - App startup time


    https://developer.android.com/topic/performance/vitals/launch-time
    Application

    onCreate
    Activity

    init
    Activity

    onCreate
    in
    fl
    ate views, etc
    時間

    View Slide

  10. アプリ起動時に起きること
    Android Developers - App startup time


    https://developer.android.com/topic/performance/vitals/launch-time
    Application

    onCreate
    Activity

    init
    その他
    Activity

    onCreate
    in
    fl
    ate views, etc
    時間

    View Slide

  11. アプリ起動時に起きること
    Android Developers - App startup time


    https://developer.android.com/topic/performance/vitals/launch-time
    Application

    onCreate
    Activity

    init
    その他
    Activity

    onCreate
    in
    fl
    ate views, etc
    ただし、毎回全てが発生するわけではない
    時間

    View Slide

  12. Android Vitalsにおける起動パターン
    アプリの安定性とパフォーマンスを改善するためのGoogleの取り組みとして、

    Android Vitalsがあり、Vitalsでは3つの起動パターンを紹介している


    ● Cold start


    ● Warm start


    ● Hot start


    完全にまっさらな状態からの起動


    部分的な起動(例:バックキーで戻ってからの再起動)


    アクティビティをフォアグラウンドにすることで起動

    View Slide

  13. Google Play Console上のAndroid Vitals
    品質 → Android Vitals → 概要 で確認できる


    View Slide

  14. ● Cold start


    ● Warm start


    ● Hot start
    で、これらをどうすればいいの?
    Android Vitalsでは、次の場合に遅いとみなされる
    5秒以上


    2秒以上


    1.5秒以上
    どれをなおせばいいの?
    coldを想定するのが良い。なぜならcoldの中にwarmとhotも含まれているから

    View Slide

  15. 計測

    View Slide

  16. なぜ計測するのか
    ありがちなパターン


    ● 「うちのアプリはXの処理に時間がかかりすぎだから直そう」


    ● 「Yの高速化は簡単そうだし先に直そう」

    View Slide

  17. なぜ計測するのか
    ありがちなパターン


    ● 「うちのアプリはXの処理に時間がかかりすぎだから直そう」


    ● 「Yの高速化は簡単そうだし先に直そう」
    ↑それってあなたの感想ですよね?

    View Slide

  18. なぜ直感で決めてはいけないか
    ボトルネックを解消しないと意味がない


    ● 1秒かかる処理を100倍速くする vs 10秒かかる処理を2倍速くする

    View Slide

  19. なぜ直感で決めてはいけないか
    ボトルネックを解消しないと意味がない


    ● 1秒かかる処理を100倍速くする vs 10秒かかる処理を2倍速くする


    修正コストと得られる効果を考えて、作業の優先順位を決めるべき


    ● 1日かけて処理Aを0.1秒短縮する vs 10日で処理Bを3秒短縮する


    View Slide

  20. なぜ直感で決めてはいけないか
    ボトルネックを解消しないと意味がない


    ● 1秒かかる処理を100倍速くする vs 10秒かかる処理を2倍速くする


    修正コストと得られる効果を考えて、作業の優先順位を決めるべき


    ● 1日かけて処理Aを0.1秒短縮する vs 10日で処理Bを3秒短縮する


    そもそも、認識していないボトルネックが存在するかもしれない

    View Slide

  21. なぜ直感で決めてはいけないか
    ボトルネックを解消しないと意味がない


    ● 1秒かかる処理を100倍速くする vs 10秒かかる処理を2倍速くする


    修正コストと得られる効果を考えて、作業の優先順位を決めるべき


    ● 1日かけて処理Aを0.1秒短縮する vs 10日で処理Bを3秒短縮する


    そもそも、認識していないボトルネックが存在するかもしれない
    計測より始めよ

    View Slide

  22. どうやって計測するか
    「Releaseビルド(に相当するbuild variant)で計測する」


    DebugビルドはDebug用の処理で実態に即していない結果になる可能性が高い


    ● LeakCanary, Flipper, Hyperion, etc.


    起動時間を短くする目的はユーザー体験の向上であり、開発効率の向上ではない


    (なおReleaseで計測すると、ビルドに時間がかかったりして大変)

    View Slide

  23. 計測手順
    大きく分けて3つ


    ● 眺める:起動処理をいくつかに分割して計測し、問題箇所の目星をつける


    ● 特定する:怪しい箇所をコードレベルで特定して改善する


    ● 確認する:本番環境で実際に改善されているか確認する

    View Slide

  24. 計測手順
    大きく分けて3つ


    ● 眺める:起動処理をいくつかに分割して計測し、問題箇所の目星をつける


    ● 特定する:怪しい箇所をコードレベルで特定して改善する


    ● 確認する:本番環境で実際に改善されているか確認する

    Jetpack MacroBenchmark logcat
    CPU Pro
    fi
    ler
    Firebase Performance Monitoring

    View Slide

  25. 計測手順
    大きく分けて3つ


    ● 眺める:起動処理をいくつかに分割して計測し、問題箇所の目星をつける


    ● 特定する:怪しい箇所をコードレベルで特定して改善する


    ● 確認する:本番環境で実際に改善されているか確認する

    Jetpack MacroBenchmark logcat
    CPU Pro
    fi
    ler
    Firebase Performance Monitoring

    View Slide

  26. 眺める:Jetpack MacroBenchmark
    (2021.10現在) alpha版、Android 10(API 29)以上


    テストを実行する形で起動時間を計測することができる


    ● cold/warm/hot start の計測


    ● 繰り返しの実行、その中央値などの計測


    ● テスト結果をトレースファイルとして閲覧


    ここでは雰囲気程度に使い方を紹介します

    (詳細は公式ドキュメントを参照してください)

    View Slide

  27. Jetpack MacroBenchmarkの使い方
    専用のモジュールを作成

    View Slide

  28. Jetpack MacroBenchmarkの使い方
    build.gradleを修正

    View Slide

  29. Jetpack MacroBenchmarkの使い方
    com.android.library → com.android.test
    testImplementation, androidTestImplementation → implementation

    View Slide

  30. Jetpack MacroBenchmarkの使い方
    AndroidManifest.xmlに追加

    View Slide

  31. Jetpack MacroBenchmarkの使い方
    テストを記述して実行

    View Slide

  32. Jetpack MacroBenchmarkの使い方
    テストを記述して実行

    View Slide

  33. 実行結果

    View Slide

  34. 実行結果

    View Slide

  35. 眺める:logcatでの計測
    ActivityTaskManagerがlogcatに起動時間を出力している



    I/ActivityTaskManager: Displayed com.myapp/.MainActivity: +256ms
    最初の描画までにかかった時間(Displayed time)
    I/ActivityTaskManager: Fully drawn com.myapp/.MainActivity: +5s192ms
    Activity.reportFullyDrawn()が呼ばれるまでの時間
    Activity.reportFullyDrawn()は手動で呼んであげる必要あり

    View Slide

  36. 図で
    Application

    onCreate
    Activity

    init
    その他
    Activity

    onCreate
    in
    fl
    ate views, etc
    Displayed time
    Fully Drawn

    View Slide

  37. 時間の計り方
    処理の開始/終了の時間を計測し、その差分を印字する場合


    val start = System.currentTimeMillis()

    View Slide

  38. 時間の計り方
    処理の開始/終了の時間を計測し、その差分を印字する場合


    currentTimeMillisは"時計"の値なので、ユーザーやネットワークから

    時計が更新されたときに値が急に過去や未来に飛んでしまう
    val start = System.currentTimeMillis()

    View Slide

  39. 時間の計り方
    処理の開始/終了の時間を計測し、その差分を印字する場合


    currentTimeMillisは"時計"の値なので、ユーザーやネットワークから

    時計が更新されたときに値が急に過去や未来に飛んでしまう

    なので、SystemClock.uptimeMillisを使うと良い(もしくはSystemClock.elapsedRealTime)
    val start = System.currentTimeMillis()
    val start = SystemClock.uptimeMillis()

    View Slide

  40. 起動開始地点
    どの地点をもって起動開始とするか


    Application.onCreate()?

    View Slide

  41. 起動開始地点
    どの地点をもって起動開始とするか


    Application.onCreate()?

    → ContenteProvider等がApplication.onCreate()より先に実行される


    より厳密に計測するにはクラスの読み込み時を開始地点にしておくと良い


    class MyApp : Application() {


    companion object { val start = SystemClock.uptimeMillis() }


    ...


    }

    View Slide

  42. 計測手順
    ● 眺める:起動処理をいくつかに分割して計測し、問題箇所の目星をつける


    ● 特定する:怪しい箇所をコードレベルで特定して改善する


    ● 確認する:本番環境で実際に改善されているか確認する

    Jetpack MacroBenchmark logcat
    CPU Pro
    fi
    ler
    Firebase Performance Monitoring

    View Slide

  43. 計測手順
    ● 眺める:起動処理をいくつかに分割して計測し、問題箇所の目星をつける


    ● 特定する:怪しい箇所をコードレベルで特定して改善する


    ● 確認する:本番環境で実際に改善されているか確認する

    Jetpack MacroBenchmark logcat
    CPU Pro
    fi
    ler
    Firebase Performance Monitoring

    View Slide

  44. 特定する:CPU Profiler
    「起動時間」を測定しないといけないのでちょっと工夫が必要


    メニューから

    「Run」 → 「Edit Con
    fi
    gurations」を選択

    View Slide

  45. CPU Profilerの使い方
    Pro
    fi
    lingタブの「Start this recording on startup」にチェックし、

    「CPU activity」「Sample Java Methods」を選択

    View Slide

  46. CPU Profilerの使い方
    Pro
    fi
    leボタン(or Run → Pro
    fi
    le 'app')で実行


    Pro
    fi
    leタブにプロファイルの様子が表示されるので、起動が完了したら止める

    View Slide

  47. CPU Profilerを活用する
    スレッド毎にトレースが表示される

    View Slide

  48. コードから実行する場合
    Debug.startMethodTracingSampling()及び
    Debug.stopMethodTracing() で

    コードから動的にプロファイルすることできる。次のようなケースで便利


    ● 計測を自動化したい


    ● 特定の開始/終了地点を正確に記録したい


    実行結果はstartで指定したパスに保存されるので、それをPro
    fi
    lerで開けばよい

    View Slide

  49. CPU Profilerを活用する
    各スレッドを開くと時系列に沿った各メソッドの実行時間が確認できる

    View Slide

  50. CPU Profilerを活用する
    各スレッドを開くと時系列に沿った各メソッドの実行時間が確認できる
    例として、MainActivity.onCreateを調査してみましょう

    View Slide

  51. 例:MainActivity.onCreateの調査
    MainActivity.onCreateをクリック

    View Slide

  52. 例:MainActivity.onCreateの調査
    右のパネルに色々表示される。今回はFlame Chartをクリック

    View Slide

  53. Flame Chart

    View Slide

  54. Flame Chart
    横軸を実行時間の割合(%)として、コールスタックが縦に積まれる
    左から実行時間の長い順に並ぶ

    View Slide

  55. Flame Chart
    横軸を実行時間の割合(%)として、コールスタックが縦に積まれる
    ボトルネック? 問題なし?
    左から実行時間の長い順に並ぶ

    View Slide

  56. Flame Chart

    View Slide

  57. Flame Chart

    View Slide

  58. Flame Chart
    setContentView() badFunction()

    View Slide

  59. Flame Chartの使用例
    setContentViewに一番時間がかかっている(でも直すのは無理そう)

    View Slide

  60. Flame Chartの使用例
    badFunctionに時間がかかっている。内訳はrepeat()とLog.d()


    View Slide

  61. 修正作業
    ぶっちゃけアプリによりけりなのでなんとも言えないが、

    大方針としてはだいたい次の3つに集約されるのでは


    ● キャッシュ


    ● 並列化・並行化


    ● 不要な処理の削除(遅延させる)

    View Slide

  62. 計測手順
    ● 眺める:起動処理をいくつかに分割して計測し、問題箇所の目星をつける


    ● 特定する:怪しい箇所をコードレベルで特定して改善する


    ● 確認する:本番環境で実際に改善されているか確認する

    Jetpack MacroBenchmark logcat
    CPU Pro
    fi
    ler
    Firebase Performance Monitoring

    View Slide

  63. 計測手順
    ● 眺める:起動処理をいくつかに分割して計測し、問題箇所の目星をつける


    ● 特定する:怪しい箇所をコードレベルで特定して改善する


    ● 確認する:本番環境で実際に改善されているか確認する

    Jetpack MacroBenchmark logcat
    CPU Pro
    fi
    ler
    Firebase Performance Monitoring

    View Slide

  64. 確認する
    手元で計測して早くなったらいいのか?


    View Slide

  65. 確認する
    手元で計測して早くなったらいいのか?


    そんなことはない

    View Slide

  66. 手元では再現しないパターン
    ● APIリクエストが本番環境のデータだと遅くなる


    ● 特定地域・特定回線で遅くなる


    ● 特定デバイスで遅くなる


    ● 普段は早いが、極端に遅いことが(特定条件化で)たま〜にある


    ● ユーザーは常に遅いシステム以上に、普段速くてたまに極端に遅くなるシステ
    ムを嫌う。意外と危険な兆候

    View Slide

  67. Firebase Performance Monitoring
    先程述べたようなことを

    大体カバーしているので便利

    View Slide

  68. めでたし?
    無事早くなりました!


    が、アプリの運用を考えると

    「遅くしないこと」も頑張らないといけない

    View Slide

  69. 運用・監視

    View Slide

  70. なぜ監視が必要か?
    機能追加を繰り返す過程で起動時間は知らない間に遅くなる


    「なんか最近起動重くない?」と気づくのはまだマシなほうで、じわじわと起動時間
    が長くなった場合はそれが当たり前になって誰も気づかない可能性がある

    View Slide

  71. よくある過ち
    「うちはちゃんと起動時間のログをとっているから問題ないよ」


    View Slide

  72. よくある過ち
    「うちはちゃんと起動時間のログをとっているから問題ないよ」


    → ログを活用する習慣がないなら、それはログをとっていないのと同じ

    View Slide

  73. 監視するには
    フローとして組み込む


    ● リリース時の確認項目や定例など


    目につくところに流す


    ● アラートを鳴らすとか、Slackに流すとか


    ● XXXにアクセスすれば見れるよ、は大体誰も見ない


    わかりやすく流す


    ● 情報量が多いとやはり誰も見なくなる(どこを見ていいのかわからなくなる)

    View Slide

  74. 某アプリでの事例
    ● FirebaseのログをData Studio(BIツール)で可視化※


    ● (週1リリースなので)週次の定例でグラフを確認
    ※ Firebaseのログは一度Big Queryにエクスポートし、それをDataStudio上で参照

    View Slide

  75. 効果あった?

    View Slide

  76. 効果あった?
    あった

    View Slide

  77. 効果あった?
    あった


    最高!

    View Slide

  78. まとめ
    ● 3種の起動パターン: cold/warm/hot start、まずはcoldから改善していくの
    がオススメ


    ● 計測より始めよ


    ● 3つの計測: 大雑把に怪しい箇所を見つけ、詳細を調べ、実際のユーザーにおける
    起動時間が改善しているか確認する


    ● 計測ツール: MacroBenchmark, logcat, CPU Pro
    fi
    ler, Firebase
    Performance Monitoring


    ● 速くするだけでなく、「遅くしない」ための監視も頑張ろう

    View Slide

  79. 参考
    https://developer.android.com/topic/performance/vitals/launch-time


    https://dev.to/pyricau/android-vitals-what-time-is-it-2oih


    https://dropbox.tech/mobile/how-we-sped-up-dropbox-android-app-startup-by-30-


    https://medium.com/okcredit/how-okcredit-android-app-improved-cold-startup-
    by-70-e02bda4836a8


    https://developer.android.com/studio/pro
    fi
    le/generate-trace-logs


    https://developer.android.com/studio/pro
    fi
    le/measuring-performance


    https://medium.com/androiddevelopers/app-startup-part-1-34f57b65cacd


    https://developer.android.com/studio/pro
    fi
    le/cpu-pro
    fi
    ler

    View Slide