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

1. 시작하기 전에

Android 10과 11에서는 사용자가 앱의 제어 기능을 더 세밀하게 제어할 수 있습니다. 기기 위치에 액세스할 수 없습니다.

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

  • 항상 허용
  • 앱 사용 중에만 허용 (Android 10)
  • 일회성 (Android 11)
  • 거부

Android 10

6a1029175b467c77.png

Android 11

73d8cc88c5877c25.png

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

기본 요건

실행할 작업

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

필요한 항목

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

2. 시작하기

시작 프로젝트 저장소 클론

이 시작 프로젝트를 빌드하면 최대한 빨리 시작할 수 있습니다. Git을 설치했다면 다음 명령어를 실행하면 됩니다.

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

언제든지 GitHub 페이지를 직접 방문하세요.

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

프로젝트 가져오기

Android 스튜디오를 열고 'Open an existing Android Studio project'를 선택합니다. 프로젝트 디렉터리를 엽니다.

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

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

fa825dae96c5dc18.png

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

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

c2273e7835c0841a.png

코드를 변경하기 전에 Android 스튜디오에서 색인 생성과 프로젝트 빌드를 완료할 때까지 기다립니다. 그러면 Android 스튜디오에서 필요한 모든 구성요소를 가져올 수 있습니다.

Reload for language changes to apply? 또는 이와 유사한 메시지가 표시되면 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의 시작 부분에서 다룬 위치 액세스의 네 가지 옵션을 기억할 것입니다. 각 단어가 의미하는 바를 살펴보세요.

  • 앱 사용 중에만 허용
  • 이 옵션은 대부분의 앱에 권장되는 옵션입니다. '사용 중'이라고도 함 또는 '포그라운드 전용' 액세스를 지원하는 경우 이 옵션은 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이 전달됩니다.

이제 수행할 작업에 관한 기본 사항을 이해했으므로 코드를 시작해 보겠습니다.

4. 위치 기능 추가

이 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.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이 발생하기 때문입니다.

구독/구독 취소 코드가 포함된 메서드가 호출되는 시점이 궁금할 수 있습니다. 사용자가 버튼을 탭하면 기본 클래스에서 트리거됩니다. 확인하려면 MainActivity.kt 클래스를 살펴보세요.

app 실행

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

출력 화면에 위치 정보가 표시됩니다. 이 앱은 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 스튜디오에서 앱을 실행하고 위치 버튼을 사용해 봅니다.

모든 것이 이전과 동일하게 작동하지만 이제 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 스튜디오에서 앱을 실행하고 버튼을 클릭해 보세요.

모든 것이 이전과 동일하게 작동하지만 이제 Android 11에서도 작동합니다. 이전에 위치에 대한 권한을 허용하지 않은 경우 이제 권한 화면이 표시됩니다.

73d8cc88c5877c25.png

cc98fac6e089bc4.png

7. Android를 위한 위치 전략

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

이 페이지에는 위치 정보 액세스 권한과 관련된 몇 가지 주요 권장사항이 나열되어 있습니다. 사용자의 로그인 정보를 앱 권한 권장사항을 참고하세요.

필요한 권한만 요청

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

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

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

우수한 사용자 환경을 유지하려면 다음 상황을 적절하게 처리할 수 있도록 앱을 설계하세요.

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

8. 축하합니다

권장사항을 염두에 두고 Android에서 위치 업데이트를 수신하는 방법을 알아봤습니다.

자세히 알아보기