Slide 1

Slide 1 text

いかにしてアプリの 
 起動時間を改善するか 2021/10/21 DroidKaigi 2021 @haru067

Slide 2

Slide 2 text

自己紹介 REALITY 2021/09/30まで カンカク 2021/10/01から @haru067 アカウント しごと

Slide 3

Slide 3 text

起動時間 とは?

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

アプリ起動時に起きること Android Developers - App startup time https://developer.android.com/topic/performance/vitals/launch-time 時間

Slide 7

Slide 7 text

アプリ起動時に起きること Android Developers - App startup time https://developer.android.com/topic/performance/vitals/launch-time Application 
 onCreate 時間

Slide 8

Slide 8 text

アプリ起動時に起きること Android Developers - App startup time https://developer.android.com/topic/performance/vitals/launch-time Application 
 onCreate Activity 
 init 時間

Slide 9

Slide 9 text

アプリ起動時に起きること 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 時間

Slide 10

Slide 10 text

アプリ起動時に起きること 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 時間

Slide 11

Slide 11 text

アプリ起動時に起きること 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 ただし、毎回全てが発生するわけではない 時間

Slide 12

Slide 12 text

Android Vitalsにおける起動パターン アプリの安定性とパフォーマンスを改善するためのGoogleの取り組みとして、 
 Android Vitalsがあり、Vitalsでは3つの起動パターンを紹介している ● Cold start ● Warm start ● Hot start 完全にまっさらな状態からの起動 部分的な起動(例:バックキーで戻ってからの再起動) アクティビティをフォアグラウンドにすることで起動

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

● Cold start ● Warm start ● Hot start で、これらをどうすればいいの? Android Vitalsでは、次の場合に遅いとみなされる 5秒以上 2秒以上 1.5秒以上 どれをなおせばいいの? coldを想定するのが良い。なぜならcoldの中にwarmとhotも含まれているから

Slide 15

Slide 15 text

計測

Slide 16

Slide 16 text

なぜ計測するのか ありがちなパターン ● 「うちのアプリはXの処理に時間がかかりすぎだから直そう」 ● 「Yの高速化は簡単そうだし先に直そう」

Slide 17

Slide 17 text

なぜ計測するのか ありがちなパターン ● 「うちのアプリはXの処理に時間がかかりすぎだから直そう」 ● 「Yの高速化は簡単そうだし先に直そう」 ↑それってあなたの感想ですよね?

Slide 18

Slide 18 text

なぜ直感で決めてはいけないか ボトルネックを解消しないと意味がない ● 1秒かかる処理を100倍速くする vs 10秒かかる処理を2倍速くする

Slide 19

Slide 19 text

なぜ直感で決めてはいけないか ボトルネックを解消しないと意味がない ● 1秒かかる処理を100倍速くする vs 10秒かかる処理を2倍速くする 修正コストと得られる効果を考えて、作業の優先順位を決めるべき ● 1日かけて処理Aを0.1秒短縮する vs 10日で処理Bを3秒短縮する

Slide 20

Slide 20 text

なぜ直感で決めてはいけないか ボトルネックを解消しないと意味がない ● 1秒かかる処理を100倍速くする vs 10秒かかる処理を2倍速くする 修正コストと得られる効果を考えて、作業の優先順位を決めるべき ● 1日かけて処理Aを0.1秒短縮する vs 10日で処理Bを3秒短縮する そもそも、認識していないボトルネックが存在するかもしれない

Slide 21

Slide 21 text

なぜ直感で決めてはいけないか ボトルネックを解消しないと意味がない ● 1秒かかる処理を100倍速くする vs 10秒かかる処理を2倍速くする 修正コストと得られる効果を考えて、作業の優先順位を決めるべき ● 1日かけて処理Aを0.1秒短縮する vs 10日で処理Bを3秒短縮する そもそも、認識していないボトルネックが存在するかもしれない 計測より始めよ

Slide 22

Slide 22 text

どうやって計測するか 「Releaseビルド(に相当するbuild variant)で計測する」 DebugビルドはDebug用の処理で実態に即していない結果になる可能性が高い ● LeakCanary, Flipper, Hyperion, etc. 起動時間を短くする目的はユーザー体験の向上であり、開発効率の向上ではない (なおReleaseで計測すると、ビルドに時間がかかったりして大変)

Slide 23

Slide 23 text

計測手順 大きく分けて3つ ● 眺める:起動処理をいくつかに分割して計測し、問題箇所の目星をつける ● 特定する:怪しい箇所をコードレベルで特定して改善する ● 確認する:本番環境で実際に改善されているか確認する

