Android で Kotlin を使用して位置情報の更新を受信する

1. 始める前に

Android 10 と 11 では、デバイスの位置情報へのアプリのアクセスをユーザーがより詳細に制御できます。

Android 11 で稼働しているアプリが位置情報アクセス権限をリクエストすると、ユーザーには次の 4 つのオプションが表示されます。

  • 常に許可
  • アプリの使用中のみ許可(Android 10 の場合)
  • 1 回のみ(Android 11 の場合)
  • 拒否

Android 10

6a1029175b467c77.png

Android 11

73d8cc88c5877c25.png

この Codelab では、位置情報の更新を受信する方法と、Android のあらゆるバージョン(特に Android 10 と 11)で位置情報をサポートする方法について学習します。この Codelab を終了すると、位置情報の更新を取得するための現在のベスト プラクティスに沿ったアプリが完成します。

前提条件

演習内容

  • Android の位置情報に関するベスト プラクティスに沿って対応します。
  • フォアグラウンドでの位置情報の利用許可を処理します(アプリの使用中にデバイスの位置情報にアクセスするようユーザーがリクエストした場合)。
  • 既存のアプリを変更して、位置情報のサブスクライブとサブスクライブ解除のコードを追加することで、位置情報へのアクセスをリクエストするサポートを追加します。
  • フォアグラウンドでの位置情報へのアクセスまたは使用中の位置情報へのアクセスに関するロジックを追加して、Android 10 と 11 をサポートするようアプリを更新します。

必要なもの

  • コードを実行するための Android Studio 3.4 以降
  • Android 10 と 11 のデベロッパー プレビューを実行しているデバイス/エミュレータ

2. はじめに

スターター プロジェクト リポジトリのクローンを作成する

できるだけ早く開始できるように、このスターター プロジェクトを基盤として構築できます。Git がインストールされている場合は、次のコマンドを実行します。

 git clone https://github.com/android/codelab-while-in-use-location

GitHub ページに直接アクセスすることもできます。

Git がない場合は、プロジェクトを ZIP ファイルとして取得できます。

プロジェクトをインポートする

Android Studio を開き、ウェルカム画面で [Open an existing Android Studio project] を選択してプロジェクト ディレクトリを開きます。

プロジェクトが読み込まれた後、ローカルで行った変更の一部が Git によりトラッキングされていないことを示すアラートが表示される場合もあります。[無視] をクリックすることもできます。(変更を Git リポジトリにプッシュバックすることはありません)。

[Android] ビューの場合は、プロジェクト ウィンドウの左上に次のような画像が表示されます([Project] ビューの場合は、プロジェクトを展開すると同じ画像が表示されます)。

fa825dae96c5dc18.png

2 つのフォルダ(basecomplete)があり、それぞれを「モジュール」と呼びます。

初めてバックグラウンドでプロジェクトをコンパイルする場合、数秒かかる場合があります。その間、Android Studio の下部にあるステータスバーに次のメッセージが表示されます。

c2273e7835c0841a.png

Android Studio でプロジェクトのインデックス作成とビルドが完了するまで待ち、その後でコードを変更します。このコンパイルで、必要なすべてのコンポーネントを Android Studio に取り込むことができます。

「Reload for language changes to take effect?」のようなメッセージが表示された場合は、[Yes] を選択します。

スターター プロジェクトを理解する

セットアップが完了し、アプリで位置情報をリクエストする準備が整いました。base モジュールを出発点として使用します。各ステップで、base モジュールにコードを追加します。この Codelab を完了すると、base モジュールのコードが complete モジュールの内容と一致するようになります。complete モジュールは、作業のチェックや、問題が発生した場合の参照用として使用できます。

主なコンポーネントは次のとおりです。

  • MainActivity - アプリがデバイスの位置情報にアクセスすることをユーザーが許可するための UI
  • LocationService - 位置情報の変更を登録および登録解除し、ユーザーがアプリのアクティビティから離れた場合にフォアグラウンド サービス(通知付き)に昇格するサービス。ここに位置情報コードを追加します。
  • Util - Location クラスの拡張関数を追加し、SharedPreferences(簡略化されたデータレイヤ)に位置情報を保存します。

エミュレータのセットアップ

Android Emulator のセットアップについては、エミュレータで実行するをご覧ください。

スターター プロジェクトを実行する

アプリを実行します。

  1. Android デバイスをパソコンに接続するか、エミュレータを起動します。(デバイスに Android 10 以降が搭載されていることを確認してください)。
  2. ツールバーで、プルダウン メニューから base の構成を選択し、[実行] をクリックします。

