Android

GPS Location, 사용자 위치 정보 가져오기

탱구리몬 2024. 5. 8. 17:12

gps 기반 위치 정보와 와 구글 play 서비스의 위치 라이브러리를 사용하는 코드들을 리뷰한다.

 

- GPS

1. Manifest에 권한 추가

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

 

 

2. 권한 체크 및 요청

// 권한 체크 함수
private fun checkPermission(): Boolean {
    return ContextCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION) ==
            PackageManager.PERMISSION_GRANTED
}

// 권한 요청
private fun requestLocationPermission() {
    if (checkPermission()) {
        return
    }
    ActivityCompat.requestPermissions(
        this,
        arrayOf(PERMISSION_ACCESS_FINE_LOCATION),
        PERMISSION_REQUEST_ACCESS_FINE_LOCATION
    )
}

// (권한 요청 -> 사용자 선택 ->) Callback
override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<out String>,
    grantResults: IntArray,
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)

    if (requestCode != PERMISSION_REQUEST_ACCESS_FINE_LOCATION) {
        return
    }

    if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
        // 요청에 의해 권한이 부여되면 위치 정보를 사용할 수 있음
        getLocation()

    } else {
        // 요청에서 권한이 거부되면, 기능 사용 불가
        Snackbar.make(
            binding.root,
            "위치 권한이 허용되지 않아 OO 기능을 사용할 수 없습니다.",
            Snackbar.LENGTH_SHORT
        ).show()
    }
}

companion object {
    // 이유 : 위치 권한 요청에 대한 결과의 Callback을 받기 위함
    private const val PERMISSION_REQUEST_ACCESS_FINE_LOCATION = 100
}

 

 

3. 위치정보 가져오기(한 번 가져오기, 주기적으로 추적하기)

// 한 번만 위치 정보를 가져오면 될 때
private fun getLocationData() {
    if (!checkPermission()) {
        return
    }

    val manager = getSystemService(LOCATION_SERVICE) as LocationManager
    val location: Location? = manager.getLastKnownLocation(PROVIDER_FUSED)

    location?.let {
        val latitude = location.latitude     // 위도
        val longitude = location.longitude   // 경도
        val accuracy = location.accuracy       // 정확도
        val time = location.time               // 획득 시간
        
        // 위도: 37.5435617, 경도: 127.0003483, 정확도: 2702.399, 시간: 1715155669659
        Log.d("map_test", "위도: $latitude, 경도: $longitude, 정확도: $accuracy, 시간: $time") 
    }
}

 

// 일정 주기 또는 위치 변경 시 추적/관찰해야 할 때
private fun initLocationListener() { // 리스너 설정
    listener = object: LocationListener {
        // onLocationChanged: 새로운 위치를 가져오면 호출됨
        override fun onLocationChanged(location: Location) {
            Log.d("map_test,","$location") // Location[fused 37.543562,127.000348 hAcc=2702.399 et=+6h23m2s577ms alt=0.0 vAcc=0.5]
        }

        // onProviderEnabled: Location Privider를 이용할 수 있는 상황일 때 호출
        override fun onProviderEnabled(provider: String) {
            super.onProviderEnabled(provider)
        }

        // onProviderDisabled: Location Privider를 이용할 수 없는 상황일 때 호출
        override fun onProviderDisabled(provider: String) {
            super.onProviderDisabled(provider)
        }
    }
}

private fun getLocationData2() {
    if (!checkPermission()) {
        return
    }

    manager.requestLocationUpdates(
        PROVIDER_FUSED,  // Location Provider 4가지 중 택1
        10_000L,         // 위치 업데이트 간의 최소 시간 간격(ms)
        10f,             // 위치 업데이트 간의 최소 거리(m)
        listener         // 위치 업데이트시 호출되는 리스너 설정
    )
}

 

 

부가. Location Provider 종류 및 정의

