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 構成を選択し、[Run] をクリックします。

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 では、最も一般的な位置情報オプションである [Allow only while using the app] に焦点を当てます。

位置情報の更新を受信するには、アプリに可視アクティビティがあるか、フォアグラウンドで実行されている(通知付きの)サービスが必要です。

権限

この 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 初期化コードにより、リクエストに必要な追加の Quality of Service パラメータ(間隔、最大待機時間、優先度)が追加されます。

// 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 はリクエストの Quality of Service のパラメータと、更新時に何を呼び出す必要があるかを把握できます。最後に、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 が発生するため、このメソッドではこのようなブロックが必要になります。

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

app を実行する

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.xml 内で TODO: 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 10 の「使用中」の位置情報をサポートしていますベストプラクティスに従って 位置情報を最適化できます

app を実行する

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 に対応させる準備が整います。

app を実行する

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

すべてが以前と同様に動作するはずですが、現在は Android 11 で動作します。以前にビジネス情報の権限を許可しなかった場合は、今度は権限の画面が表示されます。

73d8cc88c5877c25.png

cc98fac6e089bc4.png

7. Android 向けの位置情報戦略

この Codelab で示す方法で位置情報の利用許可を確認してリクエストすることで、アプリはデバイスの位置情報に関するアクセスレベルを適切に追跡できます。

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

必要な権限のみをリクエストする

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

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

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

優れたユーザー エクスペリエンスを維持するには、次の状況に適切に対処できるようにアプリを設計します。

  • アプリが位置情報にアクセスできない。
  • バックグラウンドで実行中のアプリは位置情報にアクセスできません。

8. 完了

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

詳細