99600e9d44527ab.png

  1. デバイスに次のアプリが表示されます。

99bf1dae46f99af3.png

出力画面に位置情報が表示されないことがあります。これは、地域コードがまだ追加されていないためです。

3. 場所を追加する

コンセプト

この Codelab の目的は、位置情報の更新を受信し、最終的に Android 10 と Android 11 をサポートする方法を示すことです。

ただし、コーディングを開始する前に、基本を確認しておくことをおすすめします。

位置情報へのアクセスの種類

この Codelab の冒頭で、位置情報へのアクセスに関する 4 つのオプションについて説明しました。それぞれの意味は次のとおりです。

  • アプリの使用中のみ許可
  • このオプションは、ほとんどのアプリにおすすめです。「使用中のみ」または「フォアグラウンドのみ」のアクセスとも呼ばれるこのオプションは、Android 10 で追加されました。アプリがアクティブに使用されている間のみ、デベロッパーが位置情報を取得できるようにします。次のいずれかに該当する場合、アプリはアクティブとみなされます。
  • アクティビティが表示されている。
  • フォアグラウンド サービスが進行中の通知とともに実行されている。
  • 1 回のみ
  • Android 11 で追加されました。アプリの使用中のみ許可と同じですが、許可されるのは限られた時間のみです。詳しくは、1 回限りの権限をご覧ください。
  • 拒否
  • このオプションを選択すると、位置情報にアクセスできなくなります。
  • 常に許可
  • このオプションでは、位置情報へのアクセスを常に許可できますが、Android 10 以降では追加の権限が必要です。また、有効なユースケースがあり、位置情報に関するポリシーに準拠していることを確認する必要があります。このオプションは、使用頻度が少ないため、この Codelab では扱いません。ただし、有効なユースケースがあり、バックグラウンドでの位置情報へのアクセスなど、常に位置情報を適切に処理する方法を理解したい場合は、LocationUpdatesBackgroundKotlin サンプルをご覧ください。

サービス、フォアグラウンド サービス、バインディング

アプリの使用中のみ許可の位置情報の更新を完全にサポートするには、ユーザーがアプリから移動したときのことを考慮する必要があります。その状況で更新を引き続き受信する場合は、フォアグラウンド Service を作成して Notification に関連付ける必要があります。

また、アプリが表示されているときとユーザーがアプリから離れたときに同じ Service を使用して位置情報の更新をリクエストする場合は、その Service を UI 要素にバインド/バインド解除する必要があります。

この Codelab では位置情報の更新の取得のみに焦点を当てているため、必要なコードはすべて ForegroundOnlyLocationService.kt クラスにあります。そのクラスと MainActivity.kt を参照して、それらがどのように連携しているかを確認できます。

詳細については、サービスの概要バインドされたサービスの概要をご覧ください。

権限

NETWORK_PROVIDER または GPS_PROVIDER から位置情報の更新を受け取るには、Android マニフェスト ファイル内で ACCESS_COARSE_LOCATION パーミッションまたは ACCESS_FINE_LOCATION パーミッションをそれぞれ宣言して、ユーザー パーミッションをリクエストする必要があります。これらの権限がないと、アプリは実行時に位置情報へのアクセスをリクエストできません。

これらの権限は、Android 10 以降を搭載したデバイスでアプリが使用される場合の [1 回のみ許可] と [アプリの使用中のみ許可] のケースを対象としています。

ロケーション

アプリは、com.google.android.gms.location パッケージ内のクラスを通じて、サポートされている位置情報サービスのセットにアクセスできます。

メインクラスを確認します。

  • FusedLocationProviderClient
  • これは、位置情報フレームワークの中心となるコンポーネントです。作成したら、それを使用して位置情報の更新をリクエストし、最後に確認された位置情報を取得します。
  • LocationRequest
  • これは、リクエストのサービス品質パラメータ(更新間隔、優先度、精度)を含むデータ オブジェクトです。これは、位置情報の更新をリクエストするときに FusedLocationProviderClient に渡されます。
  • LocationCallback
  • これは、デバイスの位置情報が変更されたときや、位置情報を特定できなくなったときに通知を受け取るために使用されます。これは LocationResult に渡され、データベースに保存する Location を取得できます。

これで、何をするのかがわかったので、コードの作成を始めましょう。

4. 位置情報機能を追加する