Slide 24

Slide 24 text

計測手順 大きく分けて3つ ● 眺める:起動処理をいくつかに分割して計測し、問題箇所の目星をつける ● 特定する:怪しい箇所をコードレベルで特定して改善する ● 確認する:本番環境で実際に改善されているか確認する 
 Jetpack MacroBenchmark logcat CPU Pro fi ler Firebase Performance Monitoring

Slide 25

Slide 25 text

計測手順 大きく分けて3つ ● 眺める:起動処理をいくつかに分割して計測し、問題箇所の目星をつける ● 特定する:怪しい箇所をコードレベルで特定して改善する ● 確認する:本番環境で実際に改善されているか確認する 
 Jetpack MacroBenchmark logcat CPU Pro fi ler Firebase Performance Monitoring

Slide 26

Slide 26 text

眺める:Jetpack MacroBenchmark (2021.10現在) alpha版、Android 10(API 29)以上 テストを実行する形で起動時間を計測することができる ● cold/warm/hot start の計測 ● 繰り返しの実行、その中央値などの計測 ● テスト結果をトレースファイルとして閲覧 ここでは雰囲気程度に使い方を紹介します 
 (詳細は公式ドキュメントを参照してください)

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

実行結果

Slide 34

Slide 34 text

実行結果

Slide 35

Slide 35 text

眺める:logcatでの計測 ActivityTaskManagerがlogcatに起動時間を出力している • I/ActivityTaskManager: Displayed com.myapp/.MainActivity: +256ms 最初の描画までにかかった時間(Displayed time) I/ActivityTaskManager: Fully drawn com.myapp/.MainActivity: +5s192ms Activity.reportFullyDrawn()が呼ばれるまでの時間 Activity.reportFullyDrawn()は手動で呼んであげる必要あり

Slide 36

Slide 36 text

図で Application 
 onCreate Activity 
 init その他 Activity 
 onCreate in fl ate views, etc Displayed time Fully Drawn

Slide 37

Slide 37 text

時間の計り方 処理の開始/終了の時間を計測し、その差分を印字する場合 val start = System.currentTimeMillis()

Slide 38

Slide 38 text

時間の計り方 処理の開始/終了の時間を計測し、その差分を印字する場合 currentTimeMillisは"時計"の値なので、ユーザーやネットワークから 
 時計が更新されたときに値が急に過去や未来に飛んでしまう val start = System.currentTimeMillis()

Slide 39

Slide 39 text

時間の計り方 処理の開始/終了の時間を計測し、その差分を印字する場合 currentTimeMillisは"時計"の値なので、ユーザーやネットワークから 
 時計が更新されたときに値が急に過去や未来に飛んでしまう 
 なので、SystemClock.uptimeMillisを使うと良い(もしくはSystemClock.elapsedRealTime) val start = System.currentTimeMillis() val start = SystemClock.uptimeMillis()

Slide 40

Slide 40 text

起動開始地点 どの地点をもって起動開始とするか Application.onCreate()? 


Slide 41

Slide 41 text

起動開始地点 どの地点をもって起動開始とするか Application.onCreate()? 
 → ContenteProvider等がApplication.onCreate()より先に実行される より厳密に計測するにはクラスの読み込み時を開始地点にしておくと良い class MyApp : Application() { companion object { val start = SystemClock.uptimeMillis() } ... }

Slide 42

Slide 42 text

計測手順 ● 眺める:起動処理をいくつかに分割して計測し、問題箇所の目星をつける ● 特定する:怪しい箇所をコードレベルで特定して改善する ● 確認する:本番環境で実際に改善されているか確認する 
 Jetpack MacroBenchmark logcat CPU Pro fi ler Firebase Performance Monitoring

Slide 43

Slide 43 text

計測手順 ● 眺める:起動処理をいくつかに分割して計測し、問題箇所の目星をつける ● 特定する:怪しい箇所をコードレベルで特定して改善する ● 確認する:本番環境で実際に改善されているか確認する 
 Jetpack MacroBenchmark logcat CPU Pro fi ler Firebase Performance Monitoring

Slide 44

Slide 44 text

特定する:CPU Profiler 「起動時間」を測定しないといけないのでちょっと工夫が必要 メニューから 
 「Run」 → 「Edit Con fi gurations」を選択

Slide 45

Slide 45 text

