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

1. 始める前に

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

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

  • 常に許可
  • アプリの使用中のみ許可(Android 10)
  • 今回のみ(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 構成をプルダウン メニューから選択し、[Run] をクリックします。

99600e9d44527ab.png

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

99bf1dae46f99af3.png

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

3. 位置情報を追加する

コンセプト

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

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

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

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

  • アプリの使用中のみ許可
  • このオプションは、ほとんどのアプリにおすすめです。「使用中のみ」または「フォアグラウンドのみ」のアクセスとも呼ばれるこのオプションは、Android 10 で追加されました。これにより、アプリがアクティブに使用されている場合にのみ、位置情報を取得できます。次のいずれかに当てはまる場合、アプリはアクティブであると見なされます。
  • アクティビティが表示されている。
  • フォアグラウンド サービスが進行中の通知とともに実行されている。
  • 今回のみ
  • 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 以降を搭載したデバイスでアプリを使用する場合の [今回のみ] と [アプリの使用中のみ許可] のケースを対象としています。

ロケーション

アプリは、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` を検索します。

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

// 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() メソッドは、FusedLocationProviderClientLocationCallback の位置情報の更新を受け取らないことを通知するタスクを設定します。addOnCompleteListener() は完了のコールバックを提供し、Task を実行します。

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

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

app を実行する

Android Studio からアプリを実行し、位置情報ボタンを試してください。

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

2ae45c4e297e3681.pngd66089bfb532e993.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 モジュールで、TODO: 2.2, Add foreground service typeAndroidManifest.xml を検索し、次のコードを <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 の「使用中」の位置情報をサポートします。

app を実行する

Android Studio からアプリを実行し、位置情報ボタンを試してください。

以前と同じように動作しますが、Android 10 で動作するようになりました。以前に位置情報へのアクセス権限を承認していない場合は、権限画面が表示されます。

6a1029175b467c77.pngc7c1d226e49a121.png39a262b66a275f66.png

6. Android 11 のサポート

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

嬉しいお知らせです。build.gradle ファイル以外のファイルを変更する必要はありません。

ターゲット SDK 11

  1. base モジュールで、build.gradle ファイルの TODO: Step 2.1, Target SDK を検索します。
  2. 次のように変更します。
  3. compileSdkVersion30 に設定します。
  4. targetSdkVersion30 に設定します。

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

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 に対応できました。

app を実行する

Android Studio からアプリを実行し、ボタンをクリックしてみてください。

以前と同じように動作しますが、Android 11 で動作するようになりました。以前に位置情報へのアクセス権限を承認していない場合は、権限画面が表示されます。

73d8cc88c5877c25.pngcc98fac6e089bc4.png

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

この Codelab で説明した方法で位置情報へのアクセス権限を確認してリクエストすることで、デバイスの位置情報に関するアクセス権限レベルをアプリが正しく把握できるようになります。

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

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

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

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

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

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

8. 完了

ベスト プラクティスに留意しながら、Android で位置情報の更新を受け取る方法を学習しました。

詳細