2018年5月26日土曜日

I/O Recap : ML Kit 情報まとめ(Android 向け)

* 以下は 2018年5月25日時点での情報です。


ML Kit for Firebase

現在 ML Kit はベータで、以下の機能を Android と iOS で利用することができます。
  • テキスト認識(Text recognition)
  • 顔検出(Face detection)
  • バーコードの読み取り(Barcode scanning)
  • 画像のラベルづけ(Image labeling)
  • ランドマーク認識(Landmark recognition)
  • 独自 TensorFlow Lite モデルの実行(TensorFlow Lite model serving)
  • (Google I/O 2018 のセッションで High density face contour feature と Smart Replay API が Coming soon であると紹介されています。いずれも on-device で real time に動作するようです)

これらの処理にはデバイス上で行う on-device API とクラウドで実行される cloud-based API が用意されています。
on-device API はリアルタイムに処理でき、オフラインでも動作し、無料で使うことができます。
cloud-based API(Cloud Vision API)は on-device API よりも詳しい情報を提供しますが、一定の利用回数以上は有料です。

例えばテキスト認識では、on-device API では Latin-based language しか認識できず、他の言語も認識したいなら cloud-based API を使う必要があります。
画像のラベルづけでは、on-device API では 400+ labels ですが cloud-based API なら 1000+ labels に対応しています。

Cloud Vision API を利用するには Firebase の課金プランを Blaze(従量制課金)にする必要があります。
Firebase Pricing Plans

機能ごとに毎月1000 API calls までは無料で使うことができます。
Cloud Vision API Pricing

独自の TensorFlow Lite モデルを Firebase に upload するだけで、アプリからそのモデルを実行できるようになります。 モデルのホスティングと実行は無料で使うことができます。


API には on-device でのみ使えるもの、Cloud でのみ使えるもの、両方用意されているものがあります。

機能on-deviceCloud
テキスト認識oo
顔検出o
バーコード読み取りo
画像のラベルづけoo
ランドマーク認識o
独自モデルの実行o


テキスト認識

https://firebase.google.com/docs/ml-kit/recognize-text

画像からテキストを認識します。

on-device API と cloud-based API 両方用意されています。 on-device API は Latin-based language のみ認識でき、cloud-based API は他の言語にも対応しています。

on-device API は無料で使うことができ、cloud-based API は毎月1000 API calls までは無料で使うことができます。
cloud-based API を使うには Firebase の課金プランを Blaze(従量制課金)にする必要があります。

最新は 16.0.0 です。 dependencies { implementation 'com.google.firebase:firebase-ml-vision:16.0.0' }
cloud-based API には通常の文字認識用の FirebaseVisionCloudTextDetector の他に、書類のように文字密度の高いテキストの認識用に FirebaseVisionCloudDocumentTextDetector が用意されています。


顔検出

https://firebase.google.com/docs/ml-kit/detect-faces

画像から顔を検出します。
目・耳・頬・鼻・口の位置を取得できます。
笑顔かどうか(笑顔の確率)を取得できます。
目が閉じているかどうか(目が閉じている確率)を取得できます。
検出された個々の顔ごとの識別子を取得でき、動画のフレーム間で同一の顔をトラッキングできます。

on-device API のみです。無料で使うことができます。

最新は 16.0.0 です。 dependencies { implementation 'com.google.firebase:firebase-ml-vision:16.0.0' }

バーコード読み取り

https://firebase.google.com/docs/ml-kit/read-barcodes

画像からバーコードを読み取ります。

対応フォーマット
  • Linear formats: Codabar, Code 39, Code 93, Code 128, EAN-8, EAN-13, ITF, UPC-A, UPC-E
  • 2D formats: Aztec, Data Matrix, PDF417, QR Code
バーコードの向きに関係なく認識します。

on-device API のみです。無料で使うことができます。

最新は 16.0.0 です。 dependencies { implementation 'com.google.firebase:firebase-ml-vision:16.0.0' }

画像のラベルづけ

https://firebase.google.com/docs/ml-kit/label-images

追加のメタ情報なしで画像内のエンティティ(人、物、場所、活動など)を認識し、リストとして取得できます。

on-device API と cloud-based API 両方用意されており、on-device API は 400+ labels に、cloud-based API は 1000+ labels に対応しています。
on-device API は無料で使うことができ、cloud-based API は毎月1000 API calls までは無料で使うことができます。
cloud-based API を使うには Firebase の課金プランを Blaze(従量制課金)にする必要があります。

