2018年5月18日金曜日

IO recap : Android vitals: debug app performance and reap rewards (Google I/O '18)



星1つのレビューでは42%のユーザーが安定性やバグについて言及している
星5つのレビューでは73%のユーザーがスピード、デザイン、使いやすさについて言及している

ANR 率やクラッシュ率が上がると、ユーザーがアプリで費やす時間が有意に減る


Android vitals とは、Android デバイスの安定性とパフォーマンスを向上するための Google が主導する取り組み
もっとも重要なパフォーマンスメトリクスであるバッテリー、安定性、レンダリングの情報を開発者にわかりやすく提供する
データ提供を opt in している1億以上のユーザーからの情報

昨年 Android vitals をリリースしてから 10,000 以上の開発者がコンソールからパフォーマンスを理解した
昨年に比べ、スピード、デザイン、使いやすさについて言及している星5つのレビューは 4% 増え、安定性やバグに言及している星1つのレビューは 18% 減り、リソースの使用について言及している星1つのレビューは 21% 減った


Starbucks アプリは ANR rate が 70% 減り、Crash rate が 85% 減った
ANR は 3rd party のライブラリで起こっていたため、自分たちの観測に引っかかっていなかった
Android vitals はプラットフォームレベルのツールなので、Starbucks の 3rd party crash SDK では検出できていなかったクラッシュを見つけることができた
なぜならそのクラッシュは 3rd party crash SDK が開始される前に起こっていたため

Kiloo の Subway Surfers というゲームでは ANR を 95% 減らすことができた


昨年 Android vitals を公開したとき、バッテリー、安定性、レンダリングの3つの項目があった
新しくアプリのスタートアップ時間(App startup time)と権限(Permissions)が追加された

Vitals
  • バッテリー(Battery)
  • 安定性(Stability)
  • レンダリング(Rendering)
  • New: アプリのスタートアップ時間(App startup time)
    • コールド スタートアップ時間が長い(Slow cold start)
    • ウォーム スタートアップ時間が長い(Slow warm start)
    • ホット スタートアップ時間が長い(Slow hot start)
  • New: 権限(Permissions)
    • 権限リクエストの拒否率(Permission request denials)

バッテリー(Battery)

  • 停止した wake lock
  • 過度の wakeup
  • 過度のバックグラウンドでの Wi-Fi スキャン
  • 過度のバックグラウンドでのネットワーク使用

安定性(Stability)

  • ANR 発生率
  • クラッシュ発生率

レンダリング(Rendering)

  • フリーズした UI フレーム
  • 遅いレンダリング

New: アプリのスタートアップ時間(App startup time)

  • コールド スタートアップ時間が長い : 5秒以上
  • ウォーム スタートアップ時間が長い : 2秒以上
  • ホット スタートアップ時間が長い : 1.5秒以上

New Metric: コールド スタートアップ時間が長い
  • 5秒以上かかると遅いと判断
  • コールドスタート :
    • Activity が起動してから running になるまで
    • Activity launched → onCreate() → onStart() → onResume() → Activity running
    • アプリがしばらく使われておらず、アプリがメモリ上にいない状態からスタート

New Metric: ウォーム スタートアップ時間が長い
  • 2秒以上かかると遅いと判断
  • ウォームスタート :
    • Activity が起動してから running になるまで
    • Activity launched → onCreate() → onStart() → onResume() → Activity running
    • アプリが最近使われており、アプリがメモリ上にいる状態からスタート(アプリはkillされていない)

New Metric: ホット スタートアップ時間が長い
  • 1.5秒以上かかると遅いと判断
  • ホットスタート :
    • onRestart() から running になるまで
    • onRestart() → onStart() → onResume() → Activity running
    • アプリと Activity がメモリ上にいる状態からスタート

New: 権限(Permissions)

アプリのコアバリューに必要な権限だけをリクエストし、必要に応じて権限リクエストの正当な理由をランタイム時に提供する
  • 権限リクエストの拒否率
〜40%のユーザーが権限を拒否した理由として、その権限は不必要だと思ったと回答している

権限の詳細ビューでは権限をグループに分けて表示しているので、どの権限がユーザーにとって納得感があり、どの権限が不必要だと思われているかがわかる

Android Vitals の詳細の内訳

  • 一般的な内訳
    • APK versionごと
    • デバイスごと
    • Android versionごと
  • Wake locks, wakeups
    • tag ごと
  • ANR率
    • Activity 名ごと
    • ANR type ごと
    • Clusters
  • クラッシュ率
    • Clusters

カテゴリーベンチマーク

自分のアプリの vital が特定のカテゴリーの中でどのくらい良いかを見ることができる
vital の各 metric でパーセンタイル 25, 50, 75 の値を見ることができる

概要画面で全ての vital がリストされ、直近の30日とその前の30日の値、ベンチマークの値を見ることができる



概要画面の主な指標(Core Vitals)には Google Play でのアプリの表示やランキングに影響するパフォーマンス指標が表示される
主な指標が下位25%より悪くなると Bad behavior として表示される

異常検知(Anomaly Detection)

新リリースやリグレッションの結果値に急変があるとアラートを出す
  • ANRやクラッシュ率の大きな変化
  • 主な指標(Core Vitals)の大きな変化
概要画面右上の[通知設定]からAlertをメールで受け取るよう設定できる




主な指標(Core Vitals)を改善するには

ANRの原因
  • Network / Disk operations
  • Long calculations
  • InterProcess Communication (IPC)
  • Locks and Synchronization
  • Slow BroadcastReceiver handling

Network / Disk operations

例: SharedPreferences インスタンスを生成する時点で Disk 処理が行われる override fun onCreate(state: Bundle?) { // この時点で Disk 処理が行われる prefs = PreferenceManager.getDefaultSharedPreferences(this) } どのメソッドが Network 処理や Disk 処理をするのか理解するのは難しいので StrictMode を利用する class MyApplication : Application() { override fun onCreate() { super.onCreate() if (BuildConfig.DEBUG) { StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectDiskWrites() .detectNetwork() .penaltyLog() .penaltyDeath() .build()) } } }

Long calculations

例: 数独ゲームの盤面生成に時間がかかる

Android Profiler で CPU の使用をチェックする
ちなみに Android Studio 3.2 Canary ではスタートアップ時間をプロファイルできるようになっている

StrictMode にはこのメソッドを呼ぶと遅くなるということを指定できる class GenerateBoardSource() { fun generateBoard(seed: Long) : SudokuGame { StrictMode.noteSlowCall("Generating Sudoku board") return SudokuSolver.generate() } } StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder() ... .detectCustomSlowCalls() ... .build())

InterProcess Communication (IPC)
  • 他のアプリを呼び出す場合、基本こちらに制御権がない
  • 呼び出し先が Network 処理や Disk 処理をするかもしれないので別スレッドで呼び出す


Locks and Synchronization
  • これはとても難しい問題
  • deadlock になったり main thread をブロックするかもしれない
  • デバッグが難しい
  • Android Vitals が提供する trace file の情報がデバッグに役立つかもしれない


Slow BroadcastReceiver handling
  • Android Manifest に BroadcastReceiver を登録した場合、onReceive メソッドは main thread で呼び出される
  • 実行に時間がかかる処理を onReceive() でやるべきではない
  • 10秒以内に処理を終えないと ANR になる
  • Notification に表示する画像を Disk から読み出すなどちょっとした Disk 処理が必要な場合は onReceive() で goAsync() を呼び、別のスレッドを立ち上げ、終わったら PendingResult.finish() を呼ぶ
  • https://developer.android.com/guide/components/broadcasts#effects-process-state
  • あまり長いと結局システムに kill されるので、長い処理が必要なら JobScheduler や WorkManager を使う



クラッシュ対策
  • クラッシュの対応として Activity のライフサイクルでむやみに null チェックや例外の握りつぶしをするべきではない
  • 車輪の再発明をしない : 問題を解決する利用できるライブラリを使う
    • Lifecycle handling (LiveData, ViewModel)
    • Database object mapping (Room)
    • Data paging (Paging)
    • *NEW* Fragment transitions, up/back, deep link handling (Navigation)
    • *New* Job scheduling (WorkManager)
  • 3rd party の優れたライブラリもたくさんある
  • Kotlin を使う : でも全てのクラッシュを防げる銀の弾丸ではないよ!
  • private / hidden API を使わない


バッテリー対策

停止した wake lock

wake lock が取得されたが適切に release されなかった
  • wake lock を使わない
  • 画面をつけっぱなしにしたいなら Activity の Window に FLAG_KEEP_SCREEN_ON を指定する
  • 自分で Service を管理せず job を schedule する
  • AlarmManager で BroadcastReceiver を起こすようにすると onReceive() の間 AlarmManager は wake lock を hold してしまう
  • wake lock を使わなければ permission も必要なくなる
  • どうしても wake lock を使わないといけないなら、常に PARTIAL_WAKE_LOCK を使うこと
  • wakeLock.acquire() にタイムアウトをセットすること
  • static な descriptive tag を渡すこと(Android Vitals でのデバッグがしやすくなる)
  • try { ... } finally { wakeLock.release() } すること


過度の wakeup
  • もっとも大きい原因は AlarmManager の *_WAKEUP アラーム
  • 可能ならなくす(Remove)
  • 頻度を減らす(Reduce)
  • FCM, WorkManager, JobScheduler, SyncManager などに置き換える(Replace)
  • Android Studio 3.2 Canary に追加された Energy Profiler で wake lock に関する問題をデバッグできる


関連





2018年5月17日木曜日

IO recap : Migrate your existing app to target Android Oreo and above (Google I/O '18)



新規アプリは2018年8月以降
既存アプリのアップデートは2018年11月以降
targetSdkVersion を >= 26 にしないといけない話

Permissions

Runtime permissions : ユーザーは設定からon/offできる
Special permissions : 画面の上にdrawするやつとか

Alarms

WorkManager を使う

BroadcastReceivers

Android Manifest で register したほとんどの implicit receiver はもはや受け取れなくなる
例外もある、ACTION_BOOT_COMPLETED とか

BroadcastReceiver の使用を避ける例として、JobScheduler を使ってネットワーク状態の変更を検知し、 val jobScheduler = getSystemService(Context.JOB_SCHDULER_SERVICE) as JobScheduler val jobInfo = JobInfoBuilder(JOB_ID, serviceComponent) .setRequiredNetwork(JobInfo.NETWORK_TYPE_ANY) .build() BroadcastReceiver は Android Manifest で disabled にしておき(android:enabled="false" を追加) <receiver android:name=".NetworkConnectionReceiver" android:enabled="false"> <intent-filter> <action android:name="android.net.conn.CONNECTIVITY_CHANGE" /> </intent-filter> </receiver> JobScheduler で receiver を有効にする fun setNetworkReceiverState(enabled:Boolean) { val componentName = ComponentName(package, NetworkConnectivityReceiver::class.java.name) val state = if (enabled) { PackageManager.COMPONENT_ENABLED_STATE_ENABLED } else { PackageManager.COMPONENT_ENABLED_STATE_DISABLED } packageManager.setComponentEnabledSetting( componentName, state, PackageManager.DONT_KILL_APP ) }

ACTION_MY_PACKAGE_REPLACED で全ての処理をやるのではなく ChangedPackages でも判断できる val packages : ChangedPackages = packageManager.getChangedPackages(prefs.getPackageSequenceNumber()) prefs.setPackageSequenceNumber(packages.getSequenceNumber())

Background Limits

Foreground Service にすべきものならそうする

Foreground のもの
  • Visible App
  • Foreground Service
  • Foreground Client に bound されている Service
  • ForeGround Client への Content Provider
  • AccessibilityService, NotificationListenerService, AbstractAccountAuthenticator, WallpaperServiceなどの例外もある
Background のもの
  • Not visible
  • Non-Foreground Service
  • JobService
  • BroadcastReceiver
O以降では、Background から Service を start しようとすると IllegalStateException が投げられる

Grace Period : Service が background に置かれてから1分程度は生きている

Whitelist
  • Notification action
  • High Priority FCM message
  • SMS/MMS delivery
Background Service を使わずに Background で仕事をさせるには
  • background task には WorkManager を使う
  • IntentService は JobIntentService に置き換える

JobScheduler の振る舞いについてよく理解するまで Android L で JobScheduler を使わないほうがいい
L と M の first release では JobScheduler は同じ constrains の2つの job を正しく実行しない問題がある
この問題の workaround として、同じ constrains の2つの job をスケジューリングすればよい(がどうするかは言ってない)
MR1 以降は JobScheduler はちゃんと動いているが、minimum latecy を 0 にセットするのはやめたほうがよい
失敗したときの処理は backoff でやること
backoff を超えて reschedule しないこと

PendingIntent の向き先を Service から explicit な BroadcastReceiver に変更し、30秒以内に goAsync() を呼び出す
または BroadcastReceiver 内で WorkManager を使う

外部からの time-sensitive な trigger が必要なら Firebase Cloud Messaging を使う
high priority messages はデバイスが DOZE でも起きるので使いすぎはよくない
10秒以内に実行し終えるならそのままそこで処理をし、それ以上かかるなら WorkManager を使う

ユーザーが明示的に始めた時間のかかる処理は Foreground Service で実行する
Maps Navigation, fitness tracking, playing music など

Photo Broadcasts

N 以降では Photo Broadcasts が起こらないので、代わりに ContentUris をトリガーとした work を使う val constrains = Constraints.Builder() .constraints.addContentUriTrigger(SOME_URI, true) ... .build() val work = OneTimeWorkRequest.Builder(MyWork::class.java) .setConstraints(constrains) .build()

Background Location

O 以降のデバイスでは Background Location の制限は targetSdkVersion によらず適用される

対応として
  • 1. Geofencing を使う : 100個までしか Geofencing を active にできないので、必要に応じて動的に変えるなどの対策をとる
  • 2. Beacon による Nearby Notification を使う
  • 3. FusedLocationProvider の Batch 処理 fun createLocationRequest() { ... val request = LocationRequest() request.interval = 10L * 60L * 1000L request.maxWaitTime = 30L * 60L * 1000L }
  • 4. Passive Location を使う fun createLocationRequest() { ... val request = LocationRequest() request.interval = 10L * 60L * 1000L request.maxWaitTime = 30L * 60L * 1000L request.fastestInterval = 2L * 60L * 1000L }
アプリの location を更新するのはネットワーク処理などの重たい処理と紐づいているべき

Battery

バッテリーに関する機能
  • Doze(M+)
  • Doze on the go (N+)
  • App Standby (M~O)
  • App Standby Buckets (P+)
App Standby Buckets (P+)
  • 使用履歴に基づく制限
  • アプリは Standby Bucket のどこかに割り当てられる
  • 割り当てられた Bucket によって適用される制限が変わる


Battery Saver (P+)
  • Screen Off のときは Location を取らない
  • 全てのアプリが App Standby
  • Background のアプリは Network 処理をできない
  • (OLED Devices では)可能なら Dark Theme が有効になる

Testing

Testing Doze $ adb shell dumpsys deviceidle force-idle Testing App Standby $ adb shell dumpsys battery unplug $ adb shell am get-inactive <package-name> $ adb shell am set-inactive <package-name> true Testing App Standby Buckets 1. $ adb shell dumpsys battery unplug 2. $ adb shell am get-standby-bucket <package name> 10 ACTIVE 20 WORKING_SET 30 FREQUENT 40 RARE 3. $ adb shell am set-standby-bucket <package name> <bucket> 4. API: UsageStatsManager.getAppStandbyBucket() Testing Battery Saver $ adb shell dumpsys battery unplug $ adb shell settings put global low_power 1 <do your tests> $ adb shell dumpsys battery reset API: PowerManager.isPowerSaveMode() PowerManager.ACTION_POWER_SAVE_MODE_CHANGED アプリに Dark Theme があるなら Save Mode のときは Dark Theme にするという選択肢
OLED Devices なら電池の節約になる

Modern features

  • Notification Channels
  • Display Cutout : Developer Options で Cutout モードにできる
  • Picture in Picture
  • Multi-display

non-SDK interface

DP1 で non-SDK interface の使用を制限し、使われていたら Toast や log で警告を出すようにした

DP2 ではメソッドが単に動かなくなるので、アプリがクラッシュすることになる

将来的には StrictMode に新しい VM policy を追加する
これを使って全ての non-SDK API を検出できる StrictMode.setVmPolicy( StrictMode.VmPolicy.Builder() .detectNonSdkApiUsage().build()) non-SDK の使用がライブラリ内で起こるかもしれないので、これでチェックすることが重要

https://developer.android.com/distribute/best-practices/develop/target-sdk



2018年5月16日水曜日

IO recap : What's new with ConstraintLayout and Android Studio design tools (Google I/O '18)




design 時の tools: 属性
  • tools:context
  • tools:itemCount
    • ListView, RecyclerView のプレビューで表示するitemの数
  • tools:layout
  • tools:listitem
    • ListView, RecyclerView のプレビューで表示する各itemのレイアウト
  • tools:listheader
  • tools:listfooter
  • tools:showIn
    • 指定した layout リソースに include された状態のプレビューになる
  • tools:menu
  • tools:minValue
  • tools:maxValue
  • tools:openDrawer
  • tools:text
    • プレビューでの文字
  • tools:textColor
    • プレビューでの文字色


サンプルデータ

[File] - [New] - [Sample Data Directory] から sample data を置く場所を作る
場所としては app/ 直下に sampledata/ が作られる



ここに material_colors という名前のファイルを作って、各行にカラーコードを書く #F44336 #9C27B0 #3F51B5 #673AB7 RecyclerView の各 item の ImageView に tools:tint="@sample/material_colors" と指定すると、行ごとに @sample/material_colors 内の色が順番に割り当てられる

dimensions なら 4dp 8dp 16dp ... のようなファイルを用意する

画像の custom sample data はディレクトリ(例えば albumcovers/)を作ってサンプルデータの画像をそこに配置する
ディレクトリ名に _ や大文字を使うと動かないので注意



JSON の custom sample Data では複数のサンプルをまとめて定義できる

albumname.json { "songs" : [ {"title": "Nougat", "author" : "Dylan Dalton"}, {"title": "Ice Cream", "author": "Isa Henderson"}, ... ]} tools:text="@sample/albumname.json/title" tools:text="@sample/albumname.json/author"

Predefined Sample data

Images, Text, olors, Dates...
  • @tools:sample/first_names
  • @tools:sample/last_names
  • @tools:sample/full_names
  • @tools:sample/cities
  • @tools:sample/us_phones
  • @tools:sample/us_zipcodes
  • @tools:sample/date/day_of_week
  • @tools:sample/date/ddmmyy
  • @tools:sample/date/hhmm
  • @tools:sample/date/hhmmss
  • @tools:sample/date/mmddyy
  • @tools:sample/lorem
  • @tools:sample/lorem/random
  • @tools:sample/avatars
  • @tools:sample/backgrounds/scenic
  • ...
Android Studio 3.2 では resource picker の Drawable のところに sample data category が追加された



Android Studio 3.2 の新しい design time helper では、ImageView を選択して表示されるレンチアイコンをクリックして、sample data を切り替えたり、data set のうち一つだけを使うようにできる
Browse をクリックすると、resource picker が開く



design time helper は TextView でも使える







RecyclerView でも使える

listitem に使うレイアウトや itemCount を指定できる
用意されているテンプレを指定すると、そのレイアウトリソースが layout/ にコピーされる
(たまにレンチが出なくなり、まだかなり不安定)












ConstraintLayout

去年(2017) ConstraintLayout 1.0 をリリースした
先月(2018/4) 1.1 をリリースした
implementation 'com.android.support.constraint:constraint-layout:1.1.0'

ConstraintLayout 2.0

Helpers
  • 画面に表示されないがUIを作成するのを助けるもの
  • Guideline とか Barrier とか
  • Viewの参照を保持してあれこれする
  • ConstraintHelper を継承して Custom の Helper を作れる
Helper の3つのカテゴリー
  • Layout Manipulation
    • LinearLayout のような配置を助ける Helper とか FlexBox 的な配置を助けるものとか
  • Post-Layout Manipulation
    • flying object みたいなエフェクトをかけるやつとか
  • Rendering or Decorating
    • Viewの代わりに描画するやつ
Helper の例

Layers
  • Helper の一種
  • View のセットに対する表示・非表示・変形などをサポートする




Circular Reveal
  • Rendering or Decorating 系の Helper
  • 既存の circular reveal code を利用
  • 参照しているViewにだけ適用


Lava Decorator
  • Rendering or Decorating 系の Helper
  • View の background を透明にして、この Decorator が Lava っぽい描画を実現する




Bottom Panel Decorator
  • ボトムパネルの background にインタラクティブなエフェクトを描画するやつ


Helper で view の実態と behavior を分離できる



ConstraintLayout 2.0

ConstraintLayout 2.0 では State を XML で指定できる <ConstraintLayoutStates> <State android:id="+id/small" app:constraints="@layout/layout_small" /> <State android:id="+id/large" app:constraints="@layout/layout_large" /> </ConstraintLayoutStates> fun onCreate(savedInstanceState : Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.layout); cl = findViewById(R.id.root) cl.setLayoutDescription(R.xml.layout_states) } fun change(v : View) { cl.setState(closed ? R.id.large : R.id.small) closed = !closed } 特定の constraint set が適用されるときの region を指定できる <ConstraintLayoutStates> <State app:constraints="@layout/layout_small" > <Constraints app:constraints="@layout/layout_small" app:region_widthLessThan="550dp" /> <Constraints app:constraints="@layout/layout_large" app:region_widthLessThan="450dp" /> </State> </ConstraintLayoutStates> fun onConfigurationChanged(newConfig : Configuration) { super.onConfigurationChanged(newConfig) cl.setState(newConfig.screenWidthDp, newConfig.screenHeightDp) } cl.setOnConstraintsChanged { state, layoutId -> TransitionManager.beginDelayedTransition(cl) }

MotionLayout
  • ConstraintLayout 2.2 予定
  • ConstraintLayout の subclass
  • ConstraintLayout の全てのプロパティを持っている
  • 2つの State 間のアニメーションを代わりにやってくれる
MotionLayout が複数の View と Helper を持ち、MotionScene が2つの ConstraintSet と OnTouch と KeyFrame を持つ
ConstraintSet に custom 属性が増え、アニメーションをカスタマイズできる
MotionLayout は Nest できる




Motion Editor
  • keyframe を追加してアニメーションを編集できる
  • going work now

Codelab