본문 바로가기

Android

사용자 위치 얻기

1. 위치 접근 권한


- android.permission.ACCESS_COARSE_LOCATION
: 와이파이나 모바일 데이터를 사용해 접근

- android.permission.ACCESS_FINE_LOCATION
: 위성, 와이파이, 모바이 데이터를 이용해 정확한 위치에 접근

- android.permission.ACCESS_BACKGROUND_LOCATION
: 백그라운드 상태에 위치 접근 (안드로이드 10이상, API 레벨 29)

 


2. 플랫폼 API의 위치 매니저

 

1) LocationManager 

: 사용자에 위치 얻을 때 사용하는 시스템 서비스

val manager = getSystemService(LOCATION_SERVICE) as LocationManager


2) 위치 제공자 지정


- GPS : GPS 위성 이용
- Network : 이동 통신망 이용
- Wifi : 와이파이 이용
- Passive : 다른 앱에서 이용한 마지막 위치 정보 이용

 

3) 현재 기기에 어떤 위치 제공자가 있는지 확인

 : LocationManager.allProviders

var result = "All Providers : "
val providers = manager.allProviders
for (provider in providers) {
result += " $provider. "
}
Log.d("maptest", result)  // All Providers : passive, gps, network..


4) 지금 사용할 수 있는 위치 제공자 확인 

: LocationManager.getProviders()

result = "Enabled Providers : "
val enabledProviders = manager.getProviders(true)
for (provider in enabledProviders) {
result += " $provider. "
}
Log.d("maptest", result)  // Enabled Providers : passive, gps, network..


5) 위치 정보 얻기


- LocationManager.getLastKnownLocation()
- Location

  • getAccuracy() : 위치 정확도
  • getLatitude() : 위도
  • getLongitude() : 경도
  • getTime() : 획득 시간
//앱이 장치의 정확한 위치 접근에 대한 권한 확인
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")
            }
        }


- LocationListener : 계속 위치를 가져오는 경우
    - onLocationChanged() : 새로운 위치를 가져오면 호출
    - onProviderEnablded() : 위치 제공자가 이용할 수 있는 상황이면 호출
    - onProviderDisabled() : 위치 제공자가 이용할 수 없는 상황이면 호출

 

val listener: LocationListener = object : LocationListener {
// 새로운 위치 가져오면 호출
            override fun onLocationChanged(location: Location) {
                Log.d("map_test,","${location.latitude}, ${location.longitude}, ${location.accuracy}")
            }
        }

//메서드 호출하여 위치 업데이트 요청 : GPS 사용해 위치 업데이트, 업데이트 시간 간격 10초, 위치 업데이트의 최소 거리 간격 10미터, 위치 업데이트 발생할 때 호출될 객체 LocationListener
manager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 10_000L, 10f, listener)

// 위치 업데이트 더이상 받고 싶지 않을 때 호출
manager.removeUpdates(listener)



3. 구글 Play 서비스의 위치 라이브러리


1) Fused Location Provider 라이브러리 


: 구글에서 최적의 알고리즘으로 위치 제공자 지정 가능

implementation 'com.google.android.gms:play-services:12.0.1'


2) Fused Location Provdier 핵심 클래스

- FusedLocationProvdierClient : 위치 정보 얻기
- GoogleApiClient : 위치 제공자 준비 등 다양한 콜백 제공

  • GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnection FailedListener 인터페이스를 구현한 객체를 지정
//API 클라이언트에 연결되었을 때
val connectionCallback = object: GoogleApiClient.ConnectionCallbacks{

            override fun onConnected(p0: Bundle?) {
                // 위치 제공자를 사용할 수 있을 때
                // 위치 획득
            }

            override fun onConnectionSuspended(p0: Int) {
                // 위치 제공자를 사용할 수 없을 때
            }
 }

//API 클라이언트에 연결되지 않았을 때
val onConnectionFailCallback = object : GoogleApiClient.OnConnectionFailedListener{
    override fun onConnectionFailed(p0: ConnectionResult) {
    // 사용할 수 있는 위치 제공자가 없을 때
    }
}

//객체 지정
val apiClient = GoogleApiClient.Builder(this)
    .addApi(LocationServices.API)
    .addConnectionCallbacks(connectionCallback)
    .addOnConnectionFailedListener(onConnectionFailCallback)
    .build()


3) FusedLocationProviderClient 초기화

val providerClient = LocationServices.getFusedLocationProviderClient(this)


4) GoogleApiClient 객체에 위치 제공자를 요청

