안드로이드/개발관련(Kotlin)

안드로이드 Geofence(지오펜스)

닉네임못짓는사람 2021. 12. 30. 23:10
반응형

이번 글에서는 안드로이드의 Geofence를 사용하는법에 대해 알아보도록 하자.

Geofence에서 fence는 담, 울타리 등을 의미하는데, 스마트폰의 GPS위치를 기반으로

장소와 범위를 지정하여 울타리를 만들고, 울타리 내에 들어가거나 나올때에 이벤트를 동작시키는 기능이다.

Geofence는 BroadCastReceiver를 사용하여 백그라운드에서 동작한다.

때문에 앱을 실행중이지 않을 때에도 이벤트가 발생하면 정해진 동작을 수행한다.

이제 실제로 Geofence를 사용하는 법에 대해서 알아보도록 하자.

Defendency


Geofence를 등록하기 위해선 gradle파일에 의존성을 추가해주어야 한다.

dependencies {
    ...
    implementation 'com.google.android.gms:play-services-location:17.0.0'
    ...
}

BroadCastReceiver등록


위에서 말했듯이 Geofence는 BroadCastReceiver를 사용하기 때문에 이에 사용할 Receiver를 먼저 등록해주도록 하자.

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
    <application
        .
        .
        .
        <receiver android:name=".GeofenceBroadcastReceiver"/>
        .
        .
        .
    </application>

먼저 Manifest파일에 위와 같이 리시버를 등록하고, permission을 추가해준 뒤 다음으로 class파일을 작성해보자.

class GeofenceBroadcastReceiver: BroadcastReceiver(){
    override fun onReceive(context: Context?, intent: Intent?) {
        val geofencingEvent = GeofencingEvent.fromIntent(intent)

        if (geofencingEvent.hasError()) {
            Log.e("GeofenceErr", GeofenceStatusCodes.getStatusCodeString(geofencingEvent.errorCode))
            return
        }else{
            Log.e("GeofenceErr", "NoErr")
        }
        val geofenceTransaction = geofencingEvent.geofenceTransition

        if (geofenceTransaction == Geofence.GEOFENCE_TRANSITION_ENTER ||
                geofenceTransaction == Geofence.GEOFENCE_TRANSITION_EXIT
        ) {
            val triggeringGeofences = geofencingEvent.triggeringGeofences
            val transitionMsg = when (geofenceTransaction) {
                Geofence.GEOFENCE_TRANSITION_ENTER -> "Enter"
                Geofence.GEOFENCE_TRANSITION_EXIT -> "Exit"
                else -> "-"
            }
            triggeringGeofences.forEach {
                Log.e("geofence", "${it.requestId} - $transitionMsg")
            }
        } else {
            Log.e("geofence", "Unknown")
        }
    }
}

들어오는 Event를 변수에 저장하고, 오류검사를 하여 오류가 있을 경우 return으로 종료해주도록 한다.

 

오류검사가 끝났으면 Event에 대해 어떤 동작을 수행할지 정해주는데,

