지오펜싱 만들기 및 모니터링

지오펜싱은 현재 위치에 관한 사용자의 인식과 관심 위치에 관한 사용자의 근접성 인식을 결합합니다. 관심 위치를 표시하려면 위도와 경도를 지정합니다. 위치 근접성을 조정하려면 반경을 추가합니다. 위도와 경도, 반경은 지오펜싱을 정의하고 관심 위치 주위에 원형 영역 또는 펜스를 만듭니다.

모든 앱에서 기기 사용자당 100개 제한으로 여러 활성 지오펜싱을 사용할 수 있습니다. 각 지오펜싱에서 위치 서비스에 방문 및 이탈 이벤트를 전송하도록 요청하거나 이벤트를 트리거하기 전에 지오펜싱 영역 내에서 대기하거나 머무를 시간을 지정할 수 있습니다. 만료 시간을 밀리초로 지정하여 지오펜싱 시간을 제한할 수 있습니다. 지오펜싱이 만료되면 위치 서비스에서 자동으로 삭제됩니다.

이 과정에서는 지오펜싱을 추가 및 삭제한 다음 BroadcastReceiver를 사용하여 지오펜싱 전환을 수신 대기하는 방법을 보여줍니다.

지오펜싱 모니터링 설정

지오펜싱 모니터링을 요청하는 첫 단계는 필요한 권한을 요청하는 것입니다. 지오펜싱을 사용하려면 앱에서 ACCESS_FINE_LOCATION을 요청해야 합니다. 앱이 Android 10(API 레벨 29) 이상을 타겟팅한다면 ACCESS_BACKGROUND_LOCATION도 요청해야 합니다.

필요한 권한을 요청하려면 앱 manifest에서 <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"/>
    

BroadcastReceiver를 사용하여 지오펜싱 전환을 수신 대기하려면 서비스 이름을 지정하는 요소를 추가합니다. 이 요소는 <application> 요소의 하위 요소여야 합니다.

    <application
       android:allowBackup="true">
       ...
       <receiver android:name=".GeofenceBroadcastReceiver"/>
    <application/>
    

위치 API에 액세스하려면 지오펜싱 클라이언트의 인스턴스를 만들어야 합니다. 클라이언트를 연결하는 방법을 알아보려면 다음을 참조하세요.

Kotlin

    lateinit var geofencingClient: GeofencingClient

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        geofencingClient = LocationServices.getGeofencingClient(this)
    }
    

자바

    private GeofencingClient geofencingClient;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // ...
        geofencingClient = LocationServices.getGeofencingClient(this);
    }
    

지오펜싱 만들기 및 추가

지오펜싱 개체를 만드는 데는 위치 API 빌더 클래스를, 추가하는 데는 편의 클래스를 사용하여 앱에서 지오펜싱을 만들고 추가해야 합니다. 또한 지오펜싱 전환이 발생할 때 위치 서비스에서 전송된 인텐트를 처리하려면 이 섹션에 설명된 대로 PendingIntent를 정의하면 됩니다.

참고: 단일 사용자 기기에서는 앱당 지오펜싱이 100개로 제한됩니다. 멀티 사용자 기기의 경우 기기 사용자당 앱당 지오펜싱이 100개로 제한됩니다.

지오펜싱 개체 만들기

먼저 Geofence.Builder를 사용하여 지오펜싱을 만들어 원하는 지오펜싱의 반경과 지속 시간, 전환 유형을 설정합니다. 예를 들어 목록 개체를 채우려면 다음을 참조하세요.

Kotlin

    geofenceList.add(Geofence.Builder()
            // Set the request ID of the geofence. This is a string to identify this
            // geofence.
            .setRequestId(entry.key)

            // Set the circular region of this geofence.
            .setCircularRegion(
                    entry.value.latitude,
                    entry.value.longitude,
                    Constants.GEOFENCE_RADIUS_IN_METERS
            )

            // Set the expiration duration of the geofence. This geofence gets automatically
            // removed after this period of time.
            .setExpirationDuration(Constants.GEOFENCE_EXPIRATION_IN_MILLISECONDS)

            // Set the transition types of interest. Alerts are only generated for these
            // transition. We track entry and exit transitions in this sample.
            .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)

            // Create the geofence.
            .build())
    