最新は 16.0.0、on-device API は 15.0.0 です。 dependencies { implementation 'com.google.firebase:firebase-ml-vision:16.0.0' // on-device implementation 'com.google.firebase:firebase-ml-vision-image-label-model:15.0.0' }

ランドマーク認識

https://firebase.google.com/docs/ml-kit/recognize-landmarks

画像からランドマーク(例えば東京タワーなど)を認識します。

cloud-based API のみです。そのため Firebase の課金プランを Blaze(従量制課金)にする必要があります。毎月1000 API calls までは無料です。

最新は 16.0.0 です。 dependencies { implementation 'com.google.firebase:firebase-ml-vision:16.0.0' }

独自 TensorFlow Lite モデルの実行

https://firebase.google.com/docs/ml-kit/use-custom-models

独自の TensorFlow Lite モデルは Firebase console からアップロードします。

アプリへのモデルのダウンロードは Firebase が動的に行ってくれるため、APK にモデルをバンドルする必要がありません。これによりアプリのインストール時のサイズを減らすことができます。
また、アプリのリリースとモデルのリリース(Firebase への upload)プロセスが分離されることで、それぞれのチームでリリースをハンドリングできるようになります。

Firebase Remote Config と組み合わせれば A/B test を行うこともできます。
full TensorFlow モデルを lightweight TensorFlow Lite モデルへ変換・圧縮する機能が coming soon だと I/O で発表されています。

(Firebase console に upload せずに)APK にモデルをバンドルしたり、自分のサーバーでモデルをホストしてアプリにダウンロードして、それを ML Kit の API 経由で使うこともできます。


最新は 16.0.0 です。 dependencies { implementation 'com.google.firebase:firebase-ml-model-interpreter:16.0.0' } upload した独自モデルを使うには、FirebaseCloudModelSource.Builder にモデル名を渡して指定します。
(I/O のセッション動画のコードが古いので注意)
モデル名は Firebase にモデルをアップロードするときに指定します。あとから変更はできません。 val cloudSource = FirebaseCloudModelSource.Builder("my_model_v1") ... .build() RemoteConfig でモデル名を切り替えるようにすれば、target ごとにそれぞれ異なるモデルを使うことができます。 val modelName = firebaseRemoteConfig.getString("my_model") val cloudSource = FirebaseCloudModelSource.Builder(modelName) ... .build()

High density face contour feature

I/O のセッション動画 より



100以上の点を検出し 60fps で処理できるとのこと。 coming pretty soon だそうです。


ML Kit console

左側のメニューの [DEVELOP] - [ML Kit] で ML Kit のコンソールを開くことができます。



ここのカスタムタブから独自モデルをアップロードします。



Codelabs

最初に Codelabs のアプリで動作を見てからドキュメントを読むのがよいと思います。
実際に両方ともやりましたが on-device での認識が速くすごいと思いました。

Android 向けの ML Kit のコードラボは2つ用意されています。
サンプルコードは Java で Kotlin 版は用意されていません。

Recognize text in images with ML Kit for Firebase
  • アプリにあらかじめ用意されている画像からテキストを認識する
  • on-device API と cloud-based API 両方使う
  • Cloud Vision API (https://console.cloud.google.com/apis/library/vision.googleapis.com/)を試すには Firebase の料金プランを Blaze(従量課金制)にしないといけない
左: on-device API での認識結果、右 : cloud-based API での認識結果




Identify objects in images using custom machine learning models with ML Kit for Firebase
  • アプリにあらかじめ用意されている画像に対し独自 TensowFlow Lite モデルを実行し、物体を認識する
  • TensorFlow Lite のモデルファイルが用意されているので Firebase console の設定等実際に試すことができてよい



Quick Start Sample

コードラボはテキスト認識と独自モデルしかないので、その他の機能を使い方をみるには Github で公開されている Quick Start Sample が参考になります。
  • Live Preview : カメラのプレビューに対して on-device API (顔検出、テキスト認識、バーコード読み取り、画像のラベルづけ、独自モデル)を実行
  • Still Image : 画像に対して cloud-based API(画像のラベルづけ、ランドマーク認識、テキスト認識)を実行





Google I/O 2018 session



Reference




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