建立及監控地理圍欄

地理圍欄功能結合了對使用者目前位置的認知,以及對使用者可能感興趣的鄰近地點的認知。如要標示喜好地點,請指定該地點的經緯度。如要調整該位置的鄰近地點,您必須新增半徑。緯度、經度和半徑定義了地理圍欄,在喜好地點的周圍建立一個圓形區域或圍欄。

您可以設定多個有效的地理圍欄,單一應用程式為每位裝置使用者設置的地理圍欄數量上限為 100 個。對於每項 地理圍欄,可以要求定位服務傳送進入和離開事件給您,或指定 再於地理圍欄區域內等待的時間 (或稱「停留」),才會觸發事件。您可以指定到期時間 (以毫秒為單位),藉此限制任何地理圍欄的持續時間。地理圍欄到期後,定位服務會自動將它移除。

本課程將說明如何新增及移除地理圍欄,然後使用 BroadcastReceiver 監聽地理圍欄轉換作業。

注意:在 Wear 裝置上,Geofencing API 無法有效利用 電源。我們不建議在 Wear 上使用這些 API。已讀 節省電力和電池,瞭解更多資訊。

設定地理圍欄監控

要求地理圍欄監控的第一步是要求必要權限。如要使用地理圍欄,您的應用程式必須要求下列項目:

詳情請參閱如何要求位置存取權相關指南。

如果您想使用 BroadcastReceiver 監聽地理圍欄轉換作業,請新增指定服務名稱的元素。這個元素必須是 <application> 元素的子項:

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

如要存取 Location API,您必須建立 Geofencing 用戶端的例項。如要瞭解如何連接用戶端:

Kotlin

lateinit var geofencingClient: GeofencingClient

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

Java

private GeofencingClient geofencingClient;

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

建立及新增地理圍欄

您的應用程式必須使用 Location 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())

Java

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()
}

Java

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 網路的位置準確度,也有助於降低裝置耗電量。

定義地理圍欄轉換作業的廣播接收器

從定位服務傳來的 Intent 可在應用程式中觸發各種動作,但「不」應用來啟動活動或片段,原因是各種元件只應在回應使用者動作時顯示在畫面上。在多數情況下,BroadcastReceiver 是處理地理圍欄轉換作業的絕佳方式。BroadcastReceiver 會在事件發生 (例如進入或退出地理圍欄的轉換作業) 時接收更新,並且可以啟動長時間執行的背景工作。

下列程式碼片段說明如何定義用於啟動 BroadcastReceiverPendingIntent

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)
    }
}

Java

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
        // ...
    }
}

Java

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 會注意到已叫用 Intent,並 然後從意圖取得地理圍欄事件,並判斷地理圍欄轉換作業的類型, 並判斷觸發了哪些已定義的地理圍欄。廣播 接收端可以引導應用程式開始執行背景工作,或者 並在輸出結果中傳送通知。

注意:在 Android 8.0 (API 級別 26) 以上版本中,如有應用程式 正在監控地理圍欄,然後是裝置 每隔幾分鐘就會回應地理圍欄事件。如要瞭解如何根據這些回應限制調整應用程式,請參閱背景位置資訊限制一文。

下列程式碼片段說明如何定義 BroadcastReceiver,以便在發生地理圍欄轉換作業時張貼通知。使用者點選通知時,系統會顯示應用程式的主要活動:

Kotlin

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(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))
        }
    }
}

Java