자바

    geofenceList.add(new Geofence.Builder()
        // Set the request ID of the geofence. This is a string to identify this
        // geofence.
        .setRequestId(entry.getKey())

        .setCircularRegion(
                entry.getValue().latitude,
                entry.getValue().longitude,
                Constants.GEOFENCE_RADIUS_IN_METERS
        )
        .setExpirationDuration(Constants.GEOFENCE_EXPIRATION_IN_MILLISECONDS)
        .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER |
                Geofence.GEOFENCE_TRANSITION_EXIT)
        .build());
    

이 예에서는 데이터를 상수 파일에서 가져옵니다. 실제로는 앱에서 사용자 위치에 기반해 동적으로 지오펜싱을 만들 수 있습니다.

지오펜싱 및 초기 트리거 지정

다음 스니펫은 GeofencingRequest 클래스와 이에 중첩된 GeofencingRequestBuilder 클래스를 사용하여 모니터링할 지오펜싱을 지정하고 관련 지오펜싱 이벤트가 트리거되는 방법을 설정합니다.

Kotlin

    private fun getGeofencingRequest(): GeofencingRequest {
        return GeofencingRequest.Builder().apply {
            setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
            addGeofences(geofenceList)
        }.build()
    }
    

자바

    private GeofencingRequest getGeofencingRequest() {
        GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
        builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
        builder.addGeofences(geofenceList);
        return builder.build();
    }
    

이 예에서는 두 개의 지오펜싱 트리거 사용법을 보여줍니다. 기기가 지오펜싱에 들어갈 때 GEOFENCE_TRANSITION_ENTER 전환이 트리거되고 기기가 지오펜싱에서 나갈 때 GEOFENCE_TRANSITION_EXIT 전환이 트리거됩니다. INITIAL_TRIGGER_ENTER를 지정하면 기기가 이미 지오펜싱 내부에 있는 경우 GEOFENCE_TRANSITION_ENTER가 트리거되어야 함을 위치 서비스에 알립니다.

대부분의 경우 INITIAL_TRIGGER_DWELL을 대신 사용하여 사용자가 지오펜싱 내에서 정의된 시간 동안 정지할 때만 이벤트를 트리거하는 것이 좋을 수 있습니다. 이 방법으로 기기가 지오펜싱에 잠시 들어가고 나올 때 발생하는 많은 알림으로 생기는 '알림 스팸'을 줄일 수 있습니다. 지오펜싱에서 최상의 결과를 얻는 또 다른 전략은 최소 반경을 100미터로 설정하는 것입니다. 이렇게 하면 일반적인 Wi-Fi 네트워크의 위치 정확도를 설명하고 기기 전력 소비를 줄이는 데 도움이 됩니다.

지오펜싱 전환을 위한 broadcast receiver 정의

위치 서비스에서 전송된 Intent로 앱에서 다양한 작업을 트리거할 수 있지만 활동이나 프래그먼트를 시작해서는 되는데 그 이유는 구성요소가 사용자 작업에 대한 응답으로만 표시되기 때문입니다. 대부분의 경우 BroadcastReceiver는 지오펜싱 전환을 처리하는 좋은 방법입니다. BroadcastReceiver는 지오펜싱으로 들어가는 또는 지오펜싱에서 나오는 전환과 같은 이벤트가 발생할 때 업데이트되어 장기 실행 백그라운드 작업을 시작할 수 있습니다.

다음 스니펫은 BroadcastReceiver를 시작하는 PendingIntent 정의 방법을 보여줍니다.

Kotlin

    class MainActivity : AppCompatActivity() {

        // ...

        private val geofencePendingIntent: PendingIntent by lazy {
            val intent = Intent(this, GeofenceBroadcastReceiver::class.java)
            // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
            // addGeofences() and removeGeofences().
            PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
        }
    }
    

자바

    public class MainActivity extends AppCompatActivity {

        // ...

        private PendingIntent getGeofencePendingIntent() {
            // Reuse the PendingIntent if we already have it.
            if (geofencePendingIntent != null) {
                return geofencePendingIntent;
            }
            Intent intent = new Intent(this, GeofenceBroadcastReceiver.class);
            // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when
            // calling addGeofences() and removeGeofences().
            geofencePendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.
                    FLAG_UPDATE_CURRENT);
            return geofencePendingIntent;
        }
    

지오펜싱 추가

지오펜싱을 추가하려면 GeofencingClient.addGeofences() 메서드를 사용합니다. GeofencingRequest 개체와 PendingIntent를 제공하세요. 다음 스니펫은 결과 처리를 보여줍니다.

