Kotlin으로 Android에서 위치 업데이트 받기

Android 10 및 11은 사용자가 앱 위치에 대한 더 많은 제어 권한을 제공합니다.

Android 11에서 실행되는 앱에서 위치 액세스 권한을 요청하면 사용자는 다음 네 가지 옵션을 선택할 수 있습니다.

  • 항상 허용
  • 앱 사용 중에만 허용 (Android 10에서)
  • 한 번만 (Android 11)
  • 거부

Android 10

Android 11

이 Codelab에서는 위치 업데이트를 받는 방법과 모든 Android 버전, 특히 Android 10 및 11에서 위치를 지원하는 방법을 알아봅니다. Codelab을 마치면 위치 업데이트를 가져올 수 있는 현재 권장사항을 따르는 앱이 제공될 것입니다.

기본 요건

실행할 작업

  • Android 내 위치 권장사항을 따릅니다.
  • 포그라운드 위치 정보 액세스 권한 처리 (앱을 사용하는 동안 사용자가 앱에서 기기 위치에 액세스하도록 요청하는 경우)
  • 위치 구독 및 구독 취소 코드를 추가하여 위치 액세스 요청에 대한 지원을 추가하도록 기존 앱을 수정합니다.
  • 포그라운드 위치의 사용 또는 사용 중에 액세스하는 로직을 추가하여 Android 10 및 11용 앱에 지원을 추가합니다.

필요한 항목

  • Android 스튜디오 3.4 이상에서 코드 실행
  • Android 10 및 11의 개발자 프리뷰를 실행하는 기기/에뮬레이터

시작 프로젝트 저장소 클론

이 시작 프로젝트를 사용하면 빌드를 빠르게 시작할 수 있습니다. Git이 설치되어 있다면 다음 명령어를 실행하면 됩니다.

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

언제든지 GitHub 페이지를 방문할 수 있습니다.

Git이 없는 경우 프로젝트를 ZIP 파일로 가져올 수 있습니다.

ZIP 파일 다운로드

프로젝트 가져오기

Android 스튜디오를 열고 시작 화면에서 "Open an existing Android Studio project"를 선택하고 프로젝트 디렉터리를 엽니다.

프로젝트가 로드된 후 git이 일부 로컬 변경사항을 추적하지 않는다는 알림이 표시될 수도 있습니다. 무시를 클릭할 수 있습니다. 변경사항이 Git 저장소로 다시 푸시되지 않습니다.

프로젝트 창의 왼쪽 상단 모서리에 있는 Android 뷰는 아래와 같은 이미지로 표시됩니다. Project 뷰에서는 동일한 내용을 보려면 프로젝트를 확장해야 합니다.

두 개의 폴더(basecomplete)가 있습니다. 이들을 각각 '모듈'이라고 합니다.

Android 스튜디오에서 처음으로 프로젝트를 백그라운드에서 컴파일할 때는 몇 초 정도 걸릴 수 있습니다. 그동안 Android 스튜디오 하단의 상태 표시줄에 다음 메시지가 표시됩니다.

Android 스튜디오가 프로젝트 색인 생성 및 프로젝트 빌드를 마칠 때까지 기다린 다음 코드를 변경합니다. 그러면 Android 스튜디오에서 필요한 모든 구성요소를 가져올 수 있습니다.

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을 클릭합니다.


  1. 기기에 다음 앱이 표시됩니다.


출력 화면에 위치 정보가 표시되지 않을 수 있습니다. 아직 위치 코드를 추가하지 않았기 때문입니다.

개념

이 Codelab의 핵심은 위치 업데이트 수신 방법과 최종적으로 Android 10과 Android 11을 지원하는 방법을 설명하는 것입니다.

그러나 코딩을 시작하기 전에 기본사항을 검토하는 것이 좋습니다.

위치 정보 액세스 유형

