[Android] GeoLocation

leeehaยท2022๋…„ 11์›” 27์ผ
0
post-thumbnail

GeoLocation

  • ์œ ์ €์˜ ์œ„์น˜ ์ •๋ณด๋ฅผ ์ด์šฉํ•œ ์„œ๋น„์Šค

ํผ๋ฏธ์…˜ ๋ถ€์—ฌ

  • android.permission.ACCESS_COARSE_LOCATION: ์™€์ดํŒŒ์ด ๋˜๋Š” ๋ชจ๋ฐ”์ผ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ธฐ๊ธฐ์˜ ์œ„์น˜ ์ •๋ณด ํš๋“. ์ •ํ™•๋„๋Š” ๋„์‹œ ๋ธ”๋ก 1๊ฐœ ์ •๋„์˜ ์˜ค์ฐจ ์ˆ˜์ค€
  • android.permission.ACCESS_FINE_LOCATION: ์œ„์„ฑ, ์™€์ดํŒŒ์ด ๋ฐ ๋ชจ๋ฐ”์ผ ๋ฐ์ดํ„ฐ ๋“ฑ ์œ„์น˜ ์ •๋ณด ์ œ๊ณต์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ตœ๋Œ€ํ•œ ์ •ํ™•ํ•˜๊ฒŒ ์œ„์น˜๋ฅผ ๊ฒฐ์ •ํ•จ.
  • android.permission.ACCESS_BACKGROUND_LOCATION: Android 10 (API Level 29) ์ด์ƒ์—์„œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ƒํƒœ์—์„œ ์œ„์น˜ ์ •๋ณด์— ์ ‘๊ทผํ•  ๋•Œ ํ•„์š”

Location Provider

  • GPS: GPS ์œ„์„ฑ์„ ์ด์šฉํ•˜์—ฌ ์œ„์น˜ ์ •๋ณด ํš๋“ (๊ฐ€์žฅ ๋†’์€ ์ •ํ™•๋„, but ์Œ์˜ ์ง€์—ญ์ด ๋งŽ์Œ.)
  • Network: ์ด๋™ ํ†ต์‹ ์‚ฌ ๋ง ์ •๋ณด๋ฅผ ์ด์šฉํ•˜์—ฌ ์œ„์น˜ ์ •๋ณด ํš๋“ (์ง€ํ•˜์ฒ  ์•ˆ์ด๋‚˜ ๊ฑด๋ฌผ ์•ˆ๊ณผ ๊ฐ™์ด GPS์—์„œ ์Œ์˜ ์ง€์—ญ์ธ ๊ณณ๋„ ์œ„์น˜ ์ถ”์ ํ•  ์ˆ˜ ์žˆ์Œ.)
  • Wifi: ์™€์ดํŒŒ์ด์˜ AP ์ •๋ณด๋ฅผ ์ด์šฉํ•˜์—ฌ ์œ„์น˜ ์ •๋ณด ํš๋“
  • Passive: ๋‹ค๋ฅธ ์•ฑ์—์„œ ์ด์šฉํ•œ ๋งˆ์ง€๋ง‰ ์œ„์น˜ ์ •๋ณด ํš๋“
  • ํฐ์— ์–ด๋–ค ์œ„์น˜ ์ •๋ณด ์ œ๊ณต์ž๊ฐ€ ์žˆ๋Š”์ง€ ํŒŒ์•…ํ•˜๋ ค๋ฉด?

  • ํ˜„ ์‹œ์ ์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํ”„๋กœํผํ‹ฐ๋ฅผ ํš๋“ํ•˜๋ ค๋ฉด?

  • ๊ธฐ๋ณธ์ ์œผ๋กœ ์œ„๋„/๊ฒฝ๋„๋Š” ๋„/๋ถ„/์ดˆ๋กœ ํ‘œ์‹œ๋˜๋Š”๋ฐ ์ผ๋ฐ˜์ ์ธ ์‹œ์Šคํ…œ์—์„œ๋Š” ์‹ค์ˆ˜ํ˜• ํƒ€์ž…์œผ๋กœ ์ œ๊ณต๋œ๋‹ค.
    ex) 37๋„ 30๋ถ„ 30์ดˆ โ†’ 37.5๋„

  • ์œ ์ € ์œ„์น˜ ์ •๋ณด๋ฅผ ํš๋“ํ•˜๋Š” ๋Œ€ํ‘œ์ ์ธ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” LocationManager๊ณผ Fused API๊ฐ€ ์žˆ๋‹ค.


LocationManager

  • ํ”Œ๋žซํผ API์—์„œ ์ œ๊ณต๋˜๋Š” ์‹œ์Šคํ…œ ์„œ๋น„์Šค
val manager = getSystemService(LOCATION_SERVICE) as LocationManager 
  • ์œ„์น˜ ์ •๋ณด ํš๋“์€ LocationManager์˜ getLastKnownLocation() ํ•จ์ˆ˜๋ฅผ ์ด์šฉ
  • ๊ฒฐ๊ณผ๊ฐ’์€ Location ๊ฐ์ฒด๋กœ ์ „๋‹ฌ๋จ.
    • getAccuracy(): ์ •ํ™•๋„
    • getLatitude(): ์œ„๋„
    • getLongitude(): ๊ฒฝ๋„
    • getTime(): ์œ„์น˜ ์ •๋ณด ํš๋“ ์‹œ๊ฐ„