Kotlin

    geofencingClient?.addGeofences(getGeofencingRequest(), geofencePendingIntent)?.run {
        addOnSuccessListener {
            // Geofences added
            // ...
        }
        addOnFailureListener {
            // Failed to add geofences
            // ...
        }
    }
    

자바

    geofencingClient.addGeofences(getGeofencingRequest(), getGeofencePendingIntent())
            .addOnSuccessListener(this, new OnSuccessListener<Void>() {
                @Override
                public void onSuccess(Void aVoid) {
                    // Geofences added
                    // ...
                }
            })
            .addOnFailureListener(this, new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    // Failed to add geofences
                    // ...
                }
            });
    

지오펜싱 전환 처리

위치 서비스에서 사용자가 지오펜싱에 들어가거나 나오는 것을 감지하면 개발자가 지오펜싱 추가 요청에서 포함한 PendingIntent에 포함된 Intent를 전송합니다. GeofenceBroadcastReceiver와 같은 broadcast receiver는 Intent가 호출된 것을 알아채고 나면 인텐트에서 지오펜싱 이벤트를 얻고 지오펜싱 전환 유형을 결정하며 정의된 지오펜싱 중에 트리거된 것을 판단할 수 있습니다. broadcast receiver는 앱에서 백그라운드 작업 실행을 시작하도록 하거나 원하는 경우 알림을 출력으로 전송할 수 있습니다.

참고: Android 8.0(API 레벨 26) 이상에서 지오펜싱을 모니터링하는 동안 앱이 백그라운드에서 실행되고 있다면 기기에서 2분마다 지오펜싱 이벤트에 응답합니다. 이러한 응답 제한에 앱을 맞추는 방법을 알아보려면 백그라운드 위치 제한을 참조하세요.

다음 스니펫은 지오펜싱 전환이 발생할 때 알림을 게시하는 BroadcastReceiver 정의 방법을 보여줍니다. 사용자가 알림을 클릭하면 앱의 기본 활동이 표시됩니다.

Kotlin

    class GeofenceBroadcastReceiver : BroadcastReceiver() {
        // ...
        override fun onReceive(context: Context?, intent: Intent?) {
            val geofencingEvent = GeofencingEvent.fromIntent(intent)
            if (geofencingEvent.hasError()) {
                val errorMessage = GeofenceErrorMessages.getErrorString(this,
                        geofencingEvent.errorCode)
                Log.e(TAG, 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

                // Get the transition details as a String.
                val geofenceTransitionDetails = getGeofenceTransitionDetails(
                        this,
                        geofenceTransition,
                        triggeringGeofences
                )

                // Send notification and log the transition details.
                sendNotification(geofenceTransitionDetails)
                Log.i(TAG, geofenceTransitionDetails)
            } else {
                // Log the error.
                Log.e(TAG, getString(R.string.geofence_transition_invalid_type,
                        geofenceTransition))
            }
        }
    }
    

자바

    public class GeofenceBroadcastReceiver extends BroadcastReceiver {
        // ...
        protected void onReceive(Context context, Intent intent) {
            GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
            if (geofencingEvent.hasError()) {
                String errorMessage = GeofenceErrorMessages.getErrorString(this,
                        geofencingEvent.getErrorCode());
                Log.e(TAG, errorMessage);
                return;
            }

            // Get the transition type.
            int geofenceTransition = geofencingEvent.getGeofenceTransition();

            // 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.
                List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();

                // Get the transition details as a String.
                String geofenceTransitionDetails = getGeofenceTransitionDetails(
                        this,
                        geofenceTransition,
                        triggeringGeofences
                );

                // Send notification and log the transition details.
                sendNotification(geofenceTransitionDetails);
                Log.i(TAG, geofenceTransitionDetails);
            } else {
                // Log the error.
                Log.e(TAG, getString(R.string.geofence_transition_invalid_type,
                        geofenceTransition));
            }
        }
    

PendingIntent를 통해 전환 이벤트를 감지한 후 BroadcastReceiver는 지오펜싱 전환 유형을 얻고 앱에서 알림을 트리거하려고 사용하는 이벤트 중 하나인지를 테스트합니다. 이 경우에는 GEOFENCE_TRANSITION_ENTER 또는 GEOFENCE_TRANSITION_EXIT입니다. 그런 다음 서비스는 알림을 전송하고 전환 세부정보를 기록합니다.

지오펜싱 모니터링 중지

더 이상 필요하지 않거나 원하지 않을 때 지오펜싱 모니터링을 중지하면 기기의 배터리 전력 및 CPU 주기를 절약할 수 있습니다. 지오펜싱을 추가 및 삭제하는 데 사용되는 기본 활동에서 지오펜싱 모니터링을 중지할 수 있습니다. 지오펜싱을 삭제하면 즉시 중지됩니다. API는 요청 ID를 통해 또는 주어진 PendingIntent 관련 지오펜싱을 삭제하여 지오펜싱 삭제 메서드를 제공합니다.

다음 스니펫은 PendingIntent로 지오펜싱을 삭제하여 기기가 이전에 추가된 지오펜싱에 들어가거나 나올 때 발생하는 모든 추가 알림을 중지합니다.

Kotlin

    geofencingClient?.removeGeofences(geofencePendingIntent)?.run {
        addOnSuccessListener {
            // Geofences removed
            // ...
        }
        addOnFailureListener {
            // Failed to remove geofences
            // ...
        }
    }
    

자바

    geofencingClient.removeGeofences(getGeofencePendingIntent())
            .addOnSuccessListener(this, new OnSuccessListener<Void>() {
                @Override
                public void onSuccess(Void aVoid) {
                    // Geofences removed
                    // ...
                }
            })
            .addOnFailureListener(this, new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    // Failed to remove geofences
                    // ...
                }
            });
    