この Codelab では、最も一般的な位置情報オプションである [アプリの使用中のみ許可] に焦点を当てます。

位置情報の更新データを受け取るには、アプリに表示可能なアクティビティがあるか、フォアグラウンドで実行中のサービス(通知付き)が必要です。

権限

この Codelab の目的は、位置情報の更新を受け取る方法を示すことであり、位置情報の権限をリクエストする方法を示すことではないため、権限ベースのコードはすでに記述されています。すでに理解している場合は、スキップしてもかまいません。

権限のハイライトは次のとおりです(この部分については対応は不要です)。

  1. AndroidManifest.xml で使用する権限を宣言します。
  2. 位置情報にアクセスする前に、ユーザーがアプリにアクセス権限を付与しているかどうかを確認します。アプリがまだ権限を取得していない場合は、アクセスをリクエストします。
  3. ユーザーの権限の選択を処理します。(このコードは MainActivity.kt で確認できます)。

AndroidManifest.xml または MainActivity.ktTODO: Step 1.0, Review Permissions を検索すると、権限用に記述されたすべてのコードが表示されます。

詳しくは、権限の概要をご覧ください。

それでは、位置情報コードの記述を開始しましょう。

位置情報の更新に必要な主な変数を確認する

base モジュールで、TODO: Step 1.1, Review variables

ForegroundOnlyLocationService.kt ファイル。

この手順では操作は必要ありません。位置情報の更新を受け取るために使用する主なクラスと変数を理解するには、次のコードブロックとコメントを確認するだけで済みます。

// TODO: Step 1.1, Review variables (no changes).
// FusedLocationProviderClient - Main class for receiving location updates.
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient

// LocationRequest - Requirements for the location updates, i.e., how often you
// should receive updates, the priority, etc.
private lateinit var locationRequest: LocationRequest

// LocationCallback - Called when FusedLocationProviderClient has a new Location.
private lateinit var locationCallback: LocationCallback

// Used only for local storage of the last known location. Usually, this would be saved to your
// database, but because this is a simplified sample without a full database, we only need the
// last location to create a Notification if the user navigates away from the app.
private var currentLocation: Location? = null

FusedLocationProviderClient の初期化を確認する

base モジュールで、ForegroundOnlyLocationService.kt ファイル内の TODO: Step 1.2, Review the FusedLocationProviderClient を検索します。コードは次のようになります。

// TODO: Step 1.2, Review the FusedLocationProviderClient.
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)

前のコメントで説明したように、これは位置情報の更新を取得するためのメインクラスです。変数はすでに初期化されていますが、コードを確認して初期化の方法を理解することが重要です。位置情報の更新をリクエストするコードは、後でここに追加します。

LocationRequest を初期化する

  1. base モジュールで、ForegroundOnlyLocationService.kt ファイル内の TODO: Step 1.3, Create a LocationRequest を検索します。
  2. コメントの後に次のコードを追加します。

LocationRequest 初期化コードは、リクエストに必要な追加のサービス品質パラメータ(間隔、最大待機時間、優先度)を追加します。

// TODO: Step 1.3, Create a LocationRequest.
locationRequest = LocationRequest.create().apply {
   // Sets the desired interval for active location updates. This interval is inexact. You
   // may not receive updates at all if no location sources are available, or you may
   // receive them less frequently than requested. You may also receive updates more
   // frequently than requested if other applications are requesting location at a more
   // frequent interval.
   //
   // IMPORTANT NOTE: Apps running on Android 8.0 and higher devices (regardless of
   // targetSdkVersion) may receive updates less frequently than this interval when the app
   // is no longer in the foreground.
   interval = TimeUnit.SECONDS.toMillis(60)

   // Sets the fastest rate for active location updates. This interval is exact, and your
   // application will never receive updates more frequently than this value.
   fastestInterval = TimeUnit.SECONDS.toMillis(30)

   // Sets the maximum time when batched location updates are delivered. Updates may be
   // delivered sooner than this interval.
   maxWaitTime = TimeUnit.MINUTES.toMillis(2)

   priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
  1. コメントを読んで、それぞれの仕組みを理解します。

LocationCallback を初期化する

  1. base モジュールで、ForegroundOnlyLocationService.kt ファイル内の TODO: Step 1.4, Initialize the LocationCallback を検索します。
  2. コメントの後に次のコードを追加します。
// TODO: Step 1.4, Initialize the LocationCallback.
locationCallback = object : LocationCallback() {
    override fun onLocationResult(locationResult: LocationResult) {
        super.onLocationResult(locationResult)

        // Normally, you want to save a new location to a database. We are simplifying
        // things a bit and just saving it as a local variable, as we only need it again
        // if a Notification is created (when the user navigates away from app).
        currentLocation = locationResult.lastLocation

        // Notify our Activity that a new location was added. Again, if this was a
        // production app, the Activity would be listening for changes to a database
        // with new locations, but we are simplifying things a bit to focus on just
        // learning the location side of things.
        val intent = Intent(ACTION_FOREGROUND_ONLY_LOCATION_BROADCAST)
        intent.putExtra(EXTRA_LOCATION, currentLocation)
        LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent)

        // Updates notification content if this service is running as a foreground
        // service.
        if (serviceRunningInForeground) {
            notificationManager.notify(
                NOTIFICATION_ID,
                generateNotification(currentLocation))
        }
    }
}