val location: Location? = manager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
  • ์ง€์†์ ์œผ๋กœ ์œ„์น˜๋ฅผ ํš๋“ํ•ด์•ผ ํ•œ๋‹ค๋ฉด, LocationListener ์‚ฌ์šฉ

์‹ค์Šต ์˜ˆ์ œ

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
package com.tutorial.c81

import android.Manifest
import android.content.pm.PackageManager
import android.location.LocationManager
import android.os.Bundle
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat

class MainActivity : AppCompatActivity() {
    lateinit var resultView: TextView
    lateinit var manager: LocationManager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        resultView = findViewById(R.id.resultView)
        manager = getSystemService(LOCATION_SERVICE) as LocationManager

        val launcher = registerForActivityResult(
            ActivityResultContracts.RequestPermission()
        ) { isGranted ->
            if(isGranted){
                getLocation()
            }else{
                Toast.makeText(this, "denied...", Toast.LENGTH_SHORT).show()
            }
        }

        val status = ContextCompat.checkSelfPermission(this,
            "android.permission.ACCESS_FINE_LOCATION")
        if(status == PackageManager.PERMISSION_GRANTED){
            getLocation()
        }else{
            launcher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
        }
    }

    private fun getLocation() {
        val location = manager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
        location?.let {
            val latitude = location.latitude
            val longitude = location.longitude
            val accuracy = location.accuracy
            val time = location.time
            resultView.text = "$latitude\n $longitude\n $accuracy\n $time"
        }
    }
}


Fused API

  • ์œ„์น˜ ์ •๋ณด๋ฅผ ํš๋“ํ•  ๋•Œ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์ƒํ™ฉ์„ ๊ณ ๋ คํ•ด์•ผ ํ•œ๋‹ค.
  • ๋‚ฎ์€ ์ „๋ ฅ ์†Œ๋ชจ, ์ •ํ™•๋„ ํ–ฅ์ƒ, ๊ฐ„๋‹จํ•œ API ๋“ฑ
  • ์ •๋ณด ํš๋“๊ณผ ๊ด€๋ จ๋œ ์ฝ”๋“œ์˜ ๋ณต์žก๋„๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด ๊ตฌ๊ธ€์—์„œ ์ œ๊ณตํ•˜๋Š” API๊ฐ€ Fused Location Provider
implementation 'com.google.android.gms:play-services:12.0.1'

Fused Location Provider์˜ ํ•ต์‹ฌ ํด๋ž˜์Šค 2๊ฐ€์ง€

  • FusedLocationProviderClient: ์œ„์น˜ ์ •๋ณด ํš๋“
  • GoogleApiClient: ์œ„์น˜ ์ •๋ณด ์ œ๊ณต์ž๋ฅผ ์ด์šฉํ•˜๊ธฐ ์œ„ํ•œ ์ค€๋น„, ๋‹ค์–‘ํ•œ ์ฝœ๋ฐฑ ํ•จ์ˆ˜ ์ œ๊ณต
  • GoogleApiClient์—๋Š” GoogleApiClient.ConnectionCallbacks์™€ GoogleApiClient.OnConnectionFailedListener ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ๊ฐ์ฒด๋ฅผ ์ง€์ •

  • FusedLocationProviderClient์˜ getLastLocation() ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด ์œ„์น˜ ํš๋“
  • ๊ฒฐ๊ณผ๊ฐ’์€ addOnSuccessListener() ํ•จ์ˆ˜์— ๋“ฑ๋กํ•œ OnSuccessListener ๊ตฌํ˜„ ๊ฐ์ฒด์˜ onSuccess() ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉฐ ์ „๋‹ฌ๋จ.

์‹ค์Šต ์˜ˆ์ œ

package com.tutorial.c82

import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.api.GoogleApiClient
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices

class MainActivity : AppCompatActivity() {
    lateinit var resultView: TextView
    lateinit var providerClient: FusedLocationProviderClient

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        resultView = findViewById(R.id.resultView)

        val apiClient = GoogleApiClient.Builder(this)
            .addApi(LocationServices.API)
            .addConnectionCallbacks(connectionCallback)
            .addOnConnectionFailedListener(connectionFailedCallback)
            .build()

        providerClient = LocationServices.getFusedLocationProviderClient(this)

        val launcher = registerForActivityResult(
            ActivityResultContracts.RequestPermission()
        ){
            if(it){
                apiClient.connect()
            }else{
                Toast.makeText(this, "denied...", Toast.LENGTH_SHORT).show()
            }
        }