지오펜싱을 정기적 위치 업데이트와 같은 다른 위치 인식 기능과 결합할 수 있습니다. 자세한 내용은 이 클래스의 다른 과정을 참조하세요.

지오펜싱 권장사항 사용

이 섹션에서는 Android용 위치 API와 함께 지오펜싱을 사용하기 위한 권장사항에 대해 간략하게 설명합니다.

전력 소비 줄이기

다음 방법을 사용하여 지오펜싱을 사용하는 앱의 전력 소비를 최적화할 수 있습니다.

  • 알림 응답성을 더 높은 값으로 설정합니다. 이렇게 하면 지오펜싱 알림의 지연 시간이 증가하여 전력 소비가 개선됩니다. 예를 들어 응답성 값을 5분으로 설정하면 앱은 5분마다 한 번씩만 방문 또는 이탈 알림을 확인합니다. 낮은 값으로 설정한다고 해서 그 시간 내에 사용자에게 알려준다는 의미는 아닙니다. 예를 들어 값을 5초로 설정하면 알림을 받는 데는 좀 더 시간이 걸릴 수 있습니다.

  • 집이나 직장과 같이 사용자가 많은 시간을 보내는 위치에는 더 큰 지오펜싱 반경을 사용하세요. 큰 반경으로 전력 소비가 직접적으로 줄어들지는 않지만 앱에서 방문 또는 이탈을 확인하는 빈도를 줄여 효과적으로 전체 전력 소비가 낮아집니다.

지오펜싱의 최적 반경 선택

최상의 결과를 얻으려면 지오펜싱의 최소 반경을 100~150미터로 설정해야 합니다. Wi-Fi를 사용할 수 있는 경우 위치 정확도는 일반적으로 20~50미터입니다. 실내 위치를 사용할 수 있다면 정확도 범위가 5미터까지 작아질 수 있습니다. 지오펜싱 내부에서 실내 위치의 사용 가능 여부를 알 수 없다면 Wi-Fi 위치 정확도가 약 50미터라고 가정하세요.

Wi-Fi 위치를 사용할 수 없다면(예: 시골 지역을 운전 중일 때) 위치 정확도가 떨어집니다. 정확도 범위는 수백 미터에서 수 킬로미터까지 커질 수 있습니다. 이 같은 경우 더 큰 반경을 사용하여 지오펜싱을 만들어야 합니다.

체류 전환 유형을 사용하여 알림 스팸 줄이기

운전하며 지오펜싱을 잠시 지나갈 때 많은 알림을 받는다면 알림을 줄이는 최선의 방법은 GEOFENCE_TRANSITION_ENTER 대신 GEOFENCE_TRANSITION_DWELL 전환 유형을 사용하는 것입니다. 이 방법으로 사용자가 주어진 시간 동안 지오펜싱 내부에서 정지할 때만 체류 알림이 전송됩니다. 배회 지연 시간을 설정하여 시간을 선택할 수 있습니다.

필요한 경우에만 지오펜싱 재등록