ここで作成する LocationCallback は、新しい位置情報の更新が利用可能になったときに FusedLocationProviderClient が呼び出すコールバックです。

コールバックでは、まず LocationResult オブジェクトを使用して最新の位置情報を取得します。その後、ローカル ブロードキャスト(有効な場合)を使用して新しい位置情報を Activity に通知するか、このサービスがフォアグラウンド Service として実行されている場合は Notification を更新します。

  1. コメントすべてを読んで、各パートの役割を理解します。

位置情報の変更をサブスクライブする

すべてを初期化したので、更新を受け取ることを FusedLocationProviderClient に通知する必要があります。

  1. base モジュールで、ForegroundOnlyLocationService.kt ファイル内の Step 1.5, Subscribe to location changes を検索します。
  2. コメントの後に次のコードを追加します。
// TODO: Step 1.5, Subscribe to location changes.
fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())

requestLocationUpdates() 呼び出しにより、位置情報の更新を受け取ることを FusedLocationProviderClient に通知します。

以前に定義した LocationRequestLocationCallback が認識されているはずです。これにより、FusedLocationProviderClient はリクエストのサービス品質パラメータと、更新があった場合に呼び出すべきものを認識します。最後に、Looper オブジェクトはコールバックのスレッドを指定します。

また、このコードは try/catch ステートメント内にあります。このメソッドにはこのようなブロックが必要です。アプリに位置情報へのアクセス権限がない場合に SecurityException が発生するためです。

地域の変更の登録を解除する

アプリが位置情報へのアクセスを必要としなくなった場合は、位置情報の更新の登録を解除することが重要です。

  1. base モジュールで、ForegroundOnlyLocationService.kt ファイル内の TODO: Step 1.6, Unsubscribe to location changes を検索します。
  2. コメントの後に次のコードを追加します。
// TODO: Step 1.6, Unsubscribe to location changes.
val removeTask = fusedLocationProviderClient.removeLocationUpdates(locationCallback)
removeTask.addOnCompleteListener { task ->
   if (task.isSuccessful) {
       Log.d(TAG, "Location Callback removed.")
       stopSelf()
   } else {
       Log.d(TAG, "Failed to remove Location Callback.")
   }
}

removeLocationUpdates() メソッドは、LocationCallback の位置情報の更新を受け取らないように FusedLocationProviderClient に通知するタスクを設定します。addOnCompleteListener() は完了のコールバックを提供し、Task を実行します。

前のステップと同様に、このコードが try/catch ステートメント内にあることに気づいたかもしれません。このメソッドにはこのようなブロックが必要です。アプリに位置情報へのアクセス権限がない場合、SecurityException が発生するためです。

subscribe/unsubscribe コードを含むメソッドがいつ呼び出されるのか疑問に思うかもしれません。これらは、ユーザーがボタンをタップしたときにメインクラスでトリガーされます。確認する場合は、MainActivity.kt クラスをご覧ください。

アプリを実行する

Android Studio からアプリを実行し、位置情報ボタンを試します。

出力画面に位置情報が表示されます。これは Android 9 向けの完全に機能するアプリです。

2ae45c4e297e3681.png

d66089bfb532e993.png

5. Android 10 のサポート

このセクションでは、Android 10 のサポートを追加します。

アプリはすでに位置情報の変更をサブスクライブしているため、作業はそれほど多くありません。

実際には、フォアグラウンド サービスが位置情報目的で使用されることを指定するだけで済みます。

ターゲット SDK 29

  1. base モジュールで、build.gradle ファイル内の TODO: Step 2.1, Target Android 10 and then Android 11. を検索します。
  2. 次のように変更します。
  3. targetSdkVersion29 に設定します。