        val status = ContextCompat.checkSelfPermission(this,
            "android.permission.ACCESS_FINE_LOCATION")
        if(status == PackageManager.PERMISSION_GRANTED){
            apiClient.connect()
        }else{
            launcher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
        }
    }

    private val connectionCallback = object: GoogleApiClient.ConnectionCallbacks {
        // ์œ„์น˜ ์ •๋ณด ์ œ๊ณต์ž๊ฐ€ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ƒํƒœ๊ฐ€ ๋˜์—ˆ์„ ๋•Œ
        override fun onConnected(p0: Bundle?) {
            providerClient.lastLocation.addOnSuccessListener {
                val latitude = it?.latitude
                val longitude = it?.longitude
                resultView.text = "$latitude, $longitude"
            }
        }

        // ์œ„์น˜ ์ •๋ณด ์ œ๊ณต์ž๊ฐ€ ์‚ฌ์šฉ ๋ถˆ๊ฐ€๋Šฅํ•œ ์ƒํƒœ๊ฐ€ ๋˜์—ˆ์„ ๋•Œ
        override fun onConnectionSuspended(p0: Int) {

        }
    }

    private val connectionFailedCallback = object: GoogleApiClient.OnConnectionFailedListener {
        override fun onConnectionFailed(p0: ConnectionResult) {

        }
    }
}


GoogleMap

  • play-service ๋ผ์ด๋ธŒ๋Ÿฌ์ด์—์„œ ์ง€๋„ ๋ทฐ๋Š” ํ”„๋ž˜๊ทธ๋จผํŠธ๋กœ ์ œ๊ณตํ•œ๋‹ค.

  • GoogleMap์ด๋ผ๋Š” ํด๋ž˜์Šค๊ฐ€ ์ง€๋„๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค.

์‹ค์Šต ์˜ˆ์ œ

implementation 'com.google.android.gms:play-services:12.0.1'
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tutorial.c83">

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AndroidLab">
        
        <uses-library android:name="org.apache.http.legacy" android:required="false"/>
        <meta-data
            android:name="com.google.android.maps.v2.API_KEY"
            android:value=""/>
        <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>
package com.tutorial.c83

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
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.CameraPosition
import com.google.android.gms.maps.model.LatLng

class MainActivity : AppCompatActivity(), OnMapReadyCallback {
    var googleMap: GoogleMap? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        (supportFragmentManager.findFragmentById(R.id.mapView) as SupportMapFragment)
            .getMapAsync(this)
    }

    override fun onMapReady(p0: GoogleMap?) {
        googleMap = p0
        val latLng = LatLng(37.566610, 126.978403) // ์„œ์šธ ์‹œ์ฒญ์˜ ์œ„์น˜
        val position = CameraPosition.Builder() // ๋ณด์—ฌ์ค„ ์ง€๋„ ์œ„์น˜ ์„ค์ •
            .target(latLng)
            .zoom(16f)
            .build()
        googleMap?.moveCamera(CameraUpdateFactory.newCameraPosition(position))
    }
}

SHA-1 ์ง€๋ฌธ ์–ป๊ธฐ

Settings > Experimental > Do not build Gradle task list during Gradle sync ์ฒดํฌ ํ•ด์ œ

์•ˆ๋“œ๋กœ์ด๋“œ ์ŠคํŠœ๋””์˜ค ์˜ค๋ฅธ์ชฝ์— Gradle ํƒญ > signingReport

cf) ํ…Œ์ŠคํŠธ์šฉ์ด ์•„๋‹ˆ๋ผ, ์ •์‹ ์•ฑ์„ ์œ„ํ•œ SHA-1 ํ‚ค๋ฅผ ์–ป์œผ๋ ค๋ฉด ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋นŒ๋“œํ•˜๋ฉด์„œ ์‚ฌ์ธํ–ˆ๋˜ ํ‚ค๋ฅผ ์ด์šฉํ•ด์•ผ ํ•œ๋‹ค.

Google Maps Platform์—์„œ API ํ‚ค ์–ป๊ธฐ

https://developers.google.com/maps/documentation/android-sdk/get-api-key

์•„๋ž˜ value์— API ํ‚ค๋ฅผ ๋“ฑ๋กํ•ด์ค€๋‹ค.

<uses-library android:name="org.apache.http.legacy" android:required="false"/>
<meta-data
     android:name="com.google.android.maps.v2.API_KEY"
     android:value="~~~~~"/>
<meta-data
     android:name="com.google.android.gms.version"
     android:value="@integer/google_play_services_version"/>

profile
์Šต๊ด€์ด ๋  ๋•Œ๊นŒ์ง€ ๐Ÿ“

1๊ฐœ์˜ ๋Œ“๊ธ€

comment-user-thumbnail
2023๋…„ 4์›” 10์ผ

ํ˜น์‹œ ์•ˆ๋“œ๋กœ์ด๋“œ 29๋ฒ„์ „ ๋ฐ‘์ด๋ฉด ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๊ถŒํ•œ๋งŒ๋ฐ›์œผ๋ฉด ์•ฑ๋‚ด๋ ค๊ฐ€๋„ watchPostion ๊ฒŒ์† ๋ฐ์ดํ„ฐ ๋ฐ›๊ณ ์žˆ๋‚˜์š” ?

๋‹ต๊ธ€ ๋‹ฌ๊ธฐ