등록된 지오펜싱은 com.google.android.gms 패키지에서 소유한 com.google.process.location 프로세스에 보관됩니다. 앱에서는 다음 이벤트를 처리하기 위해 아무것도 할 필요가 없는데 이는 시스템에서 이러한 이벤트 후에 지오펜싱을 복원하기 때문입니다.

  • Google Play 서비스가 업그레이드되었습니다.
  • 리소스 제한으로 인해 시스템에서 Google Play 서비스가 종료되었다가 다시 시작되었습니다.
  • 위치 프로세스가 비정상 종료되었습니다.

앱에서는 다음 이벤트 후에도 여전히 필요하다면 지오펜싱을 다시 등록해야 하는데 이는 시스템에서 다음과 같은 경우에 지오펜싱을 복구할 수 없기 때문입니다.

  • 기기가 재부팅되었습니다. 앱에서는 기기의 부팅 완료 작업을 수신 대기한 다음 필요한 지오펜싱을 다시 등록해야 합니다.
  • 앱이 제거되었다가 다시 설치되었습니다.
  • 앱 데이터가 삭제되었습니다.
  • Google Play 서비스 데이터가 삭제되었습니다.
  • 앱에서 GEOFENCE_NOT_AVAILABLE 알림을 수신했습니다. 이는 일반적으로 NLP(Android 네트워크 위치 제공업체)가 사용 중지된 후에 발생합니다.

지오펜싱 방문 이벤트 문제해결

기기가 지오펜싱에 들어갈 때 지오펜싱이 트리거되지 않는다면( GEOFENCE_TRANSITION_ENTER 알림이 트리거되지 않음) 먼저 이 가이드에 설명된 대로 지오펜싱이 올바르게 등록되었는지 확인합니다.

예상대로 알림이 작동하지 않는 몇 가지 이유는 다음과 같습니다.

  • 지오펜싱 내부에서 정확한 위치를 사용할 수 없거나 지오펜싱이 너무 작습니다. 대부분의 기기에서 지오펜싱 서비스는 지오펜싱 트리거를 위해 네트워크 위치만 사용합니다. 지오펜싱 서비스에서 이 방법을 사용하는 이유는 네트워크 위치가 훨씬 적은 전력을 소비하고 개별 위치를 얻는 데도 시간이 덜 걸리며 무엇보다도 실내에서 사용할 수 있기 때문입니다.
  • 기기의 Wi-Fi가 꺼져 있습니다. Wi-Fi를 켜두면 위치 정확도가 크게 개선될 수 있습니다. 따라서 Wi-Fi를 끄면 지오펜싱 반경, 기기 모델, Android 버전 등 여러 설정에 따라 애플리케이션에서 지오펜싱 알림을 전혀 받지 못할 수 있습니다. Android 4.3(API 레벨 18)부터 'Wi-Fi 검색 전용 모드' 기능을 추가하여 사용자가 Wi-Fi를 사용 중지할 수 있으면서 여전히 좋은 네트워크 위치를 얻습니다. 둘 다 사용 중지된 경우 사용자에게 메시지를 표시하고 사용자가 Wi-Fi 또는 Wi-Fi 검색 전용 모드를 사용 설정할 수 있는 바로가기를 제공하는 것이 좋습니다. SettingsClient를 사용하여 기기의 시스템 설정이 최적의 위치 감지를 위해 올바르게 구성되었는지 확인하세요.

    참고: 앱이 Android 10(API 레벨 29) 이상을 타겟팅하는 경우 앱이 시스템 앱이거나 기기 정책 컨트롤러(DPC)가 아닌 한 WifiManager.setEnabled()를 직접 호출할 수 없습니다. 대신 설정 패널을 사용하세요.

  • 지오펜싱 내부에 안정적인 네트워크 연결이 없습니다. 안정적인 데이터 연결이 없으면 알림이 생성되지 않을 수 있습니다. 이는 지오펜싱 서비스가 결과적으로 데이터 연결이 필요한 네트워크 위치 제공업체에 의존하기 때문입니다.
  • 알림이 늦을 수 있습니다. 지오펜싱 서비스는 지속적으로 위치를 쿼리하지 않으므로 알림을 받을 때 약간의 지연이 있을 수 있습니다. 일반적으로 지연 시간은 2분 미만이며 기기가 이동 중일 때는 더 적습니다. 백그라운드 위치 제한이 실행되고 있다면 지연 시간은 평균 약 2~3분입니다. 기기가 상당한 시간 동안 정지되어 있다면 지연 시간은 최대 6분까지 늘어날 수 있습니다.