コードは次のようになります。

android {
   // TODO: Step 2.1, Target Android 10 and then Android 11.
   compileSdkVersion 29
   defaultConfig {
       applicationId "com.example.android.whileinuselocation"
       minSdkVersion 26
       targetSdkVersion 29
       versionCode 1
       versionName "1.0"
   }
...
}

この操作を行うと、プロジェクトの同期を求められます。[Sync Now] をクリックします。

153f70847e0ec320.png

これで、アプリは Android 10 への対応がほぼ完了しました。

フォアグラウンド サービスのタイプを追加

Android 10 では、使用中の位置情報へのアクセスが必要な場合、フォアグラウンド サービスのタイプを含める必要があります。この場合、位置情報を取得するために使用されています。

base モジュールで、AndroidManifest.xmlTODO: 2.2, Add foreground service type を検索し、次のコードを <service> 要素に追加します。

android:foregroundServiceType="location"

コードは次のようになります。

<application>
   ...

   <!-- Foreground services in Android 10+ require type. -->
   <!-- TODO: 2.2, Add foreground service type. -->
   <service
       android:name="com.example.android.whileinuselocation.ForegroundOnlyLocationService"
       android:enabled="true"
       android:exported="false"
       android:foregroundServiceType="location" />
</application>

これで、アプリは、Android の位置情報に関するベスト プラクティスに沿って、Android 10 の「使用中」の位置情報をサポートしています。

アプリを実行する

Android Studio からアプリを実行し、位置情報ボタンを試します。

すべてが以前と同様に動作しますが、Android 10 で動作するようになります。以前に位置情報の権限を許可していない場合は、権限画面が表示されます。

6a1029175b467c77.png

c7c1d226e49a121.png

39a262b66a275f66.png

6. Android 11 をサポート

このセクションでは、Android 11 をターゲットにします。

build.gradle ファイル以外のファイルを変更する必要はありません。

対象 SDK 11

  1. base モジュールで、build.gradle ファイル内の TODO: Step 2.1, Target SDK を検索します。
  2. 次のように変更します。
  3. compileSdkVersion から 30
  4. targetSdkVersion から 30

コードは次のようになります。

android {
   TODO: Step 2.1, Target Android 10 and then Android 11.
   compileSdkVersion 30
   defaultConfig {
       applicationId "com.example.android.whileinuselocation"
       minSdkVersion 26
       targetSdkVersion 30
       versionCode 1
       versionName "1.0"
   }
...
}

この操作を行うと、プロジェクトの同期を求められます。[Sync Now] をクリックします。

153f70847e0ec320.png

これで、アプリは Android 11 に対応できました。

アプリを実行する

Android Studio からアプリを実行し、ボタンをクリックしてみます。

すべてが以前と同じように動作しますが、Android 11 で動作するようになります。以前に位置情報の権限を許可していない場合は、権限画面が表示されます。

73d8cc88c5877c25.png

cc98fac6e089bc4.png

7. Android の位置情報に関する戦略

この Codelab に記載されている方法に沿って位置情報パーミッションをチェック、リクエストすることで、デバイス位置情報に関するアクセス権限レベルをアプリが正しく把握できるようになります。

このページでは、位置情報権限に関連する重要なベスト プラクティスをいくつか紹介します。ユーザーのデータを安全に保つ方法については、アプリの権限に関するおすすめの方法をご覧ください。

必要なアクセス権限だけをリクエストする

必要なアクセス権限だけをリクエストするようにしてください。次に例を示します。

  • どうしても必要な場合を除き、アプリの起動時には位置情報アクセス権限をリクエストしないでください。
  • アプリが Android 10 以降をターゲットとしており、フォアグラウンド サービスがある場合は、マニフェストで "location"foregroundServiceType を宣言します。
  • ユーザーの位置情報へのより安全で透明性の高いアクセスで説明されている有効なユースケースがない限り、バックグラウンドでの位置情報の利用許可をリクエストしないでください。

アクセス権限が付与されない場合はグレースフル デグラデーションをサポートする

優れたユーザー エクスペリエンスを維持するために、次の状況を適切に処理できるようにアプリを設計してください。

  • アプリが位置情報にアクセスできない。
  • アプリがバックグラウンドで稼働しているときは位置情報にアクセスできない。

8. 完了

ベスト プラクティスを念頭に置いて、Android で位置情報の更新データを受信する方法を学習しました。

詳細