if(geofenceTransaction == ...부분을 보면 위 코드에선 사용자가 geofence울타리에 들어가거나 나갔을 때 동작을 수행한다.

 

이렇게 BroadCastReceiver를 다 작성해주었다면, 다음으로 geofence를 실제로 등록하는 코드로 넘어가보도록 하자.

Geofence생성


class MainActivity : AppCompatActivity(){
    private lateinit var binding: ActivityMapBinding
    lateinit var geofencingClient: GeofencingClient
    val geofenceList: MutableList<Geofence> by lazy{
        mutableListOf()
    }
    .
    .
    .

가장 먼저 MutableList하나를 선언해주도록 하자.

이곳에 Geofence들을 추가하여 한번에 등록하도록 할 것이다.

    private fun getGeofence(reqId: String, geo: LatLng, radius: Float): Geofence {
        return Geofence.Builder()
                .setRequestId(reqId)
                .setCircularRegion(geo.latitude, geo.longitude, radius)
                .setExpirationDuration(Geofence.NEVER_EXPIRE)
                .setLoiteringDelay(10000)
                .setTransitionTypes(
                        Geofence.GEOFENCE_TRANSITION_ENTER
                                or Geofence.GEOFENCE_TRANSITION_EXIT
                )
                .build()
    }

위 코드는 실제로 Geofence를 Builder에서 획득하는 함수인데,

Geofence는 울타리를 만들기 때문에 기본적으로 좌표와 범위는 필수적으로 들어가야 한다.

 

또한 setExpirationDuration은 Geofence를 유효기간을 설정하는 것인데, NEVER_EXPIRE는

Geofence를 기기에서 직접 제거하지 않는 이상 만료되지 않는것을 의미한다.

 

setTransitionTypes는 Geofence에서 감지할 이벤트들을 의미한다.

위 코드에선 ENTER와 EXIT이벤트를 감지한다.

Geofence등록


    val geoPending: PendingIntent by lazy{
        val intent = Intent(this, GeofenceBroadcastReceiver::class.java)
        PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
    }

먼저, PendingIntent를 위와 같이 정의해준다.

    private fun addGeofences(){
        if(ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED){
            geofencingClient.addGeofences(getGeofencingRequest(geofenceList), geoPending).run{
                addOnSuccessListener {
                    Log.e("addGeo", "add Success")
                }
                addOnFailureListener {
                    Log.e("addGeo", "add Fail")
                }
            }
        }
    }

그 후 Geofence를 BroadCastReceiver에 등록해주는데,

이때 Geofence 지정 및 관련 이벤트 트리거 방식을 설정하기 위해 GeofencingRequest가 필요하다.

    private fun getGeofencingRequest(list: List<Geofence>): GeofencingRequest {
        return GeofencingRequest.Builder().apply{
            setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
            addGeofences(list)
        }.build()
    }

위 코드에서 setInitiaTrigger는 사용자가 이미 Geofence내에 있을 경우

Enter와 Exit같은 이벤트 중 어떤것을 트리거해야 하는지 지정하는 부분이다.

 

위 코드들을 모두 작성하면 이제 Geofence가 등록이 되어 울타리에 출입할 때 이벤트가 발생할 것이다.

Geofence제거


마지막으로 등록한 Geofence를 제거하는 법에 대해서 알아보자.

    private fun removeGeofences(){
        geofencingClient.removeGeofences(geoPending).run{
            addOnSuccessListener {
                Log.e("removeGeo", "remove Success")
                geofenceList.clear()
            }
            addOnFailureListener{
                Log.e("removeGeo", "remove Fail")
            }
        }
    }

위에서 선언한 PendingIntent를 가지고 위와 같은 코드를 실행시켜주면 된다.

주의할 점은 위 코드를 실행할 경우 기기에 등록된 모든 Geofence가 제거된다는 점이다.

 

전체 코드

class MainActivity : AppCompatActivity(){
    private lateinit var binding: ActivityMapBinding
    lateinit var geofencingClient: GeofencingClient
    val geofenceList: MutableList<Geofence> by lazy{
        mutableListOf()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMapBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)

        geofencingClient = LocationServices.getGeofencingClient(this)
    }

    private fun getGeofence(reqId: String, geo: LatLng, radius: Float): Geofence {
        return Geofence.Builder()
                .setRequestId(reqId)
                .setCircularRegion(geo.latitude, geo.longitude, radius)
                .setExpirationDuration(Geofence.NEVER_EXPIRE)
                .setLoiteringDelay(10000)
                .setTransitionTypes(
                        Geofence.GEOFENCE_TRANSITION_ENTER
                                or Geofence.GEOFENCE_TRANSITION_EXIT
                )
                .build()
    }

    val geoPending: PendingIntent by lazy{
        val intent = Intent(this, GeofenceBroadcastReceiver::class.java)
        PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
    }

    private fun addGeofences(){
        if(ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED){
            geofencingClient.addGeofences(getGeofencingRequest(geofenceList), geoPending).run{
                addOnSuccessListener {
                    Log.e("addGeo", "add Success")
                }
                addOnFailureListener {
                    Log.e("addGeo", "add Fail")

                }
            }
        }
    }

    private fun removeGeofences(){
        geofencingClient.removeGeofences(geoPending).run{
            addOnSuccessListener {
                Log.e("removeGeo", "remove Success")
                geofenceList.clear()
            }
            addOnFailureListener{
                Log.e("removeGeo", "remove Fail")
            }
        }
    }

    private fun getGeofencingRequest(list: List<Geofence>): GeofencingRequest {
        return GeofencingRequest.Builder().apply{
            setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
            addGeofences(list)
        }.build()
    }
}

 

반응형