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



0 件のコメント:

コメントを投稿