CPU Profilerの使い方 Pro fi lingタブの「Start this recording on startup」にチェックし、 
 「CPU activity」「Sample Java Methods」を選択

Slide 46

Slide 46 text

CPU Profilerの使い方 Pro fi leボタン(or Run → Pro fi le 'app')で実行 Pro fi leタブにプロファイルの様子が表示されるので、起動が完了したら止める

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

コードから実行する場合 Debug.startMethodTracingSampling()及び Debug.stopMethodTracing() で 
 コードから動的にプロファイルすることできる。次のようなケースで便利 ● 計測を自動化したい ● 特定の開始/終了地点を正確に記録したい 実行結果はstartで指定したパスに保存されるので、それをPro fi lerで開けばよい

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

Flame Chart

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

Flame Chart

Slide 57

Slide 57 text

Flame Chart

Slide 58

Slide 58 text

Flame Chart setContentView() badFunction()

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

修正作業 ぶっちゃけアプリによりけりなのでなんとも言えないが、 
 大方針としてはだいたい次の3つに集約されるのでは ● キャッシュ ● 並列化・並行化 ● 不要な処理の削除(遅延させる)

Slide 62

Slide 62 text

計測手順 ● 眺める:起動処理をいくつかに分割して計測し、問題箇所の目星をつける ● 特定する:怪しい箇所をコードレベルで特定して改善する ● 確認する:本番環境で実際に改善されているか確認する 
 Jetpack MacroBenchmark logcat CPU Pro fi ler Firebase Performance Monitoring

Slide 63

Slide 63 text

計測手順 ● 眺める:起動処理をいくつかに分割して計測し、問題箇所の目星をつける ● 特定する:怪しい箇所をコードレベルで特定して改善する ● 確認する:本番環境で実際に改善されているか確認する 
 Jetpack MacroBenchmark logcat CPU Pro fi ler Firebase Performance Monitoring

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

確認する 手元で計測して早くなったらいいのか? そんなことはない

Slide 66

Slide 66 text

手元では再現しないパターン ● APIリクエストが本番環境のデータだと遅くなる ● 特定地域・特定回線で遅くなる ● 特定デバイスで遅くなる ● 普段は早いが、極端に遅いことが(特定条件化で)たま〜にある ● ユーザーは常に遅いシステム以上に、普段速くてたまに極端に遅くなるシステ ムを嫌う。意外と危険な兆候

Slide 67

Slide 67 text

Firebase Performance Monitoring 先程述べたようなことを 
 大体カバーしているので便利

Slide 68

Slide 68 text

めでたし? 無事早くなりました! が、アプリの運用を考えると 
 「遅くしないこと」も頑張らないといけない

Slide 69

Slide 69 text

運用・監視

Slide 70

Slide 70 text

なぜ監視が必要か? 機能追加を繰り返す過程で起動時間は知らない間に遅くなる 「なんか最近起動重くない?」と気づくのはまだマシなほうで、じわじわと起動時間 が長くなった場合はそれが当たり前になって誰も気づかない可能性がある

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

よくある過ち 「うちはちゃんと起動時間のログをとっているから問題ないよ」 → ログを活用する習慣がないなら、それはログをとっていないのと同じ

Slide 73

Slide 73 text

監視するには フローとして組み込む ● リリース時の確認項目や定例など 目につくところに流す ● アラートを鳴らすとか、Slackに流すとか ● XXXにアクセスすれば見れるよ、は大体誰も見ない わかりやすく流す ● 情報量が多いとやはり誰も見なくなる(どこを見ていいのかわからなくなる)

Slide 74

Slide 74 text

某アプリでの事例 ● FirebaseのログをData Studio(BIツール)で可視化※ ● (週1リリースなので)週次の定例でグラフを確認 ※ Firebaseのログは一度Big Queryにエクスポートし、それをDataStudio上で参照

Slide 75

Slide 75 text

効果あった?

Slide 76

Slide 76 text

効果あった? あった

Slide 77

Slide 77 text

効果あった? あった 最高!

Slide 78

Slide 78 text

まとめ ● 3種の起動パターン: cold/warm/hot start、まずはcoldから改善していくの がオススメ ● 計測より始めよ ● 3つの計測: 大雑把に怪しい箇所を見つけ、詳細を調べ、実際のユーザーにおける 起動時間が改善しているか確認する ● 計測ツール: MacroBenchmark, logcat, CPU Pro fi ler, Firebase Performance Monitoring ● 速くするだけでなく、「遅くしない」ための監視も頑張ろう

Slide 79

Slide 79 text

参考 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