Android で Kotlin を使用して現在地の更新情報を受け取る

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

Android 11 で稼働しているアプリが位置情報へのアクセスをリクエストする場合、ユーザーは次の 4 つの選択肢があります。

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

Android 10

Android 11

この Codelab では、位置情報の更新データを受信する方法と、Android(Android 10 および 11)の任意のバージョンで位置情報をサポートする方法を学びます。この Codelab を完了すると、最新の位置情報を取得するための現在のベスト プラクティスに従うアプリを作成できます。

前提条件

演習内容

  • Android での位置情報に関するおすすめの方法を実施します。
  • フォアグラウンドでの位置情報の利用許可を処理する(アプリの使用中に、アプリがデバイスの位置情報にアクセスすることをユーザーがリクエストした場合)。
  • 位置情報へのアクセスをリクエストするときのサポートを追加するよう、既存のアプリを修正します。位置情報を登録または登録解除するコードを追加します。
  • フォアグラウンドまたは使用中の位置情報にアクセスするロジックを追加して、Android 10 および 11 のアプリにサポートを追加します。

必要なもの

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

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

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

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

GitHub ページに直接アクセスしてください。

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

ZIP をダウンロード

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

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

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

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

フォルダは 2 つあります(basecomplete)。これらは「モジュール」と呼ばれます。

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

Android Studio でプロジェクトのインデックス登録とビルドが完了するまで待ってから、コードを変更します。これにより、Android Studio で必要なすべてのコンポーネントを取得できます。

言語の変更を反映するには再読み込みしますか?」というメッセージが表示された場合は、[はい] を選択します。

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

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

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

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

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

Android Emulator の設定については、エミュレータで実行するをご覧ください。

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

アプリを実行します。

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


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


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

コンセプト

この 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 権限を宣言して、ユーザーの権限をリクエストする必要があります。これらの権限がないと、アプリは実行時に位置情報へのアクセスをリクエストできません。

これらの権限は、[1 回限り] と [アプリの使用中のみ許可] に適用されます。Android 10 以降を搭載したデバイスでアプリを使用する場合に限って有効です。

場所

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

主なクラスを見てみましょう。

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

基本的な操作を学習したところで、次はコードの作成を始めましょう。

この 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().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)

       if (locationResult?.lastLocation != null) {

           // 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 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))
           }
       } else {
           Log.d(TAG, "Location information isn't available.")
       }
   }
}

ここで作成する 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.myLooper())

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 が発生するため、このようなブロックが必要になります。

サブスクライブ/登録解除コードを含むメソッドが呼び出されるタイミングが気になるかもしれません。ユーザーがボタンをタップすると、メインクラスで呼び出されます。詳しくは、MainActivity.kt クラスをご覧ください。

アプリの実行

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

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

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

アプリが位置情報の変更をすでにサブスクライブしているので、行う必要がある作業はほとんどありません。

フォアグラウンド サービスを位置情報に使用することを指定するだけです。

ターゲット SDK 29

  1. base モジュールの build.gradle ファイルで TODO: Step 2.1, Target SDK 10 を検索します。
  2. 次のように変更します。
  1. compileSdkVersion29 に設定します。
  2. buildToolsVersion"29.0.3" に設定します。
  3. targetSdkVersion29 に設定します。

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

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

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

その後、アプリは 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 で動作するようになりました。以前にビジネス情報の権限を承認しなかった場合は、権限画面が表示されます。

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

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

ターゲット SDK R

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

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

android {
   // TODO: Step 2.1, Target Android 10.
   compileSdkVersion "android-R"
   buildToolsVersion "29.0.2"
   defaultConfig {
       applicationId "com.example.android.whileinuselocation"
       minSdkVersion 26
       targetSdkVersion "R"
       versionCode 1
       versionName "1.0"
   }
...
}

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

その後、アプリを Android 11 に対応させることができるようになります。

アプリの実行

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

動作はすべて以前と同じですが、Android 11 でも機能するようになりました。以前にビジネス情報の権限を承認しなかった場合は、権限画面が表示されます。

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

このページでは、位置情報の利用許可に関する重要事項をいくつかご紹介します。ユーザーデータを安全に保つ方法について詳しくは、アプリの権限に関するおすすめの設定をご覧ください。

必要な権限のみを求める

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

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

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

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

  • アプリが位置情報にアクセスできない。
  • バックグラウンドで動作しているアプリは位置情報にアクセスできません。

Android で最新の位置情報を受け取る方法について学習しました。

詳細