apiClient.connect()


5) onConnected() 함수에서 FusedLocationProviderClient의 getLastLocation() 함수 호출

// 위치 제공자를 사용할 수 있는 상황일 때
    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()
        }
    }




4. 지도 사용 설정하기


1) Build.gradle

implementation 'com.google.android.gms:play-services-maps:18.1.0'
implementation 'com.google.android.gms:play-services-location:21.0.1'


2) Manifest


- 퍼미션 등록

<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"/>


- 구글 지도 API 이용 키 등록

<uses-library android:name="org.apache.http.legacy" android:required="true"/>
<meta-data android:name="com.google.android.maps.v2.API_KEY"
      android:value="### 구글 지도 API 키 등록 ###"/>
<meta-data android:name="com.google.android.gms.version"
      android:value="@integer/google_play_services_version"/>



5. 구글 개발자 콘솔에서 지도 API 얻기


- [ https://console.cloud.google.c](https://console.cloud.google.com/welcome?project=dark-caldron-377213)om 
- 새 프로젝트 생성 → API 및 서비스 → 사용자 인증 정보 → API 키 만들기 → 생성
- 얻은 API키 Manifest 파일에 등록

 

6. 지도 제어하기


- xml

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mapView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:name="com.google.android.gms.maps.SupportMapFragment"/>


1) 지도의 중심 이동하기 

 

- 지도 출력하는 뷰 객체

 class MainActivity : AppCompatActivity(), OnMapReadyCallback {
    
        var googleMap: GoogleMap? = null
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
     //mapview와 연결
            (supportFragmentManager.findFragmentById(R.id.mapView) as SupportMapFragment)!!.getMapAsync(this)
    
     // OnMapReadyCallback를 통해 연결완료 되면 호출
     // 지도 객체를 이용할 수 있는 상황이 될 때
        override fun onMapReady(p0: GoogleMap?) {
            googleMap = p0
        }
    }



- 지도 중심 이동

//위도, 경도 좌표
val latLng = LatLng(37.566610, 126.978403)

//카메라의 위치와 확대 수준 정의
val position = CameraPosition.Builder()
    .target(latLng) //카메라 중심 설정
    .zoom(18f) //얼마나 확대
    .build()

//카메라 위치를 포지션으로 이동
googleMap?.moveCamera(CameraUpdateFactory.newCameraPosition(position))



- 마커 표시하기

//마커 설정
val markerOptions = MarkerOptions()
        markerOptions.icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_marker))
        markerOptions.position(latLng)
        markerOptions.title("서울시청") //마커 눌렀을 때 제목
        markerOptions.snippet("Tel:01-120") //마커 눌렀을 때 설명

//마커 지도에 추가
googleMap?.addMarker(markerOptions)



- 위치 요청

//현재 위치 받아옴
val locationRequest = LocationRequest.create().apply {
            interval = 1000 //1초에 한번씩
            fastestInterval = 500 //가장 빠른 업데이트 간격 : 0.5초보다 더 빠른 업데이트 무시
            priority = LocationRequest.PRIORITY_HIGH_ACCURACY //우선순위 : 가장 높은 정확도
        }

//위치 업데이트 발생했을 때 호출하는 콜백
locationCallback = object : LocationCallback(){
    //1초에 한번씩 변경된 위치 정보가 onLocationResult 으로 전달된다.
    override fun onLocationResult(locationResult: LocationResult) {
       locationResult?.let{
//위치 정보 목록 가져오기
       for (location in it.locations){
           Log.d("위치정보",  "위도: ${location.latitude} 경도: ${location.longitude}")

        }
       }
     }
}



2) 지도에서 사용자 이벤트 처리


- GoogleMap.OnMapClickListener: 지도 클릭 이벤트
- GoogleMap.OnMapLongClickListener: 지도 롱 클릭 이벤트
- GoogleMap.OnMarkerClickListener: 마커 클릭 이벤트
- GoogleMap.OnMarkerDragListener: 마커 드래그 이벤트
- GoogleMap.OnInfoWindowClickListener: 정보 창 클릭 이벤트
- GoogleMap.OnCameraIdleListener: 지도 화면 변경 이벤트

googleMap?.setOnMapClickListener { latLng ->
            Log.d("map_test", "click : ${latLng.latitude} , ${latLng.longitude}")
        }
        
googleMap?.setOnMapLongClickListener { latLng ->
            Log.d("map_test", "long click : ${latLng.latitude} , ${latLng.longitude}")
        }
        