/*
    Location Provider 4가지
    1. gps : 인공위성 기반 / 실내 또는 기상 악화시 안됨 / 정확도 제일 높음
    2. network : 인공위성이 안될 때 씀 / 기지국 기반
    3. passive : gps, network 둘 다 안될 때 씀 / 다른 앱에서 마지막으로 얻었던 위치 정보 기반
    4. fused : google map 라이브러리 / 실외에선 gps(인공위성), 실내에서는 network(기지국) 기반
 */
private fun Test(manager: LocationManager) {
    var result = "All Providers : "
    val providers = manager.allProviders
    for (provider in providers) {
        result += " $provider. "
    }
    Log.d("maptest", result) // All Providers :  passive.  network.  fused.  gps. 


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

 

 

- 구글 Play 서비스 위치 라이브러리

 

1. Gradle 설정

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

 

2. 위치정보 가져오기

// Fragment단 코드
class GoogleMapFragment : Fragment(), OnMapReadyCallback {
    private var _binding: FragmentFirstBinding? = null
    private val binding get() = _binding!!

    private lateinit var googleMap: GoogleMap

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

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

    lateinit var locationPermission: ActivityResultLauncher<Array<String>>

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View {
        _binding = FragmentFirstBinding.inflate(inflater, container, false)

        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        locationPermission = registerForActivityResult(
            ActivityResultContracts.RequestMultiplePermissions()
        ) { results ->
            if (results.all { it.value }) {
                val mapFragment =
                    childFragmentManager.findFragmentById(R.id.fl_maps) as SupportMapFragment
                mapFragment.getMapAsync(this)
            } else { //문제가 발생했을 때
                Toast.makeText(requireContext(), "권한 승인이 필요합니다.", Toast.LENGTH_SHORT).show()
            }
        }

        //권한 요청
        locationPermission.launch(
            arrayOf(
                android.Manifest.permission.ACCESS_COARSE_LOCATION,
                android.Manifest.permission.ACCESS_FINE_LOCATION
            )
        )
    }

    // (getMapAsync(this)를 통해서) 지도 객체를 이용할 수 있는 상황이 될 때 아래 메서드가 호출됨
    override fun onMapReady(p0: GoogleMap) {
        googleMap = p0
        googleMap.mapType = GoogleMap.MAP_TYPE_NORMAL // default 노말 생략 가능

        // 마커 설정
        val markerOptions = MarkerOptions().apply {
            // val markerIcon = androidx.constraintlayout.widget.R.drawable.abc_text_cursor_material
            val seoul = LatLng(37.566610, 126.978403)

            // icon(BitmapDescriptorFactory.fromResource(markerIcon))
            position(seoul)
            title("서울시청")
            snippet("Tel:01-120")
        }
        googleMap.addMarker(markerOptions)

        fusedLocationClient = LocationServices.getFusedLocationProviderClient(requireActivity())
        updateLocation()
    }

    override fun onDestroyView() {
        super.onDestroyView()

        _binding = null
    }

    private fun checkPermission(): Boolean {
        return ActivityCompat.checkSelfPermission(
            requireContext(),
            android.Manifest.permission.ACCESS_FINE_LOCATION
        ) == PackageManager.PERMISSION_GRANTED
    }

    fun updateLocation() {
        // 현재 위치 요청
        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 (!checkPermission()) {
            return
        }

        fusedLocationClient.requestLocationUpdates(
            locationRequest, locationCallback,
            Looper.myLooper()!!
        )
    }

    fun setLastLocation(lastLocation: Location) {
        // 지도의 중심 이동
        val LATLNG = LatLng(lastLocation.latitude, lastLocation.longitude)

        val cameraPosition = CameraPosition.Builder()
            .target(LATLNG)
            .zoom(15.0f)
            .build()

        googleMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition))

        // 마지막 위치에 마커 설정
        val makerOptions = MarkerOptions()
            .position(LATLNG)
            .title("나 여기 있어용~")

        googleMap.addMarker(makerOptions)
    }
}