Codelab을 시작할 때부터 위치 액세스의 네 가지 옵션을 기억할 수 있습니다. 무슨 의미인지 살펴보세요.

  • 앱 사용 중에만 허용
  • 이 옵션은 대부분의 앱에 권장되는 옵션입니다. '사용 중'인 액세스 권한 또는 '포그라운드 전용' 액세스라고도 하는 이 옵션은 Android 10에 추가되었으며, 앱이 활발하게 사용되는 중에만 개발자가 위치를 검색할 수 있습니다. 다음 중 하나에 해당하면 앱이 활성 상태로 간주됩니다.
  • 활동이 표시됩니다.
  • 포그라운드 알림이 지속적으로 실행 중입니다.
  • 한 번만
  • Android 11에 추가된 기능은 앱 사용 중에만 허용과 동일하지만 제한된 시간 동안만 적용됩니다. 자세한 내용은 일회성 권한을 참조하세요.
  • 거부
  • 이 옵션은 위치 정보에 대한 액세스를 차단합니다.
  • 항상 허용
  • 이 옵션을 사용하면 위치에 항상 액세스할 수 있지만 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
  • 기기 위치가 변경되었거나 더 이상 확인되지 않는 경우 알림을 받는 데 사용됩니다. 데이터베이스에 저장할 Location를 가져올 수 있는 LocationResult이 전달됩니다.

이제 기본적인 작업을 알았으니 코드를 시작해 보세요.

이 Codelab에서는 가장 일반적인 위치 옵션인 앱 사용 중에만 허용에 중점을 둡니다.

위치 업데이트를 수신하려면 앱에 눈에 띄는 활동 또는 포그라운드에서 실행되는 서비스 (알림 포함)가 있어야 합니다.

권한

이 Codelab의 목적은 위치 정보 액세스 권한을 요청하는 방법이 아닌 위치 업데이트를 받는 방법을 보여주기 위한 것이므로 권한 기반 코드가 이미 작성되어 있습니다. 이미 알고 있다면 건너뛰어도 됩니다.

다음은 주요 액세스 권한입니다. 이 부분에 대해 별도로 취해야 할 조치는 없습니다.

  1. AndroidManifest.xml에서 사용하는 권한을 선언합니다.
  2. 위치 정보에 액세스하기 전에 사용자가 앱에 액세스 권한을 부여했는지 확인하세요. 앱에서 아직 권한을 받지 않았다면 액세스 권한을 요청합니다.
  3. 사용자가 선택한 권한 처리 MainActivity.kt에서 이 코드를 확인할 수 있습니다.

AndroidManifest.xml 또는 MainActivity.kt에서 TODO: 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 스튜디오에서 앱을 실행하고 위치 버튼을 사용해 봅니다.

출력 화면에 위치 정보가 표시됩니다. 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.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의 위치 권장사항을 따라 '사용하는 동안'에 Android 10 위치를 지원합니다.

실행

Android 스튜디오에서 앱을 실행하고 위치 버튼을 사용해 봅니다.

모든 것이 이전처럼 작동하지만 이제 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 스튜디오에서 앱을 실행하고 버튼을 클릭합니다.

모든 것이 이전처럼 작동했지만 이제 Android 11에서도 작동합니다. 이전에 위치에 대한 권한을 수락하지 않았다면 이제 권한 화면이 표시됩니다.

이 Codelab에 표시된 방식으로 위치 정보 액세스 권한을 확인하고 요청하면 앱이 기기 위치와 관련된 액세스 수준을 성공적으로 추적할 수 있습니다.

이 페이지에는 위치 정보 액세스 권한과 관련된 몇 가지 주요 권장사항이 나와 있습니다. 사용자 데이터를 안전하게 보호하는 방법을 자세히 알아보려면 앱 권한 권장사항을 참고하세요.

필요한 권한만 요청

필요할 때만 권한을 요청하세요. 예를 들면 다음과 같습니다.

  • 꼭 필요하지 않은 경우 앱 시작 시 위치 정보 액세스 권한을 요청하지 마세요.
  • 앱이 Android 10 이상을 타겟팅하고 포그라운드 서비스가 있다면 매니페스트에서 "location"foregroundServiceType를 선언합니다.
  • 더 안전하고 투명한 사용자 위치 액세스에 설명된 유효한 사용 사례가 없는 경우 백그라운드 위치 정보 액세스 권한을 요청하지 마세요.

권한이 부여되지 않을 경우 단계적 성능 저하 지원

높은 사용자 경험을 유지하려면 다음과 같은 상황을 적절하게 처리할 수 있도록 앱을 설계하세요.

  • 앱에 위치 정보에 대한 액세스 권한이 없는 경우
  • 앱이 백그라운드에서 실행될 때 위치 정보에 액세스할 수 없습니다.

권장사항에 유의하면서 Android에서 위치 업데이트를 받는 방법을 알아보았습니다.

자세히 알아보기