> 編寫:[penkzhou](https://github.com/penkzhou) - 原文:[http://developer.android.com/training/location/geofencing.html](http://developer.android.com/training/location/geofencing.html)
地理圍欄將用戶當前位置感知和附件地點特征感知相結合。為了標示一個感興趣的位置,我們需要指定這個位置的經緯度。為了調整位置的鄰近度,需要添加一個半徑。經緯度和半徑定義一個地理圍欄,即在感興趣的位置創建一個圓形區域或者圍欄。
我們可以有多個活動的地理圍欄(限制是一個設備用戶100個)。對于每個地理圍欄,我們可以讓 Location Services 發出進入和離開事件,或者我們可以在觸發一個事件之前,指定在某個地理圍欄區域等待一段時間或者停留。通過指定一個以毫秒為單位的截止時間,我們可以限制任何一個地理圍欄的持續時間。當地理圍欄失效后,Location Services 會自動刪除這個地理圍欄。

這節課介紹如何添加和刪除地理圍欄,和用 [IntentService](http://developer.android.com/reference/android/app/IntentService.html) 監聽地理位置變化。
### 設置地理圍欄監視
請求地理圍欄監視的第一步就是設置必要的權限。在使用地理圍欄時,我們必須設置 [ACCESS_FINE_LOCATION](http://developer.android.com/reference/android/Manifest.permission.html#ACCESS_FINE_LOCATION) 權限。在應用的 manifest 文件中添加如下子節點即可:
~~~
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
~~~
如果想要用 [IntentService](http://developer.android.com/reference/android/app/IntentService.html) 監聽地理位置變化,那么還需要添加一個節點來指定服務名字。這個節點必須是 [](http://developer.android.com/guide/topics/manifest/application-element.html) 的子節點:
~~~
<application
android:allowBackup="true">
...
<service android:name=".GeofenceTransitionsIntentService"/>
<application/>
~~~
為了訪問位置 API,我們需要創建一個 Google Play services API client 的實例。想要學習如何連接 client,請見[連接Google Play Services](#)。
### 創建和添加地理圍欄
我們的應用需要用位置 API 的 builder 類來創建地理圍欄,用 convenience 類來添加地理圍欄。另外,我們可以定義一個 [PendingIntent](http://developer.android.com/reference/android/app/PendingIntent.html)(將在這節課介紹)來處理當地理位置發生遷移時,Location Services 發出的 intent。
### 創建地理圍欄對象
首先,用 [Geofence.Builder](http://developer.android.com/reference/com/google/android/gms/location/Geofence.Builder.%20%20%20%20html) 創建一個地理圍欄,設置想要的半徑,持續時間,和地理圍欄遷移的類型。例如,填充一個叫做 `mGeofenceList` 的 list 對象:
~~~
mGeofenceList.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](http://developer.android.com/reference/com/google/android/gms/location/GeofencingRequest.html) 類。該類嵌套了 [GeofencingRequestBuilder](http://developer.android.com/reference/com/google/android/gms/location/GeofencingRequest.Builder.html) 類來需要監視的地理圍欄和設置如何觸發地理圍欄事件:
~~~
private GeofencingRequest getGeofencingRequest() {
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
builder.addGeofences(mGeofenceList);
return builder.build();
}
~~~
這個例子介紹了兩個地理圍欄觸發器。當設備進入一個地理圍欄時, [GEOFENCE_TRANSITION_ENTER](http://developer.android.com/reference/com/google/android/gms/location/Geofence.html#GEOFENCE_TRANSITION_ENTER) 轉移會觸發。當設備離開一個地理圍欄時, [GEOFENCE_TRANSITION_EXIT](http://developer.android.com/reference/com/google/android/gms/location/Geofence.html#GEOFENCE_TRANSITION_EXIT) 轉移會觸發。如果設備已經在地理圍欄里面,那么指定 [INITIAL_TRIGGER_ENTER](http://developer.android.com/reference/com/google/android/gms/location/GeofencingRequest.html#INITIAL_TRIGGER_ENTER) 來通知位置服務觸發 [GEOFENCE_TRANSITION_ENTER](http://developer.android.com/reference/com/google/android/gms/location/Geofence.html#GEOFENCE_TRANSITION_ENTER)。
在很多情況下,使用 [INITIAL_TRIGGER_DWELL](http://developer.android.com/reference/com/google/android/gms/location/GeofencingRequest.html#INITIAL_TRIGGER_DWELL) 可能會更好。僅僅當由于到達地理圍欄中已定義好的持續時間,而導致用戶停止時,[INITIAL_TRIGGER_DWELL](http://developer.android.com/reference/com/google/android/gms/location/GeofencingRequest.html#INITIAL_TRIGGER_DWELL) 才會觸發事件。這個方法可以減少當設備短暫地進入和離開地理圍欄時,由大量的通知造成的“垃圾警告信息”。另一種獲取最好的地理圍欄結果的策略是設置最小半徑為100米。這有助于估計典型的 Wifi 網絡的位置精確度,也有利于降低設備的功耗。
### 為地理圍欄轉移定義Intent
從 Location Services 發送來的Intent能夠觸發各種應用內的動作,但是不能用它來打開一個 [Activity](# "An activity represents a single screen with a user interface.") 或者 Fragment,這是因為應用內的組件只能在響應用戶動作時才可見。大多數情況下,處理這一類 Intent 最好使用 [IntentService](http://developer.android.com/reference/android/app/IntentService.html)。一個 [IntentService](http://developer.android.com/reference/android/app/IntentService.html) 可以推送一個通知,可以進行長時間的后臺作業,可以將 intent 發送給其他的 services ,還可以發送一個廣播 intent。下面的代碼展示了如何定義一個 [PendingIntent](http://developer.android.com/reference/android/app/PendingIntent.html) 來啟動一個 [IntentService](http://developer.android.com/reference/android/app/IntentService.html):
~~~
public class MainActivity extends FragmentActivity {
...
private PendingIntent getGeofencePendingIntent() {
// Reuse the PendingIntent if we already have it.
if (mGeofencePendingIntent != null) {
return mGeofencePendingIntent;
}
Intent intent = new Intent(this, GeofenceTransitionsIntentService.class);
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when
// calling addGeofences() and removeGeofences().
return PendingIntent.getService(this, 0, intent, PendingIntent.
FLAG_UPDATE_CURRENT);
}
~~~
### 添加地理圍欄
使用 [GeoencingApi.addGeofences()](http://developer.android.com/reference/com/google/android/gms/location/GeofencingApi.html#addGeofences(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.location.GeofencingRequest, android.app.PendingIntent)) 方法來添加地理圍欄。為該方法提供 Google API client,[GeofencingRequest](http://developer.android.com/reference/com/google/android/gms/location/GeofencingRequest) 對象和 [PendingIntent](http://developer.android.com/reference/android/app/PendingIntent.html)。下面的代碼,在 [onResult()](http://developer.android.com/reference/com/google/android/gms/common/api/ResultCallback.html#onResult(R)) 中處理結果,假設主 [activity](# "An activity represents a single screen with a user interface.") 實現 [ResultCallback](http://developer.android.com/reference/com/google/android/gms/common/api/ResultCallback.html)。
~~~
public class MainActivity extends FragmentActivity {
...
LocationServices.GeofencingApi.addGeofences(
mGoogleApiClient,
getGeofencingRequest(),
getGeofencePendingIntent()
).setResultCallback(this);
~~~
### 處理地理圍欄轉移
當 Location Services 探測到用戶進入或者離開一個地理圍欄,它會發送一個包含在 [PendingIntent](http://developer.android.com/reference/android/app/PendingIntent.html) 的 Intent,這個 [PendingIntent](http://developer.android.com/reference/android/app/PendingIntent.html) 就是在添加地理圍欄時被我們包括在請求當中。這個 Intent 被一個類似 `GeofenceTransitionsIntentService` 的 service 接收,這個 service 從 intent 得到地理圍欄事件,決定地理圍欄轉移的類型,和決定觸發哪個已定義的地理圍欄。然后它會發出一個通知。
下面的代碼介紹了如何定義一個 IntentService。這個 IntentService 在地理圍欄轉移出現時,會推送一個通知。當用戶點擊這個通知,那么應用的主 [activity](# "An activity represents a single screen with a user interface.") 會出現:
~~~
public class GeofenceTransitionsIntentService extends IntentService {
...
protected void onHandleIntent(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 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 檢測轉移事件之后,這個 IntentService 獲取地理圍欄轉移類型和測試一個事件是不是應用用來觸發通知的 —— 要么是 GEOFENCE_TRANSITION_ENTER,要么是 GEOFENCE_TRANSITION_EXIT。然后,這個 service 會發出一個通知并且記錄轉移的詳細信息。
### 停止地理圍欄監視
當不再需要監視地理圍欄或者想要節省設備的電池電量和 CPU 周期時,需要停止地理圍欄監視。我們可以在用于添加和刪除地理圍欄的主 [activity](# "An activity represents a single screen with a user interface.") 里停止地理圍欄監視;刪除地理圍欄會導致它馬上停止。API 要么通過 request IDs,要么通過刪除與指定 PendingIntent 相關的地理圍欄來刪除地理圍欄。
下面的代碼通過 PendingIntent 刪除地理圍欄,當設備進入或者離開之前已經添加的地理圍欄時,停止所有通知:
~~~
LocationServices.GeofencingApi.removeGeofences(
mGoogleApiClient,
// This is the same pending intent that was used in addGeofences().
getGeofencePendingIntent()
).setResultCallback(this); // Result processed in onResult().
}
~~~
你可以將地理圍欄同其他位置感知的特性結合起來,比如周期性的位置更新。像要了解更多的信息,請看本章的其它課程。
- 序言
- Android入門基礎:從這里開始
- 建立第一個App
- 創建Android項目
- 執行Android程序
- 建立簡單的用戶界面
- 啟動其他的Activity
- 添加ActionBar
- 建立ActionBar
- 添加Action按鈕
- 自定義ActionBar的風格
- ActionBar的覆蓋層疊
- 兼容不同的設備
- 適配不同的語言
- 適配不同的屏幕
- 適配不同的系統版本
- 管理Activity的生命周期
- 啟動與銷毀Activity
- 暫停與恢復Activity
- 停止與重啟Activity
- 重新創建Activity
- 使用Fragment建立動態的UI
- 創建一個Fragment
- 建立靈活動態的UI
- Fragments之間的交互
- 數據保存
- 保存到Preference
- 保存到文件
- 保存到數據庫
- 與其他應用的交互
- Intent的發送
- 接收Activity返回的結果
- Intent過濾
- Android分享操作
- 分享簡單的數據
- 給其他App發送簡單的數據
- 接收從其他App返回的數據
- 給ActionBar增加分享功能
- 分享文件
- 建立文件分享
- 分享文件
- 請求分享一個文件
- 獲取文件信息
- 使用NFC分享文件
- 發送文件給其他設備
- 接收其他設備的文件
- Android多媒體
- 管理音頻播放
- 控制音量與音頻播放
- 管理音頻焦點
- 兼容音頻輸出設備
- 拍照
- 簡單的拍照
- 簡單的錄像
- 控制相機硬件
- 打印
- 打印照片
- 打印HTML文檔
- 打印自定義文檔
- Android圖像與動畫
- 高效顯示Bitmap
- 高效加載大圖
- 非UI線程處理Bitmap
- 緩存Bitmap
- 管理Bitmap的內存
- 在UI上顯示Bitmap
- 使用OpenGL ES顯示圖像
- 建立OpenGL ES的環境
- 定義Shapes
- 繪制Shapes
- 運用投影與相機視圖
- 添加移動
- 響應觸摸事件
- 添加動畫
- View間漸變
- 使用ViewPager實現屏幕側滑
- 展示卡片翻轉動畫
- 縮放View
- 布局變更動畫
- Android網絡連接與云服務
- 無線連接設備
- 使得網絡服務可發現
- 使用WiFi建立P2P連接
- 使用WiFi P2P服務
- 執行網絡操作
- 連接到網絡
- 管理網絡
- 解析XML數據
- 高效下載
- 為網絡訪問更加高效而優化下載
- 最小化更新操作的影響
- 避免下載多余的數據
- 根據網絡類型改變下載模式
- 云同步
- 使用備份API
- 使用Google Cloud Messaging
- 解決云同步的保存沖突
- 使用Sync Adapter傳輸數據
- 創建Stub授權器
- 創建Stub Content Provider
- 創建Sync Adpater
- 執行Sync Adpater
- 使用Volley執行網絡數據傳輸
- 發送簡單的網絡請求
- 建立請求隊列
- 創建標準的網絡請求
- 實現自定義的網絡請求
- Android聯系人與位置信息
- Android聯系人信息
- 獲取聯系人列表
- 獲取聯系人詳情
- 使用Intents修改聯系人信息
- 顯示聯系人頭像
- Android位置信息
- 獲取最后可知位置
- 獲取位置更新
- 顯示位置地址
- 創建和監視地理圍欄
- Android可穿戴應用
- 賦予Notification可穿戴特性
- 創建Notification
- 在Notifcation中接收語音輸入
- 為Notification添加顯示頁面
- 以Stack的方式顯示Notifications
- 創建可穿戴的應用
- 創建并運行可穿戴應用
- 創建自定義的布局
- 添加語音功能
- 打包可穿戴應用
- 通過藍牙進行調試
- 創建自定義的UI
- 定義Layouts
- 創建Cards
- 創建Lists
- 創建2D-Picker
- 創建確認界面
- 退出全屏的Activity
- 發送并同步數據
- 訪問可穿戴數據層
- 同步數據單元
- 傳輸資源
- 發送與接收消息
- 處理數據層的事件
- Android TV應用
- 創建TV應用
- 創建TV應用的第一步
- 處理TV硬件部分
- 創建TV的布局文件
- 創建TV的導航欄
- 創建TV播放應用
- 創建目錄瀏覽器
- 提供一個Card視圖
- 創建詳情頁
- 顯示正在播放卡片
- 幫助用戶在TV上探索內容
- TV上的推薦內容
- 使得TV App能夠被搜索
- 使用TV應用進行搜索
- 創建TV游戲應用
- 創建TV直播應用
- TV Apps Checklist
- Android企業級應用
- Ensuring Compatibility with Managed Profiles
- Implementing App Restrictions
- Building a Work Policy Controller
- Android交互設計
- 設計高效的導航
- 規劃屏幕界面與他們之間的關系
- 為多種大小的屏幕進行規劃
- 提供向下和橫向導航
- 提供向上和歷史導航
- 綜合:設計樣例 App
- 實現高效的導航
- 使用Tabs創建Swipe視圖
- 創建抽屜導航
- 提供向上的導航
- 提供向后的導航
- 實現向下的導航
- 通知提示用戶
- 建立Notification
- 當啟動Activity時保留導航
- 更新Notification
- 使用BigView風格
- 顯示Notification進度
- 增加搜索功能
- 建立搜索界面
- 保存并搜索數據
- 保持向下兼容
- 使得你的App內容可被Google搜索
- 為App內容開啟深度鏈接
- 為索引指定App內容
- Android界面設計
- 為多屏幕設計
- 兼容不同的屏幕大小
- 兼容不同的屏幕密度
- 實現可適應的UI
- 創建自定義View
- 創建自定義的View類
- 實現自定義View的繪制
- 使得View可交互
- 優化自定義View
- 創建向后兼容的UI
- 抽象新的APIs
- 代理至新的APIs
- 使用舊的APIs實現新API的效果
- 使用版本敏感的組件
- 實現輔助功能
- 開發輔助程序
- 開發輔助服務
- 管理系統UI
- 淡化系統Bar
- 隱藏系統Bar
- 隱藏導航Bar
- 全屏沉浸式應用
- 響應UI可見性的變化
- 創建使用Material Design的應用
- 開始使用Material Design
- 使用Material的主題
- 創建Lists與Cards
- 定義Shadows與Clipping視圖
- 使用Drawables
- 自定義動畫
- 維護兼容性
- Android用戶輸入
- 使用觸摸手勢
- 檢測常用的手勢
- 跟蹤手勢移動
- Scroll手勢動畫
- 處理多觸摸手勢
- 拖拽與縮放
- 管理ViewGroup中的觸摸事件
- 處理鍵盤輸入
- 指定輸入法類型
- 處理輸入法可見性
- 兼容鍵盤導航
- 處理按鍵動作
- 兼容游戲控制器
- 處理控制器輸入動作
- 支持不同的Android系統版本
- 支持多個控制器
- Android后臺任務
- 在IntentService中執行后臺任務
- 創建IntentService
- 發送工作任務到IntentService
- 報告后臺任務執行狀態
- 使用CursorLoader在后臺加載數據
- 使用CursorLoader執行查詢任務
- 處理查詢的結果
- 管理設備的喚醒狀態
- 保持設備的喚醒
- 制定重復定時的任務
- Android性能優化
- 管理應用的內存
- 代碼性能優化建議
- 提升Layout的性能
- 優化layout的層級
- 使用include標簽重用layouts
- 按需加載視圖
- 使得ListView滑動順暢
- 優化電池壽命
- 監測電量與充電狀態
- 判斷與監測Docking狀態
- 判斷與監測網絡連接狀態
- 根據需要操作Broadcast接受者
- 多線程操作
- 在一個線程中執行一段特定的代碼
- 為多線程創建線程池
- 啟動與停止線程池中的線程
- 與UI線程通信
- 避免出現程序無響應ANR
- JNI使用指南
- 優化多核處理器(SMP)下的Android程序
- Android安全與隱私
- Security Tips
- 使用HTTPS與SSL
- 為防止SSL漏洞而更新Security
- 使用設備管理條例增強安全性
- Android測試程序
- 測試你的Activity
- 建立測試環境
- 創建與執行測試用例
- 測試UI組件
- 創建單元測試
- 創建功能測試
- 術語表