참고링크 : https://developer.android.com/training/location/geofencing#kotlin
개요
Android 에서는 Geofencing 기능을 통해 현재 위치가 특정 위치 반경에 진입/머물기/이탈 등의 이벤트를 처리 가능합니다.
단말 유저 당, 앱 당 최대 100개의 geofence 를 사용할 수 있습니다.
You can have multiple active geofences, with a limit of 100 per app, per device user.
Location Services에 geofence 를 위한 이벤트들을 요청해놓을 수 있습니다.
요청한 변경들이 감지되면 BroadcastReciever 등을 통해 처리가 가능합니다.
- 반경 진입
- 반경 이탈
- 반경 진입 후 지정한 시간 동안 이탈 안함
- geofence 만료 시간 (만료 시간 후 Location Service는 geofence를 알아서 지웁니다.)
항목 |
설명 |
최대 등록 수 |
100 per app, per device user |
만료시간 |
Geofence 마다 millisecond 로 지정. NEVER_EXPIRE 옵션 지원. 시간 지정 후 만료시 자동으로 Geofence 제거됨. |
영역 지정 |
Circle 형태로 등록 되며, 위경도와 반경(m) 지정. 최상의 결과는 반경 100~150m 사이로 설정해야 함. Wi-FI 사용 가능시 20~40m 까지 가능. 실내 위치 사용시는 5m 정도로 작을 수도 있음. Geofence 내부 위치를 알수 없다면 Wi-Fi 정확도는 약 50m라고 가정함. Wi-Fi 위치 사용할 수 없다면 수백m ~ 수Km 편차 생길수도 있음. |
머물기 시간 지정 |
Geofence 진입 후 머물기로 체크할 시간을 millisecond로 지정. |
Geofence 감지할 변경 타입 |
진입 (GEOFENCE_TRANSITION_ENTER) 이탈 (GEOFENCE_TRANSITION_EXIT) 머뭄 (GEOFENCE_TRANSITION_DWELL) |
응답시간 지정 |
응답 시간이 큰 경우 전력을 크게 절약하고 응답 시간을 낮춘다고 바로 알림을 받지는 않음. |
Geofence 추가시 초기 알림 설정 |
진입 (INITIAL_TRIGGER_ENTER) 이탈 (INITIAL_TRIGGER_EXIT) 머뭄 (INITIAL_TRIGGER_DWELL) |
Geofence 사용 이유 설명 |
앱의 백그라운드 위치에 액세스하므로 사용자에게 권한 설명 필요. |
Geofence 재등록이 필요없는 경우 |
Google Play 서비스 업그레이드나 리소스 제한으로 종료 후 재시작시 Location 프로세스 충돌시 |
Geofence 재등록이 필요한 경우 |
단말 재부팅시 부팅 완료 시점을 받아 다시 등록해야 함. 앱이 제거 후 재 설치시 앱 데이터 삭제시 Google Play 서비스 데이터 삭제시 앱에 GEOFENCE_NOT_AVAILABLE 알림 수신시 (일반적으로 Android's Network Location Provider 비활성화시 발생함) |
Geofence 진입이 잘 동작하지 않는 경우 (GEOFENCE_TRANSITION_ENTER) |
Geofence 정확한 위치를 사용할 수 없거나 반경이 너무 작은 경우 Wi-Fi가 꺼져 있는 경우 (SettingsClient를 사용해 장치 설정 체크 필요) - 참고로 Android 10에서는 WifiManager.setEnabled() 호출 불가하므로 설정 패널 사용. - 시스템 앱이나 Device policy controller 에서는 가능. Geofence 내 안정적인 네트워크 연결이 없을 경우 단순히 알림이 늦는 경우. - 지속적으로 위치 쿼리하는 형식이 아니라 대기 시간이 필요함. - 일반적인 대기 시간은 2 분 미만이며 장치가 움직일 때는 훨씬 적음. - 백그라운드 위치 제한 적용시 대기 시간은 평균 약 2~3분 정도. - 단말이 장시간 정지된 경우 대기 시간이 증가할 수 있음. (최대 6분) |
Android App 샘플 만들기
1. 권한 추가
먼저 권한을 아래와 같이 추가 후 앱에서 permission 체크 및 요청 로직을 추가합니다.
ACCESS_BACKGROUND_LOCATION
권한은 Android 10 부터 필요합니다.
App 권한 요청 처리
Background 위치 접근 권한 처리
AndroidManifest.xml
<manifest... >
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!-- Required if your app targets Android 10 (API level 29) or higher -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
...
</manifest>
MainActivity.kt
private val MY_PERMISSIONS_REQ_ACCESS_FINE_LOCATION = 100
private val MY_PERMISSIONS_REQ_ACCESS_BACKGROUND_LOCATION = 101
override fun onCreate(savedInstanceState: Bundle?) {
...
checkPermission()
}
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<String>, grantResults: IntArray) {
when (requestCode) {
MY_PERMISSIONS_REQ_ACCESS_FINE_LOCATION,
MY_PERMISSIONS_REQ_ACCESS_BACKGROUND_LOCATION -> {
grantResults.apply {
if (this.isNotEmpty()) {
this.forEach {
if (it != PackageManager.PERMISSION_GRANTED) {
checkPermission()
return
}
}
} else {
checkPermission()
}
}
}
}
}
private fun checkPermission() {
val permissionAccessFineLocationApproved = ActivityCompat
.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
if (permissionAccessFineLocationApproved) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val backgroundLocationPermissionApproved = ActivityCompat
.checkSelfPermission(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION) ==
PackageManager.PERMISSION_GRANTED
if (!backgroundLocationPermissionApproved) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
MY_PERMISSIONS_REQ_ACCESS_BACKGROUND_LOCATION
)
}
}
} else {
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
MY_PERMISSIONS_REQ_ACCESS_FINE_LOCATION
)
}
}
2. dependency 추가
Location Service 및 Geofence 사용을 위해서 location 라이브러리를 추가합니다.
app/build.gradle
dependencies {
...
implementation 'com.google.android.gms:play-services-location:17.0.0'
...
}
3. Geofence List 추가
테스트 용으로 추가할 Geofence 데이터를 생성합니다.
Geofence 객체 생성시 여러가지 설정을 할 수 있습니다.
ex) 위치 및 반경, 어떤 이벤트를 걸건지, 만료나 머무는 체크 시간을 얼마나 할지 등등
MainActivity.kt
val geofenceList: MutableList<Geofence> by lazy {
mutableListOf(
getGeofence("현대백화점", Pair(37.5085864,127.0601149)),
getGeofence("삼성역", Pair(37.5094518,127.063603))
)
}
private fun getGeofence(reqId: String, geo: Pair<Double, Double>, radius: Float = 100f): Geofence {
return Geofence.Builder()
.setRequestId(reqId) // 이벤트 발생시 BroadcastReceiver에서 구분할 id
.setCircularRegion(geo.first, geo.second, radius) // 위치 및 반경(m)
.setExpirationDuration(Geofence.NEVER_EXPIRE) // Geofence 만료 시간
.setLoiteringDelay(10000) // 머물기 체크 시간
.setTransitionTypes(
Geofence.GEOFENCE_TRANSITION_ENTER // 진입 감지시
or Geofence.GEOFENCE_TRANSITION_EXIT // 이탈 감지시
or Geofence.GEOFENCE_TRANSITION_DWELL) // 머물기 감지시
.build()
}
4. Geofence Client 생성
Location API 사용을 위하여 Geofencing Client 인스턴스를 생성해야 합니다.
MainActivity.kt
private val geofencingClient: GeofencingClient by lazy {
LocationServices.getGeofencingClient(this)
}
5. Geofencing Request 빌드
Geofence 지정 및 관련 이벤트 트리거 방식을 설정하기 위해 GeofencingRequest 를 빌드합니다.
MainActivity.kt
private fun getGeofencingRequest(list: List<Geofence>): GeofencingRequest {
return GeofencingRequest.Builder().apply {
// Geofence 이벤트는 진입시 부터 처리할 때
setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
addGeofences(list) // Geofence 리스트 추가
}.build()
}
6. Broadcast Receiver 추가
Geofencing 변경 이벤트를 받을 BroadcastReceiver 를 추가 후 등록해줍니다.
GeofenceBroadcastReceiver.kt
class GeofenceBroadcastReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
val errorMessage = GeofenceStatusCodes.getStatusCodeString(geofencingEvent.errorCode)
Log.e("GeofenceBR", errorMessage)
return
}
// Get the transition type.
val geofenceTransition = geofencingEvent.geofenceTransition // 발생 이벤트 타입
// Test that the reported transition was of interest.
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
// Get the geofences that were triggered. A single event can trigger
// multiple geofences.
val triggeringGeofences = geofencingEvent.triggeringGeofences
val transitionMsg = when(geofenceTransition) {
Geofence.GEOFENCE_TRANSITION_ENTER -> "Enter"
Geofence.GEOFENCE_TRANSITION_EXIT -> "Exit"
else -> "-"
}
triggeringGeofences.forEach {
Toast.makeText(context, "${it.requestId} - $transitionMsg", Toast.LENGTH_LONG).show()
}
} else {
Toast.makeText(context, "Unknown", Toast.LENGTH_LONG).show()
}
}
}
AndroidManifest.xml
<application... >
...
<receiver android:name=".GeofenceBroadcastReceiver"/>
</application>
MainActivity.kt
private val geofencePendingIntent: PendingIntent by lazy {
val intent = Intent(this, GeofenceBroadcastReceiver::class.java)
PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
7. Geofence Request 및 Callback 추가
Geofencing Client에 Geofence 정보 및 트리거 방식을 가지고 있는 Geofencing Request과 이벤트 발생시 처리할 Broadcast Receiver를 추가해줍니다.
MainActivity.kt
private fun addGeofences() {
geofencingClient.addGeofences(getGeofencingRequest(geofenceList), geofencePendingIntent).run {
addOnSuccessListener {
Toast.makeText(this@MainActivity, "add Success", Toast.LENGTH_LONG).show()
}
addOnFailureListener {
Toast.makeText(this@MainActivity, "add Fail", Toast.LENGTH_LONG).show()
}
}
}
준비는 끝났습니다. 앱을 실행해보아 동작을 확인합니다.