public class GeofenceBroadcastReceiver extends BroadcastReceiver {
    // ...
    protected void onReceive(Context context, Intent intent) {
        GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
        if (geofencingEvent.hasError()) {
            String errorMessage = GeofenceStatusCodes
                    .getStatusCodeString(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_ENTERGEOFENCE_TRANSITION_EXIT)。接著,服務會傳送通知並記錄轉換詳細資料。

停止地理圍欄監控

如果不再需要,可以停止地理圍欄監控,有助於節省電力 以及裝置的 CPU 循環您可以停止監控地理圍欄 在用於新增及移除地理圍欄的主要活動中;地理圍欄移除之後,地理圍欄就會阻止 立即生效API 提供的方法可依據要求 ID 移除地理圍欄,也可移除與指定 PendingIntent 相關聯的地理圍欄。

下列程式碼片段會依據 PendingIntent 移除地理圍欄,藉此在裝置進入或離開先前新增的地理圍欄時,停止所有後續通知:

Kotlin

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

Java

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 專用的 Location API 使用地理圍欄時的建議做法。

降低耗電量

您可以運用下列技巧,針對使用地理圍欄的應用程式耗電量進行最佳化:

  • 通知回應時間設為較高的值。這麼做可加長地理圍欄快訊的延遲時間,藉此降低耗電量。舉例來說,假設您將回應時間值設為五 則應用程式每 5 分鐘只會檢查一次進入或離開事件快訊。 設定較低的值不一定代表使用者會在這段時間內收到通知 (例如,將這個值設為 5 秒,則可能需要更長一點的時間才能收到快訊)。

  • 針對使用者會花費大量時間的地點,使用較大的地理圍欄半徑。 例如住家或公司較大的半徑範圍不會直接降低耗電量,卻能減少 應用程式檢查進入或離開的頻率,可有效降低整體電力 提高用量上限

為地理圍欄選擇最合適的半徑範圍

為獲得最佳成果,地理圍欄的最小半徑範圍應設為介於 100 至 150 公尺之間。 如果能夠連上 Wi-Fi 網路,則定位精確度通常會介於 20 至 50 公尺之間。要是能提供室內位置資訊,則精確度範圍可縮小至 5 公尺。除非您知道系統可提供地理圍欄內的室內位置資訊,否則請假定 Wi-Fi 定位精確度約為 50 公尺。

如果系統無法提供 Wi-Fi 位置資訊 (例如在鄉村地區開車時),定位精確度就會降低。精確度範圍可延伸到數百公尺至 幾公里。在此情況下,您應使用較大的半徑範圍來建立地理圍欄。

向使用者說明應用程式使用地理圍欄的原因

由於應用程式會於使用地理圍欄時在背景存取位置資訊,請考慮應用程式可為使用者提供哪些好處。向他們清楚說明 您的應用程式需要這項存取權,讓使用者更容易理解,同時也能提升資訊透明度。

如要進一步瞭解位置存取權相關最佳做法, 包括地理圍欄,詳情請參閱隱私權最佳做法 做法一文。

利用停留轉換類型來減少垃圾快訊

如果您在開車短暫經過地理圍欄時收到大量警示,最佳作法是 減少快訊是使用 GEOFENCE_TRANSITION_DWELL 轉換類型,而不是 GEOFENCE_TRANSITION_ENTER。這樣一來,只有在使用者於地理圍欄內停留一段指定時間後,系統才會傳送停留快訊。您可以透過設定滯留延遲來選擇時間長度。

僅在必要情況下重新註冊地理圍欄

已註冊的地理圍欄會保存在 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) 或 您無法直接呼叫 WifiManager.setEnabled(),除非 應用程式為系統應用程式或裝置政策 控制器 (DPC)。而必須改用設定面板

  • 地理圍欄內沒有穩定可靠的網路連線。沒有穩定可靠的數據連線,系統就可能無法產生快訊。這是因為地理圍欄服務必須仰賴網路位置供應商才能順利運作,而網路位置供應商則需要使用數據連線。
  • 快訊可能會延遲。地理圍欄服務不會持續查詢以下項目: 所以接收快訊的時間稍有延遲延遲時間通常小於 2 時間。如果 背景說明 位置資訊限制有效,平均延遲時間約為 2 至 3 分鐘。 如果裝置長時間處於靜止狀態,延遲時間可能會加長 (最多 6 分鐘)。

其他資源

如要進一步瞭解地理圍欄,請參閱下列資料:

範例

範例應用程式建立及監控地理圍欄。