googleMap?.setOnCameraIdleListener {
            val position = googleMap!!.cameraPosition
            val zoom = position.zoom
            val latitude = position.target.latitude
            val longitude = position.target.longitude
            Log.d("map_test", "User change : $zoom $latitude , $longitude")
        }
        
googleMap?.setOnMarkerClickListener { marker ->
            true
        }
        
googleMap?.setOnInfoWindowClickListener { marker ->
        }



7. 예제


- build.gradle

dependencies {

    implementation 'androidx.core:core-ktx:1.10.1'
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.9.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'

    implementation 'com.google.android.gms:play-services-maps:18.1.0'
    implementation 'com.google.android.gms:play-services-location:21.0.1'
}



- AndriodManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <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
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.MapTest"
        tools:targetApi="31">
        
        <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"/>
        
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>



- activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mapView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:name="com.google.android.gms.maps.SupportMapFragment"/>



- MainActivity

package com.android.ex11_googlemap

import android.Manifest
import android.annotation.SuppressLint
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 androidx.core.content.ContextCompat
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationListener
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.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.MarkerOptions
import com.google.android.gms.tasks.OnSuccessListener

//OnMapReadyCallback : 지도가 준비될 때 호출되는 콜백을 제공
class MainActivity : AppCompatActivity(), OnMapReadyCallback {

    private lateinit var mGoogleMap: GoogleMap

    //위치 서비스가 gps를 사용해서 위치를 확인
    lateinit var fusedLocationClient: FusedLocationProviderClient

    //위치 값 요청에 대한 갱신 정보를 받는 변수
    lateinit var locationCallback: LocationCallback

    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(
//2개의 권한 요청
                Manifest.permission.ACCESS_COARSE_LOCATION,
                Manifest.permission.ACCESS_FINE_LOCATION
            )
        )
    }

    // 지도 객체를 이용할 수 있는 상황이 될 때
    override fun onMapReady(p0: GoogleMap) {

        val seoul = LatLng(37.566610, 126.978403)
        
//구글 맵 객체
mGoogleMap = p0
//지도 유형 설정
        mGoogleMap.mapType = GoogleMap.MAP_TYPE_NORMAL // default 노말 생략 가능
        
mGoogleMap.apply {

//마커 설정
            val markerOptions = MarkerOptions()
            markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE))
            markerOptions.position(seoul)
            markerOptions.title("서울시청")
            markerOptions.snippet("Tel:01-120")
            addMarker(markerOptions)
        }

////위치 정보 받음
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
        //메서드 호출, 위치 업데이트 처리
updateLocation()
    }

//위치 업데이트
    fun updateLocation(){

//위치 업데이트 요청, 1초마다, 0.5보다 빠른 업데이트 무시, 우선순위 정확도
        val locationRequest = LocationRequest.create().apply {
            interval = 1000
            fastestInterval = 500
            priority = LocationRequest.PRIORITY_HIGH_ACCURACY
        }

        locationCallback = object : LocationCallback(){
            //1초에 한번씩 변경된 위치 정보가 onLocationResult 으로 전달된다.
            override fun onLocationResult(locationResult: LocationResult) {
                locationResult?.let{
                    for (location in it.locations){
                        Log.d("위치정보",  "위도: ${location.latitude} 경도: ${location.longitude}")
                           setLastLocation(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
        }

//requestLocationUpdates : 위치 업데이트 요청, 업데이트 처리하는 콜백, 위치 업데이트 이루어지는 스레드 명시
        fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback,
            Looper.myLooper()!!
        )
    }

//실시간으로 사용자의 최신 위치 받아와 지도에 마커로 표시, 카메라를 해당 위치로 이동
    fun setLastLocation(lastLocation: Location){

//위치 정보 사용하여 객체 생성
        val LATLNG = LatLng(lastLocation.latitude,lastLocation.longitude)

//마커 정보 생성
        val makerOptions = MarkerOptions().position(LATLNG).title("나 여기 있어용~")
//카메라 위치, 줌 레벨 생성
        val cameraPosition = CameraPosition.Builder().target(LATLNG).zoom(15.0f).build()

//마커 지도에 추가
        mGoogleMap.addMarker(makerOptions)

//카메라 위치 이동
        mGoogleMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition))
    }
}

 

 

'Android' 카테고리의 다른 글

Fragment 생명주기  (0) 2023.11.22
ROOM  (1) 2023.09.14
Parcelize  (0) 2023.08.30
어댑터뷰  (0) 2023.08.29
Dialog  (0) 2023.08.25