티스토리 뷰
앱개발 심화 1주차 정리 - 2
사용자 위치 얻기
위치접근권한
- android.permission.ACCESS_COARSE_LOCATION: 와이파이나 모바일 데이터를 사용해 기기의 위치에
접근하는 권한이다. (도시에서 1블록 정도의 오차 수준)
- android.permission.ACCESS_FINE_LOCATION: 위성, 와이파이, 모바일 데이터 등 이용할 수 있는 위치
제공자를 사용해 최대한 정확한 위치에 접근하는 권한입니다.
- android.permission.ACCESS_BACKGROUND_LOCATION: API 레벨 29 이상에서 백그라운드
상태에서 위치에 접근하는 권한입니다.
안드로이드에서 사용자의 위치 정보를 얻으려면 해당 권한을 사용자로부터 획득해야한다.
권한 요청 절차는 사용자의 동의를 얻는 과정을 포함하며, 보안과 사용자의 개인정보 보호를 위해 필수적이다.
매니패스트 설정
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
앱이 위치 정보에 접근하려면, 매니패스트 파일에 위치 권한을 추가해야 한다.
FINE_LOCATION 권한을 추가하려면 COARSE_LOCATION도 함께 추가해줘야 한다.
권한 획득
// 위치 권한 요청
private fun requestLocationPermission() {
if (ContextCompat.checkSelfPermission(
this, ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
// 권한이 없을 경우, 사용자에게 요청
ActivityCompat.requestPermissions(
this,
arrayOf(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION),
LOCATION_PERMISSION_REQUEST_CODE
)
} else {
getLocation() // 권한이 이미 있을 경우, 위치 정보 사용 가능
}
}
// 권한 요청 결과 처리
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
LOCATION_PERMISSION_REQUEST_CODE -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 위치 권한이 허용된 경우에 대한 처리
} else {
// 위치 권한이 거부된 경우에 대한 처리
}
return
}
}
}
private fun getLocation() {} // 위치정보 가져오기
onCreate에 requestLocationPermission을 호출하여 위치 권한을 얻어온다.
사용자 위치 얻어오기
val manager = getSystemService(LOCATION_SERVICE) as LocationManager
사용자의 위치를 얻을 때는 LocationManager라는 시스템 서비스를 이용한다.
- GPS: GPS 위성을 이용합니다.
- Network: 이동 통신망을 이용합니다.
- Wifi: 와이파이를 이용합니다.
- Passive: 다른 앱에서 이용한 마지막 위치 정보를 이용합니다.
위치 제공자에는 이러한 종류들이 있다.
var result = "All Providers : "
val providers = manager.allProviders
for (provider in providers) {
result += " $provider. "
}
Log.d("maptest", result) // All Providers : passive, gps, network..
현재 기기에 어떤 위치 제공자가 있는지를 알아볼 땐 LocationManager의 allProviders 프로퍼티를 이용한다.
result = "Enabled Providers : "
val enabledProviders = manager.getProviders(true)
for (provider in enabledProviders) {
result += " $provider. "
}
Log.d("maptest", result) // Enabled Providers : passive, gps, network..
지금 사용할 수 있는 위치 제공자를 알아볼 땐 getProviders를 이용한다.
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
val location: Location? = manager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
location?.let{
val latitude = location.latitude
val longitude = location.longitude
val accuracy = location.accuracy
val time = location.time
Log.d("map_test", "$latitude, $location, $accuracy, $time")
}
}
위치 정보를 한 번만 가져오려면 getLastKnownLocation을 이용한다.
val listener: LocationListener = object : LocationListener {
override fun onLocationChanged(location: Location) {
Log.d("map_test,","${location.latitude}, ${location.longitude}, ${location.accuracy}")
}
}
manager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 10_000L, 10f, listener)
// (.. 생략 ..) //
manager.removeUpdates(listener)
위치 정보를 계속해서 가져와야 한다면 가져와야 한다면 LocationListener를 이용한다.
특정시간마다 위치정보를 가져오고 이전 위치정보는 삭제하는 방식이다.
구글 Play 서비스의 위치 라이브러리
위치 제공자를 선정할 땐 전력 소비량, 정확도, API 복잡성, 기기 호환성 등을 고려해야한다.
구글에서는 최적의 알고리즘으로 위치 제공자를 지정할 수 있도록 하는
Fused Location Provider라는 라이브러리를 제공한다.
implementation("com.google.android.gms:play-services:12.0.1")
앱 수준 그래들 파일에 해당 구문을 추가한다.
val connectionCallback = object: GoogleApiClient.ConnectionCallbacks{
override fun onConnected(p0: Bundle?) {
// 위치 제공자를 사용할 수 있을 때
}
override fun onConnectionSuspended(p0: Int) {
// 위치 제공자를 사용할 수 없을 때
}
}
val onConnectionFailCallback = object : GoogleApiClient.OnConnectionFailedListener{
override fun onConnectionFailed(p0: ConnectionResult) {
// 사용할 수 있는 위치 제공자가 없을 때
}
}
val apiClient = GoogleApiClient.Builder(this)
.addApi(LocationServices.API)
.addConnectionCallbacks(connectionCallback)
.addOnConnectionFailedListener(onConnectionFailCallback)
.build()
GoogleApiClient의 인터페이스를 구현하여 apiClient를 초기화한다.
val providerClient = LocationServices.getFusedLocationProviderClient(this)
FusedLocationProviderClient 초기화하는 구문이다.
apiClient.connect()
GoogleApiClient 객체에 위치 제공자를 요청하는 구문이다.
override fun onConnected(p0: Bundle?) {
if(ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) === PackageManager.PERMISSION_GRANTED){
providerClient.lastLocation.addOnSuccessListener(
this@MainActivity,
object: OnSuccessListener<Location> {
override fun onSuccess(p0: Location?) {
p0?.let {
val latitude = p0.latitude
val longitude = p0.longitude
Log.d("map_test", "$latitude, $longitude")
}
}
}
)
apiClient.disconnect()
}
}
onConnected에는 권환 확인 후 위치정보를 얻은 뒤, apiClient의 연결을 해제하는 로직이 들어간다.
구글 지도앱 만들기
API키 얻기
구글 클라우드 들어가서 구글 계정으로 로그인 해준다.
Google Maps Platform 선택 후
API 사용 설정을 완료한다.
새 프로젝트 생성 후
사용자 인증 정보 화면으로 들어간다.
그 후 API 키를 생성하고 복사한다.
예제
implementation("com.google.android.gms:play-services-maps:18.1.0")
implementation("com.google.android.gms:play-services-location:21.0.1")
그래들 파일에 해당 종속성들을 추가해준다.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application
...>
<uses-library android:name="org.apache.http.legacy" android:required="true"/>
<meta-data android:name="com.google.android.maps.v2.API_KEY"
android:value="== Google Cloud에서 생성한 API키 입력=="/>
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/>
...
</application>
매니패스트에 위와같은 설정을 추가해준다.
package com.example.practice
import android.Manifest
import android.content.pm.PackageManager
import android.location.Location
import android.os.Bundle
import android.os.Looper
import android.util.Log
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
import com.google.android.gms.location.Priority
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.BitmapDescriptorFactory
import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.Marker
import com.google.android.gms.maps.model.MarkerOptions
class MainActivity : AppCompatActivity(), OnMapReadyCallback {
private var currentMarker: Marker? = null // 현재 마커를 저장할 변수
private lateinit var mGoogleMap: GoogleMap
private lateinit var fusedLocationClient: FusedLocationProviderClient
private lateinit var locationCallback: LocationCallback
private lateinit var locationPermission: ActivityResultLauncher<Array<String>>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 사용자가 모든 권환을 확인했다면 구글지도 표시
locationPermission = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { results ->
if (results.all { it.value }) {
(supportFragmentManager.findFragmentById(R.id.mapView) as SupportMapFragment).getMapAsync(
this
)
} else {
Toast.makeText(this, "권한 승인이 필요합니다.", Toast.LENGTH_LONG).show()
}
}
// 위치 권한 요청
locationPermission.launch(
arrayOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
)
)
}
// 권한이 승인되어 지도 객체를 이용할 수 있을 때
override fun onMapReady(p0: GoogleMap) {
mGoogleMap = p0 // 구글맵 객체 초기화
mGoogleMap.mapType = GoogleMap.MAP_TYPE_NORMAL // 구글 맵 기본 타입은 normal이라 해당 구문 생략 가능
// 서울에 마커표시
val seoul = LatLng(37.566610, 126.978403)
mGoogleMap.apply {
val markerOptions = MarkerOptions()
markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE))
markerOptions.position(seoul)
markerOptions.title("서울시청")
markerOptions.snippet("Tel:01-120") // 부제목
addMarker(markerOptions)
}
// FusedLocationProviderClient 객체 초기화 후 위치 업데이트 설정
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
updateLocation()
}
private fun updateLocation(){
// 1초마다 위치정보 요청을 보냄
val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 1000)
.setMinUpdateIntervalMillis(500).build()
// 변경된 위치 정보 null 체크 후 지도 업데이트
locationCallback = object : LocationCallback(){
override fun onLocationResult(locationResult: LocationResult) {
locationResult.let{
for (location in it.locations){
Log.d("위치정보", "위도: ${location.latitude} 경도: ${location.longitude}")
setMarkerAndCamera(location) // 실시간으로 위치를 받아오기에 맵을 확대해도 다시 줄어든다.
}
}
}
}
//권한이 부여되지 않았으면 위치 위치 업데이트 중지
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
return
}
// 현재 스레드에서 콜백을 호출해 위치 업데이트 요청을 보냄
fusedLocationClient.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.myLooper()!!
)
}
// 현재 위치를 받아와 마커 표시 및 카메라 위치 설정
fun setMarkerAndCamera(lastLocation: Location) {
val location = LatLng(lastLocation.latitude, lastLocation.longitude)
currentMarker?.remove()
val makerOptions = MarkerOptions().position(location).title("현재 위치")
currentMarker = mGoogleMap.addMarker(makerOptions)
val cameraPosition = CameraPosition.Builder().target(location).zoom(15.0f).build()
mGoogleMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition))
}
}
현재 위치를 받아와 지도에 표시하는 예제이다.
강의에서 알려준 코드는 마커가 너무 과도하게 찍혀서
위치정보 갱신 시 기존에 있던 마커를 삭제하는 로직을 추가했다.
'내일배움캠프 > Android 국비지원' 카테고리의 다른 글
TIL 60일차 (Compose 특강 3회차 정리) (0) | 2024.08.12 |
---|---|
TIL 59일차 (앱개발 심화 1주차 정리 - 3) (0) | 2024.08.12 |
TIL 57일차 (앱개발 심화 1주차 정리 - 1) (0) | 2024.08.08 |
TIL 56일차 (챌린지반 6주차 세션 정리) (0) | 2024.08.08 |
TIL 55일차 (Compose 특강 2회차 정리) (0) | 2024.08.08 |