1.整合裝置位置
1.1 問題:
要在應用程式在使用裝置的定位功能報告當前的物理位置。
1.2 解決方案
(API Level 9)
可利用Google在其Play Service 庫中提供的混合位置提供程式。移動應用程式通常提供給使用者的最強大優勢之一就是能夠包括基於使用者目前所在位置的資訊來新增上下文。應用程式可能要求位置服務基於如下標準提供裝置位置的更新:
- 效率:在將另一個更新提供給應用程式之前經過的最小時間量,單位為毫秒。
- 距離:在提供另一個更新之前裝置必須移動的最小距離。
- 計數:在關閉提供程式之前提供的更大更新數。
- 到期時間:啟動請求之後和關閉提供程式之前的最大時間量。
注意:
Android也在LocationManager中直接提供了非Google位置服務。此API幫助不大,開發人員必須單獨管理來自離散位置源的請求。
Android允許應用程式從多個來源獲得位置資料。高精度(同時也是高能耗)的位置修正資料來自裝置上的GPS,而低精度的資料是通過網路源獲得的,如蜂窩網路基站塔和Wi-Fi熱點。Google的混合位置提供程式因為如下原因而得名:將這些多個來源“混合”在一起,隨時向開發人員提供最佳的結果。使用高級別的標準發出位置請求,由裝置決定應該該呼叫哪些硬體介面:
- PRIORITY_BALANCED_POWER_ACCURACY:提供GPS和網路源的混合,以便通過較低的能耗獲得較為精確的資料,並且更快速地進行修正。
- PRIORITY_HIGH_ACCURACY:雖然初步的修正可能來自其他地方,但要求最終的修正資料來自GPS(最精確)。這個選項也最為耗電,因為GPS通常一直保持開啟。
- PRIORITY_LOW_POWER:
- PRIORITY_NO_POWER:使應用程式成為主動的觀察者。僅在另一個應用程式觸發時才提供更新。
要點:
本例中的位置服務是作為Google+Play/">Google Play Serivices庫的一部分進行分發的,它在任意平臺級別都不是原生SDK的一部分。然而,目標平臺為API Level 9或以後版本的應用程式以及Google Play體系內的裝置都可以使用此繪相簿。
1.3 實現機制
在下面程式碼清單中,註冊一個Activity來監聽對使用者可見的位置更新,並且將位置資訊顯示在螢幕上。
注意:
本例使用來自AppCompat庫的ActionBarActivity,從而可以使用API Level 11 之前的分段。
監控位置更新的Activity
public class MainActivity extends ActionBarActivity implements GooglePlayServicesClient.ConnectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener, com.google.android.gms.location.LocationListener { private static final String TAG = "AndroidRecipes"; private static final int UPDATE_INTERVAL = 15 * 1000; private static final int FASTEST_UPDATE_INTERVAL = 2 * 1000; /*Play Services 的客戶端介面*/ private LocationClient mLocationClient; /* 我們想要接收的相關元資料*/ private LocationRequest mLocationRequest; /* 最近已知的裝置位置 */ private Location mCurrentLocation; private TextView mLocationView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mLocationView = new TextView(this); setContentView(mLocationView); //確認play services已啟用並保持更新 int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this); switch (resultCode) { case ConnectionResult.SUCCESS: Log.d(TAG, "Google Play Services is ready to go!"); break; default: showPlayServicesError(resultCode); return; } //新增位置更新監控 mLocationClient = new LocationClient(this, this, this); mLocationRequest = LocationRequest.create() //設定所需的精確級別 .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) //設定所需的(不精確的) 的位置更新頻率 .setInterval(UPDATE_INTERVAL) //限制更新請求的最大速率 .setFastestInterval(FASTEST_UPDATE_INTERVAL); } @Override public void onResume() { super.onResume(); //在移入前臺時,附加到 Play Services mLocationClient.connect(); } @Override public void onPause() { super.onPause(); //不在前臺時禁止更新 if (mLocationClient.isConnected()) { mLocationClient.removeLocationUpdates(this); } //從Play Services取消附加 mLocationClient.disconnect(); } private void updateDisplay() { if(mCurrentLocation == null) { mLocationView.setText("Determining Your Location..."); } else { mLocationView.setText(String.format("Your Location:\n%.2f, %.2f", mCurrentLocation.getLatitude(), mCurrentLocation.getLongitude())); } } /** Play Services的位置 */ /* * 當 Play Services 缺失或版本不準確時, * 客戶端將以對話方塊的形式幫助使用者進行更新 */ private void showPlayServicesError(int errorCode) { // Get the error dialog from Google Play services Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog( errorCode, this, 1000 /* RequestCode */); //如果 Google Play services可以提供錯誤對話方塊 if (errorDialog != null) { // 為錯誤對話方塊建立的DialogFragment SupportErrorDialogFragment errorFragment = SupportErrorDialogFragment.newInstance(errorDialog); // 在 DialogFragment中顯示錯誤對話方塊 errorFragment.show( getSupportFragmentManager(), "Location Updates"); } } @Override public void onConnected(Bundle bundle) { Log.d(TAG, "Connected to Play Services"); //立即獲得最近已知的位置 mCurrentLocation = mLocationClient.getLastLocation(); //註冊更新 mLocationClient.requestLocationUpdates(mLocationRequest, this); } @Override public void onDisconnected() { } @Override public void onConnectionFailed(ConnectionResult connectionResult) { } /** LocationListener 回撥 */ @Override public void onLocationChanged(Location location) { Log.d(TAG, "Received location update"); mCurrentLocation = location; updateDisplay(); } }
要點:
在應用程式中使用位置服務時,記住需要在應用程式的清單中宣告android.permission.ACCESS_COARSE_LOCATION或android.permission.ACCESS_FINE_LOCATION許可權。如果聲明瞭android.permission.ACCESS_FINE_LOCATION許可權,就不必宣告另一個許可權,因為它已經包含了模糊位置服務的許可權。
這個示例構造一個LocationRequest,其中包含希望應用於所接收更新的所有標準。我們指示該服務以15秒的間隔僅返回高度精確的結果。這種間隔是不準確的,即Android大約每隔15秒傳遞更新,這個時間可多可少。唯一的保證是,在此間隔內最多隻會收到一個更新。未設定此間隔的上限,我們還設定最快間隔為兩秒。這會限制更新,確保我們不會在快於兩秒的間隔內收到兩個更新。
收集位置資料是一個資源密集型操作,因此要確保僅在Activity位於前臺時才執行此操作。我們的示例會等待直到onResume()連線位置服務,並在onPause()中立即斷開連線。Google Play Services是非同步的,這意味著必須連線並等待回撥,然後才可以執行真正的設定工作。直到建立指向Play Services的連線之後,才可以訪問LocationClient。
如果LocationClient.connect()在後面嘗試觸發onConnected(),我們就可以使用前面通過requestLocationUpdates()構造的請求來開始請求更新。如果我們需要在獲得另一個更新之前引用資料,則此時也可以通過getLastLocation()獲得最近已知的位置。如果最近沒有進行修正,則該值可以返回null。
模擬位置改變
如果使用Android模擬器測試應用程式的話,應用程式將不能從任何系統提供程式中接收真實的位置資料資訊。但是,通過SDK的Monitor工具,可以手動地注入位置改變事件。
在啟用DDMS的情況下,選擇Emulator Control選項卡並查詢Location Controls部分。在選項卡式的介面中可以直接輸入經度/緯度對,或者通過命令列檔案格式讀取一系列經度/緯度對。
在手動輸入單個值時,必須在文字框中輸入有效的經度和緯度。然後可以單擊Send按鈕,作為所選模擬器的事件注入該位置。註冊以偵聽位置改變的任何應用程式也會收到包含此位置值的更新。
當位置更新到達時,會呼叫已註冊偵聽器的onLocationChanged()方法。該例持續引用其所接收的最新位置,對於每個傳入的更新,重置位置值,並且更新使用者介面顯示以反映新的更改。
注意:
如果在服務或其他後臺操作中接收位置更新,Google建議最小時間間隔應不小於60 000 (60秒)。
為使此應用程式正在構建和執行,我們需要額外關聯一些檔案。以下兩段清單程式碼描述了這些需求。
build.gradle的部分程式碼
apply plugin: 'com.android.application' android { ... } ... } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.google.android.gms:play-services:6.1.+' compile 'com.android.support:appcompat-v7:21.0.+' }
AndroidManifest.xml的部分程式碼
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.examples.mylocation"> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <application...> <!-- 需要樣板檔案以啟動 Play Services --> <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> <activity android:name=".MainActivity" android:label="@string/app_name" android:theme="@style/Theme.AppCompat.Light.DarkActionBar"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
在專案build.gradle檔案內,必須將Google Play Services新增為依賴項。在此我們使用加號來確保始終通過最新的客戶端庫進行構建。Play Services也要要求向具有客戶端版本的應用程式新增<meta-data>元素,客戶端使用此元素確定裝置上的Play Services版本目前是滿足要求。版本號資源內置於庫中,因此無須進行定義。
我們正在訪問裝置的位置服務,因此還需要請求ACCESS_FINE_LOCATION許可權,這是訪問GPS位置服務的必需許可權。如果應用程式使用較低精確度的值,就可以不需要請求ACCESS_FINE_LOCATION許可權。
Google Play Services不可用時的情況
在此例的頂部(見程式碼清單:監控位置更新的Activity)有一些樣板程式碼,用於使用GooglePlayServicesUtil的isGooglePlayServicesAvailable()檢測裝置上的Google Play Services 正在執行且保持更新。該方法將確認Play Services正在裝置上執行,並且最少為客戶端應用程式中指定的版本。
如果此檢查失敗,Play Services也以ErrorDialogFragment的形式提供了UI,向用戶顯示此UI以更新 Play Services。此對話方塊將觸發適當的設定UI,從而自動將 Play Services更新到最新版本。在我們的示例中,showPlayServicesError()方法負責處理此工作。