注:本文翻譯自Google官方的Android Developers Training文檔,譯者技術一般,由于喜愛安卓而產生了翻譯的念頭,純屬個人興趣愛好。
原文鏈接: http://developer.android.com/training/location/receive-location-updates.html
如果你的應用有導航的功能,你可能會希望可以定期獲取用戶的地理位置。雖然你可以通過 LocationClient.getLastLocation() 做到這一點,但是一個更加直接的方法是向定位服務申請定期更新。作為響應,定位服務會自動用最佳的地理位置信息(基于當前激活的可以提供位置信息的傳感器,如WiFi或者GPS)更新到你的應用。
要從定位服務定期獲取地理位置更新,你使用定位客戶端發送一個請求。根據請求的形式,定位服務或是激活一個回調函數,并把一個 Location 對象傳遞給該函數,或是發送一個 Intent ,在其數據部分包含了地理位置信息。有兩方面因素會影響精度和頻率,一個是你的應用申請的定位權限,一個是你在請求中傳遞給定位服務的參數。
一). 指定應用權限
使用位置服務的應用必須請求定位權限。Android有兩個定位權限: ACCESS_COARSE_LOCATION (粗定位)和 ACCESS_FINE_LOCATION (精定位)。你所選擇的權限決定了定位的精度。如果你只請求粗定位,位置服務所范圍的地點信息大致會精確到一個城市街區。
如果請求 ACCESS_FINE_LOCATION ,它也暗含了 ACCESS_COARSE_LOCATION 的權限。
例如,要添加 ACCESS_COARSE_LOCATION ,將下面的代碼作為 <manifest> 元素的子元素:
< uses-permission android:name ="android.permission.ACCESS_COARSE_LOCATION" />
二). 檢查Google Play服務
位置服務是Google Play服務APK的其中一部分。由于用戶設備的狀態時難以預料的,你應該一直在你嘗試連接定位服務之前,檢查APK是否已經安裝。要檢查APK是否安裝,可以調用 GooglePlayServicesUtil.isGooglePlayServicesAvailable() ,它會返回一個整形的結果碼,其含義可以參閱: ConnectionResult 。如果你遇到了一個錯誤,可以調用 GooglePlayServicesUtil.getErrorDialog() ,來獲取一個本地的對話框,引導用戶執行正確地行為,之后將這一對話框顯示在一個 DialogFragment 上。這一對話框可能允許用戶解決當前的問題,此時Google Play服務會發回一個結果到你的activity中。要處理這一結果,需要覆寫 onActivityResult() 方法。
Note:
要使你的應用可以兼容1.6及以后版本的系統,顯示 DialogFragment 的activity必須是 FragmentActivity 的子類,而非 Activity 。使用 FragmentActivity 還可以允許你調用 getSupportFragmentManager() 方法來顯示 DialogFragment 。
由于你一直需要在你的代碼多個地方檢查Google Play服務,所以應該定義一個方法將檢查行為進行封裝,之后在每次連接嘗試之前進行檢查。下面的代碼片段包含了檢查Google Play服務所需要的代碼:
public class MainActivity extends FragmentActivity { ... // Global constants /* * Define a request code to send to Google Play services * This code is returned in Activity.onActivityResult */ private final static int CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000 ; ... // Define a DialogFragment that displays the error dialog public static class ErrorDialogFragment extends DialogFragment { // Global field to contain the error dialog private Dialog mDialog; // Default constructor. Sets the dialog field to null public ErrorDialogFragment() { super (); mDialog = null ; } // Set the dialog to display public void setDialog(Dialog dialog) { mDialog = dialog; } // Return a Dialog to the DialogFragment. @Override public Dialog onCreateDialog(Bundle savedInstanceState) { return mDialog; } } ... /* * Handle results returned to the FragmentActivity * by Google Play services */ @Override protected void onActivityResult( int requestCode, int resultCode, Intent data) { // Decide what to do based on the original request code switch (requestCode) { ... case CONNECTION_FAILURE_RESOLUTION_REQUEST : /* * If the result code is Activity.RESULT_OK, try * to connect again */ switch (resultCode) { case Activity.RESULT_OK : /* * Try the request again */ ... break ; } ... } ... } ... private boolean servicesConnected() { // Check that Google Play services is available int resultCode = GooglePlayServicesUtil. isGooglePlayServicesAvailable( this ); // If Google Play services is available if (ConnectionResult.SUCCESS == resultCode) { // In debug mode, log the status Log.d("Location Updates" , "Google Play services is available." ); // Continue return true ; // Google Play services was not available for some reason } else { // Get the error code int errorCode = connectionResult.getErrorCode(); // Get the error dialog from Google Play services Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog( errorCode, this , CONNECTION_FAILURE_RESOLUTION_REQUEST); // If Google Play services can provide an error dialog if (errorDialog != null ) { // Create a new DialogFragment for the error dialog ErrorDialogFragment errorFragment = new ErrorDialogFragment(); // Set the dialog in the DialogFragment errorFragment.setDialog(errorDialog); // Show the error dialog in the DialogFragment errorFragment.show( getSupportFragmentManager(), "Location Updates" ); } } } ... }
在后續章節的代碼片段中,都會調用這一方法來驗證是否可獲取Google Play服務。
三). 定義位置服務回調函數
在你創建定位客戶端之前,實現定位服務的接口,以和你的應用進行交互:
指定當定位連接上或者沒有連接上時,定位服務調用的方法。
指定當嘗試連接到定位客戶端時,如果出現了錯誤,定位服務調用的方法。這一方法使用之前定義的 showErrorDialog 方法來顯示一個錯誤對話框,它嘗試使用Google Play服務來解決這一問題。
下面的樣例代碼展示了如何指定接口和定義相關的函數:
public class MainActivity extends FragmentActivity implements GooglePlayServicesClient.ConnectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener { ... /* * Called by Location Services when the request to connect the * client finishes successfully. At this point, you can * request the current location or start periodic updates */ @Override public void onConnected(Bundle dataBundle) { // Display the connection status Toast.makeText( this , "Connected" , Toast.LENGTH_SHORT).show(); } ... /* * Called by Location Services if the connection to the * location client drops because of an error. */ @Override public void onDisconnected() { // Display the connection status Toast.makeText( this , "Disconnected. Please re-connect." , Toast.LENGTH_SHORT).show(); } ... /* * Called by Location Services if the attempt to * Location Services fails. */ @Override public void onConnectionFailed(ConnectionResult connectionResult) { /* * Google Play services can resolve some errors it detects. * If the error has a resolution, try sending an Intent to * start a Google Play services activity that can resolve * error. */ if (connectionResult.hasResolution()) { try { // Start an Activity that tries to resolve the error connectionResult.startResolutionForResult( this , CONNECTION_FAILURE_RESOLUTION_REQUEST); /* * Thrown if Google Play services canceled the original * PendingIntent */ } catch (IntentSender.SendIntentException e) { // Log the error e.printStackTrace(); } } else { /* * If no resolution is available, display a dialog to the * user with the error. */ showErrorDialog(connectionResult.getErrorCode()); } } ... }
定義地理位置更新回調函數
定位服務或是以一個 Intent 的形式,或者以一個參數的形式將為之更新傳遞給一個你定義的回調函數。這節課將會講解如何使用一個回調函數來獲取更新,課程中使用的代碼基本可以用于任何應用場景。如果你想要以一個 Intent 的形式接收位置更新,可以閱讀: Recognizing the User's Current Activity 。它提供了類似的可以參考的模板。
位置服務所調用的將為之更新發送給你的應用的回調函數是在
LocationListener
接口的
onLocationChanged()
方法中指定的。傳入的參數是一個
Location
對象,包含了地點的經緯度。下面的代碼片段展示了如何指定接口和定義方法:
public class MainActivity extends FragmentActivity implements GooglePlayServicesClient.ConnectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener, LocationListener { ... // Define the callback method that receives location updates @Override public void onLocationChanged(Location location) { // Report to the UI that the location was updated String msg = "Updated Location: " + Double.toString(location.getLatitude()) + "," + Double.toString(location.getLongitude()); Toast.makeText( this , msg, Toast.LENGTH_SHORT).show(); } ... }
現在你已經有了回調函數,你可以配置位置更新的請求了。首先第一步是指定控制更新的參數。
四). 指定更新參數
定位服務允許你控制更新之間的時間間隔以及你期望的位置精確度,通過設置 LocationRequest 對象中的值,再將這一對象作為你的請求的一部分發出以開始更新。
首先,設置下列間隔參數:
更新間隔:
通過 LocationRequest.setInterval() 設置。這一方法以毫秒為單位設置你的應用接收更新的事件間隔。如果沒有其它應用從定位服務接收更新,那么你的應用將會以這一頻率接收更新。
最快更新間隔:
通過
LocationRequest.setFastestInterval()
設置。這一方法設置的是你的應用能處理更新的最快間隔時間,以毫秒為單位。你需要設置這個頻率是因為其它應用也會影響位置更新非頻率。定位服務會以所有應用通過
LocationRequest.setInterval()
設置的最快的間隔時間來發送更新。如果這一頻率比你的應用能夠處理的頻率要快,那么你可能會遇到UI閃爍或數據溢出等問題。為了避免這一情況發生,應該調用
LocationRequest.setFastestInterval()
這一方法設置更新頻率的最高限額。
調用 LocationRequest.setFastestInterval() 方法還可以節省電量。當你通過 LocationRequest.setInterval() 請求了一個更新間隔后,又用 LocationRequest.setFastestInterval() 請求了一個最大速率后,你的應用會以正常速率進行更新。如果其它應用使用了一個更快的更新速率,那么你的更新頻率也會加快。如果沒有其它應用申請了更快的更新速率,那么你的應用會以 LocationRequest.setInterval() 中所設置的速率進行更新。
接下來,設置精度參數。在一個前臺應用程序中,你需要以高頻率更新地理位置,所以使用 LocationRequest.PRIORITY_HIGH_ACCURACY 設置精度。
下面的代碼片段展示課如何設置更新間隔和精度:
public class MainActivity extends FragmentActivity implements GooglePlayServicesClient.ConnectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener, LocationListener { ... // Global constants ... // Milliseconds per second private static final int MILLISECONDS_PER_SECOND = 1000 ; // Update frequency in seconds public static final int UPDATE_INTERVAL_IN_SECONDS = 5 ; // Update frequency in milliseconds private static final long UPDATE_INTERVAL = MILLISECONDS_PER_SECOND * UPDATE_INTERVAL_IN_SECONDS; // The fastest update frequency, in seconds private static final int FASTEST_INTERVAL_IN_SECONDS = 1 ; // A fast frequency ceiling in milliseconds private static final long FASTEST_INTERVAL = MILLISECONDS_PER_SECOND * FASTEST_INTERVAL_IN_SECONDS; ... // Define an object that holds accuracy and frequency parameters LocationRequest mLocationRequest; ... @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); // Create the LocationRequest object mLocationRequest = LocationRequest.create(); // Use high accuracy mLocationRequest.setPriority( LocationRequest.PRIORITY_HIGH_ACCURACY); // Set the update interval to 5 seconds mLocationRequest.setInterval(UPDATE_INTERVAL); // Set the fastest update interval to 1 second mLocationRequest.setFastestInterval(FASTEST_INTERVAL); ... } ... }
Note:
如果你的應用要訪問網絡或者在接收到更新后需要做其它長期的任務,那么應該調整更新頻率到一個比較慢的值。這可以避免你的應用接收到太多它來不及處理的更新數據。一旦長期處理的任務結束了,可以再通過設置最快更新頻率到一個較快的值。
五). 開始位置更新
要發送位置更新請求,在
onCreate()
創建一個定位客戶端,之后連接它,并通過
requestLocationUpdates()
發起請求。因為你的客戶端必須連接以后你的應用才能收到更新,所以你應該在
onStart()
方法中連接到客戶端。這能保證當你的應用可見時,你都能獲取一個已連接的有效的客戶端。因為你需要在發出請求前先進行連接,所以在
ConnectionCallbacks.onConnected()
發出更新請求。
另外要記住用戶可能會有各種各樣的原因希望關閉位置更新。你應該為用戶提供一個這樣做的方法,并且你應該保證當更新關閉了之后,你不會在 onStart() 中啟動更新。為了記錄用戶的設置,在 onPause() 方法中保存應用的 SharedPreferences ,并在 onResume() 方法中獲取它。
下面的代碼片段展示了如何在 onCreate() 方法中設置客戶端,以及如何在 onStart() 方法中連接并發出更新請求:
public class MainActivity extends FragmentActivity implements GooglePlayServicesClient.ConnectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener, LocationListener { ... // Global variables ... LocationClient mLocationClient; boolean mUpdatesRequested; ... @Override protected void onCreate(Bundle savedInstanceState) { ... // Open the shared preferences mPrefs = getSharedPreferences("SharedPreferences" , Context.MODE_PRIVATE); // Get a SharedPreferences editor mEditor = mPrefs.edit(); /* * Create a new location client, using the enclosing class to * handle callbacks. */ mLocationClient = new LocationClient( this , this , this ); // Start with updates turned off mUpdatesRequested = false ; ... } ... @Override protected void onPause() { // Save the current setting for updates mEditor.putBoolean("KEY_UPDATES_ON" , mUpdatesRequested); mEditor.commit(); super .onPause(); } ... @Override protected void onStart() { ... mLocationClient.connect(); } ... @Override protected void onResume() { /* * Get any previous setting for location updates * Gets "false" if an error occurs */ if (mPrefs.contains("KEY_UPDATES_ON" )) { mUpdatesRequested = mPrefs.getBoolean( "KEY_UPDATES_ON", false ); // Otherwise, turn off location updates } else { mEditor.putBoolean( "KEY_UPDATES_ON", false ); mEditor.commit(); } } ... /* * Called by Location Services when the request to connect the * client finishes successfully. At this point, you can * request the current location or start periodic updates */ @Override public void onConnected(Bundle dataBundle) { // Display the connection status Toast.makeText( this , "Connected" , Toast.LENGTH_SHORT).show(); // If already requested, start periodic updates if (mUpdatesRequested) { mLocationClient.requestLocationUpdates(mLocationRequest, this ); } } ... }
更多關于保存配置信息的知識,可以查看: Saving Key-Value Sets 。
六). 停止位置更新
要停止位置更新,在 onPause() 方法中保存更新標識的狀態,并在 onStop() 方法中通過調用 removeLocationUpdates(LocationListener) 來停止更新,例如:
public class MainActivity extends FragmentActivity implements GooglePlayServicesClient.ConnectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener, LocationListener { ... /* * Called when the Activity is no longer visible at all. * Stop updates and disconnect. */ @Override protected void onStop() { // If the client is connected if (mLocationClient.isConnected()) { /* * Remove location updates for a listener. * The current Activity is the listener, so * the argument is "this". */ removeLocationUpdates( this ); } /* * After disconnect() is called, the client is * considered "dead". */ mLocationClient.disconnect(); super .onStop(); } ... }
現在你已經有了請求并接收定期位置更新的基本應用框架。你可以將這節課中所講的東西結合到導航,行為識別,反地址解析等等場景中。
下一節課中,我們將會講解如何使用當前地點顯示現在的街道地址。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
