본문 바로가기
Mobile Programming/Android

[Kotlin] 구글 지도로 현재 위치 나타내고 위경도 출력하기

by 푸고배 2020. 8. 7.

GPS 기능을 이용해서 구글지도에 현재 사용자의 위치를 표현해주는 어플을 만들어본다.


1. Gradle 의존성 추가


구글 지도를 사용하기 위해 프로젝트 생성시 기본 액티비티를 Google Maps Activity로 설정해준다.

먼저 Google Map 사용을 위해 아래와 같이 Gradle Scripts>build.gradle(Module:app)의 dependencies에 의존성을 추가해준다.


// 위치 정보
implementation 'com.google.android.gms:play-services-location:17.0.0'
// 구글 지도, MapsActivity 추가 시 자동으로 추가됨
implementation 'com.google.android.gms:play-services-maps:17.0.0'


개발의 편의성을 위해 Anko 라이브러리도 추가해준다.


implementation 'org.jetbrains.anko:anko:0.10.5'


2. Google Map API 키 발급


res>values 폴더의 google_maps_api.xml에 주석처리 되어있는 https://console.developers.google.com/... 로 이동한다.

다음 화면에서 프로젝트 만들기를 누른 후 API키 발급 버튼을 눌러 API키를 발급 받는다.

발급받은 키를 복사해 google_maps_api.xml의 아래 코드 부분에 붙여넣는다.


<string name="google_maps_key" templateMergeStrategy="preserve" translatable="false">발급받은 키 입력</string>


빌드 해보면 Google Maps Activity의 default 예시인 시드니에 마커가 찍힌 Goggle Map이 정상적으로 로드된다.

지정된 위치가 아닌 실시간 사용자의 위치를 얻어오기 위해서는 별도의 권한 등록이 추가적으로 필요하다.


3. AndroidManifest.xml에 권한 등록


휴대폰 현재 위치를 조회하기 위해서는 manifests>AndroidManifes.xml에서 위치 조회에 대한 권한을 등록해야한다. 

Google Maps Activity로 설정하여 프로젝트를 생성한 경우 자동으로 아래의 코드가 추가되어 있지만 없는 경우 직접 추가해준다.


<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />


4. MapsActivity 코드 수정하기


먼저 위치 정보를 얻기위한 초기화를 해준다.


private lateinit var fuseLocationProvicerClient:FusedLocationProviderClient
private var locationRequest = LocationRequest()
private var locationCallback = MyLocationCallBack()

private val REQUEST_ACCESS_FINE_LOCATION = 1000

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_maps)
// SupportMapFragment를 가져와서 지도가 준비되면 알림을 받습니다.
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)

locationInit()
}
private fun locationInit(){
fuseLocationProvicerClient = FusedLocationProviderClient(this)

locationCallback = MyLocationCallBack()

locationRequest = LocationRequest()
// GPS 우선
locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
locationRequest.interval = 10000
// 정확함. 이것보다 짧은 업데이트는 하지 않음
locationRequest.fastestInterval = 5000
}


위치 정보를 주기적으로 요청하는 코드는 액티비티가 화면에 보일 때만 수행하는 것이 좋으므로 onResum() 메서드에 위치 정보 요청을 수행하며, onPause() 메서드를 이용해 위치 정보 요청을 삭제한다.

또한, 사용자가 위치 제공에 동의했는지에 따라 동작 여부가 나뉜다.


private fun showPermissionInfoDialog(){
alert("현재 위치 정보를 얻으려면 위치 권한이 필요합니다", "권한이 필요한 이유"){
yesButton {
// 권한 요청
ActivityCompat.requestPermissions(this@MapsActivity,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_ACCESS_FINE_LOCATION)
}
noButton { }
}.show()
}

private fun permissionCheck(cancel:()->Unit, ok:()->Unit){
// 위치 권한이 있는지 검사
if(ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)!=PackageManager.PERMISSION_GRANTED){
// 권한이 허용되지 않음
if(ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)){
// 이전에 권한을 한 번 거부한 적이 있는 경우에 실행할 함수
cancel()
} else{
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_ACCESS_FINE_LOCATION)
}
} else {
// 권한을 수락했을 때 실행할 함수
ok()
}
}

// 사용자가 권한을 수락하거나 거부했을 때
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when(requestCode){
REQUEST_ACCESS_FINE_LOCATION->{
if((grantResults.isNotEmpty()
&& grantResults[0] == PackageManager.PERMISSION_GRANTED)){
// 권한 허용됨
addLocationListener()
} else {
// 권한 거부
toast("권한 거부 됨")
}
return
}
}
}


퍼미션 확인 함수를 통해 위치 권한이 동의되어 있는지 확인 후 허용되지 않았으면 권한 거부됨이라는 Toast 메세지를 띄우고, 허용되어있다면 현재 위치를 업데이트 시키는 함수(addLocationListener)를 호출한다.


@SuppressLint("MissingPermission")
private fun addLocationListener(){
fuseLocationProvicerClient.requestLocationUpdates(locationRequest,
locationCallback, null)
}


현재 위치를 업데이트 시키는 함수는 @SuppressLint("MissingPermission)를 통해 권한을 검사 후 locationCallback을 부른다. 위에서 선언한 locationCallback은 MyLocationCallBack의 인스턴스이며 LocationRequest()는 아래와 같이 구현되어 있다.


inner class MyLocationCallBack:LocationCallback(){
override fun onLocationResult(locationRequest: LocationResult?) {
super.onLocationResult(locationRequest)

val location = locationRequest?.lastLocation

location?.run{
// 14 level로 확대하고 현재 위치로 카메라 이동
val latLng = LatLng(latitude, longitude)
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 17f))

Log.d("MapsActivity", "위도: $latitude, 경도: $longitude")
}
}
}


loactionRequest?.lastLocation은 마지막으로 알려진 위치를 가져오며, 스레드를 이용하여 location의 위경도를 가져와 mMap.animateCamera 함수를 통해 지도의 중심점을 해당 위치로 옮긴다. 정확한 위경도 값을 알기 위해 Log를 찍어볼 수도 있다.


override fun onPause() {
super.onPause()
removeLocationListener()
}
private fun removeLocationListener(){
// 현재 위치 요청을 삭제
fuseLocationProvicerClient.removeLocationUpdates(locationCallback)
}


위의 함수를 이용해 액티비티가 가려질 시 LocationListenser를 삭제할 수 있다.


참고 자료 :  오준석의 안드로이드 생존코딩(코틀린 편)

반응형

댓글