注:本文翻譯自Google官方的Android Developers Training文檔,譯者技術一般,由于喜愛安卓而產生了翻譯的念頭,純屬個人興趣愛好。
原文鏈接: http://developer.android.com/training/location/activity-recognition.html
樣例代碼:
行為認知會嘗試檢測當前用戶的物理行為,比如:行走,駕駛或者靜止站立。從一個行為認知客戶端發出更新信息的請求,同之前的定位或者地理圍欄所使用的定位客戶端相比有所不同,但大致思路是一致的。基于你所選擇的的更新間隔,定位服務會發出一個或更多個用戶當前可能的行為信息,同時每一個信息都會有一個可能性級別。這節課將向你展示如何從定位服務實現用戶的行為識別。
一). 請求行為認知更新
從定位服務請求一個行為認知更新與請求定期的地點更新是比較類似的。你通過客戶端發出請求,然后定位服務通過一個 PendingIntent 將更新信息發回給你的應用。然而,在你請求行為識別更新之前,你需要申請一些特別的權限,然后你使用不同類型的客戶端發出申請。下面各個章節將會講解如何請求權限,連接客戶端并請求更新。
申請就收更新的權限
一個應用如果想要獲取行為認知更新,必須有“ com.google.android.gms.permission.ACTIVITY_RECOGNITION ”的權限。要為你的權限請求這一權限,將下列的XML標簽作為 <manifest> 標簽的子標簽添加到你的清單列表當中:
< uses-permission android:name ="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
另外,行為認知不需要 ACCESS_COARSE_LOCATION 權限和 ACCESS_FINE_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("Activity Recognition" , "Google Play services is available." ); // Continue return true ; // Google Play services was not available for some reason } else { // Get the error dialog from Google Play services Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog( resultCode, 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(), "Activity Recognition" ); } return false ; } } ... }
在后續章節的代碼片段中,都會調用這一方法來驗證是否可獲取Google Play服務。
發送行文更新請求
從一個實現了定位服務所需要額回調函數的 Activity 或者 Fragment 發送更新請求。當你請求連接到一個行為認知客戶端時,最好將請求做成異步的進程。當客戶端已經連接了,定位服務會調用你的 onConnected() 實現。在這個方法中,你可以將更新請求發送給定位服務;該請求是同步的。一旦你發出了請求,你可以關閉客戶端的連接。
整個過程會在下面的各個代碼片段中展開。
定義Activity或者Fragment
定義一個 FragmentActivity 或者 Fragment 實現下列的接口:
當客戶端連接或者斷開連接時定位服務調用的方法。
當請求連接到客戶端時發生錯誤,定位服務調用的方法。
例如:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener { ... }
之后定義全局變量和常量。給更新間隔定義常量,為行為識別客戶端添加變量,同時還需要為定位服務發送更新時使用的
PendingIntent
添加另一個變量:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener { ... // Constants that define the activity detection interval public static final int MILLISECONDS_PER_SECOND = 1000 ; public static final int DETECTION_INTERVAL_SECONDS = 20 ; public static final int DETECTION_INTERVAL_MILLISECONDS = MILLISECONDS_PER_SECOND * DETECTION_INTERVAL_SECONDS; ... /* * Store the PendingIntent used to send activity recognition events * back to the app */ private PendingIntent mActivityRecognitionPendingIntent; // Store the current activity recognition client private ActivityRecognitionClient mActivityRecognitionClient; ... }
在 onCreate() 中,實例化行為認知客戶端以及 PendingIntent :
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener { ... @Override onCreate(Bundle savedInstanceState) { ... /* * Instantiate a new activity recognition client. Since the * parent Activity implements the connection listener and * connection failure listener, the constructor uses "this" * to specify the values of those parameters. */ mActivityRecognitionClient = new ActivityRecognitionClient(mContext, this , this ); /* * Create the PendingIntent that Location Services uses * to send activity recognition updates back to this app. */ Intent intent = new Intent( mContext, ActivityRecognitionIntentService. class ); /* * Return a PendingIntent that starts the IntentService. */ mActivityRecognitionPendingIntent = PendingIntent.getService(mContext, 0 , intent, PendingIntent.FLAG_UPDATE_CURRENT); ... } ... }
開始請求進程
定義一個方法來請求行為識別更新。在該方法中,請求一個到定位服務的連接。你可以在你的activity中任何地方調用該方法;其目標是啟動請求更新的一系列操作。
要保證競爭情況的發生(當你的應用嘗試在第一個請求沒有執行完畢時就啟動另一個請求),定義一個布爾標記變量,它跟蹤當前請求的狀態。當你開始請求時將它設置為 True ,當請求完畢后,將它設置為 false 。
下面的代碼片段展示了如何啟動一個更新請求:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener { ... // Global constants ... // Flag that indicates if a request is underway. private boolean mInProgress; ... @Override onCreate(Bundle savedInstanceState) { ... // Start with the request flag set to false mInProgress = false ; ... } ... /** * Request activity recognition updates based on the current * detection interval. * */ public void startUpdates() { // Check for Google Play services if (! servicesConnected()) { return ; } // If a request is not already underway if (! mInProgress) { // Indicate that a request is in progress mInProgress = true ; // Request a connection to Location Services mActivityRecognitionClient.connect(); // } else { /* * A request is already underway. You can handle * this situation by disconnecting the client, * re-setting the flag, and then re-trying the * request. */ } } ... }
實現 onConnected() 。在該方法中,從定位服務請求行為識別更新。當定位服務完成了連接,并調用了 onConnected() ,更新請求會被立即調用:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener { ... /* * Called by Location Services once the location client is connected. * * Continue by requesting activity updates. */ @Override public void onConnected(Bundle dataBundle) { /* * Request activity recognition updates using the preset * detection interval and PendingIntent. This call is * synchronous. */ mActivityRecognitionClient.requestActivityUpdates( DETECTION_INTERVAL_MILLISECONDS, mActivityRecognitionPendingIntent); /* * Since the preceding call is synchronous, turn off the * in progress flag and disconnect the client */ mInProgress = false ; mActivityRecognitionClient.disconnect(); } ... }
處理連接斷開的情況
在一些情況下,定位服務可能會在你調用 disconnect() 之前就從行為識別客戶端就端開了連接。要處理這一情況,實現 onDisconnected() 方法。在該方法中,對請求標記變量進行設置,來指出目前請求并不在流程中,然后刪除客戶端:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener { ... /* * Called by Location Services once the activity recognition * client is disconnected. */ @Override public void onDisconnected() { // Turn off the request flag mInProgress = false ; // Delete the client mActivityRecognitionClient = null ; } ... }
處理連接錯誤
除了處理定位服務的常規回調函數外,你還需要提供一個回調函數,該函數會在連接錯誤發生的時候被定為服務調用。該回調函數可以重用 DialogFragment 類(你在檢查Google Play服務時所定義的類)。同時它也可以重用當用戶與錯誤對話框交互時,接收任何由Google Play服務返回的結果的 onActivityResult() 函數。下面的代碼片段展示了該回調函數的一個例子:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener { ... // Implementation of OnConnectionFailedListener.onConnectionFailed @Override public void onConnectionFailed(ConnectionResult connectionResult) { // Turn off the request flag mInProgress = false ; /* * If the error has a resolution, start a Google Play services * activity to resolve it. */ if (connectionResult.hasResolution()) { try { connectionResult.startResolutionForResult( this , CONNECTION_FAILURE_RESOLUTION_REQUEST); } catch (SendIntentException e) { // Log the error e.printStackTrace(); } // If no resolution is available, display an error dialog } 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(), "Activity Recognition" ); } } ... } ... }
二). 處理行為更新
要處理每個更新間隔中定位服務發送的
Intent
,定義一個
IntentService
以及它所需要的方法
onHandleIntent()
。定位服務會將行為認知更新以
Intent
對象的形式發出,當你調用了
requestActivityUpdates()
后使用你提供的
PendingIntent
。一旦你為
PendingIntent
提供了一個顯式地intent,只有你定義的
IntentService
會接收你的intent。
下面的代碼片段闡述了如何在一個行為認知更新中處理數據。
定義一個IntentService
首先定義類及其需要的方法 onHandleIntent() :
/** * Service that receives ActivityRecognition updates. It receives * updates in the background, even if the main Activity is not visible. */ public class ActivityRecognitionIntentService extends IntentService { ... /** * Called when a new activity detection update is available. */ @Override protected void onHandleIntent(Intent intent) { ... } ... }
之后,處理intent中的數據。從更新中,你可以獲取一個可能行為的列表以及每個行為的可能性級別。下面的代碼片段展示了如何獲取最可能的行為信息,行為的可能性級別和它的類型:
public class ActivityRecognitionIntentService extends IntentService { ... @Override protected void onHandleIntent(Intent intent) { ... // If the incoming intent contains an update if (ActivityRecognitionResult.hasResult(intent)) { // Get the update ActivityRecognitionResult result = ActivityRecognitionResult.extractResult(intent); // Get the most probable activity DetectedActivity mostProbableActivity = result.getMostProbableActivity(); /* * Get the probability that this activity is the * the user's actual activity */ int confidence = mostProbableActivity.getConfidence(); /* * Get an integer describing the type of activity */ int activityType = mostProbableActivity.getType(); String activityName = getNameFromType(activityType); /* * At this point, you have retrieved all the information * for the current update. You can display this * information to the user in a notification, or * send it to an Activity or Service in a broadcast * Intent. */ ... } else { /* * This implementation ignores intents that don't contain * an activity update. If you wish, you can report them as * errors. */ } ... } ... }
方法 getNameFromType()會將activity類型轉換為帶有描述性的字符串。在一個需要發布的應用中,你應該從資源文件中獲取字符串而非使用固定的變量值:
public class ActivityRecognitionIntentService extends IntentService { ... /** * Map detected activity types to strings * @param activityType The detected activity type * @return A user-readable name for the type */ private String getNameFromType( int activityType) { switch (activityType) { case DetectedActivity.IN_VEHICLE: return "in_vehicle" ; case DetectedActivity.ON_BICYCLE: return "on_bicycle" ; case DetectedActivity.ON_FOOT: return "on_foot" ; case DetectedActivity.STILL: return "still" ; case DetectedActivity.UNKNOWN: return "unknown" ; case DetectedActivity.TILTING: return "tilting" ; } return "unknown" ; } ... }
在清單文件中指明IntentService
要在系統中指明 IntentService ,需要再應用的清單文件中添加一個 <service> 標簽,例如:
< service android:name ="com.example.android.location.ActivityRecognitionIntentService" android:label ="@string/app_name" android:exported ="false" > </ service >
注意,你不需要為該服務指定intent過濾器,因為它僅會接收顯式的intent。如何創建接收的行為更新intent在之前的章節中已經說過了。
三). 停止行為認知更新
要停止行為認知更新,其思路和請求更新是一致的,但是調用的函數是 removeActivityUpdates() 而不是 requestActivityUpdates() 。
由于移除更新會使用一些你在添加更新時所用到的方法,我們首先為兩個操作定義請求類型:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener { ... public enum REQUEST_TYPE {START, STOP} private REQUEST_TYPE mRequestType; ... }
修改啟動行為認知的代碼,這樣它就能使用“ START ”請求類型:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener { ... public void startUpdates() { // Set the request type to START mRequestType = REQUEST_TYPE.START; /* * Test for Google Play services after setting the request type. * If Google Play services isn't present, the proper request type * can be restarted. */ if (! servicesConnected()) { return ; } ... } ... public void onConnected(Bundle dataBundle) { switch (mRequestType) { case START : /* * Request activity recognition updates using the * preset detection interval and PendingIntent. * This call is synchronous. */ mActivityRecognitionClient.requestActivityUpdates( DETECTION_INTERVAL_MILLISECONDS, mActivityRecognitionPendingIntent); break ; ... /* * An enum was added to the definition of REQUEST_TYPE, * but it doesn't match a known case. Throw an exception. */ default : throw new Exception("Unknown request type in onConnected()." ); break ; } ... } ... }
開始過程
定義一個方法,用來請求停止行為認知更新。在該方法中,設置請求類型,并請求一個到定位服務的連接。你可以在你的activity的任何地方調用該方法;其目的是要開始一系列方法的調用來停止更新:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener { ... /** * Turn off activity recognition updates * */ public void stopUpdates() { // Set the request type to STOP mRequestType = REQUEST_TYPE.STOP; /* * Test for Google Play services after setting the request type. * If Google Play services isn't present, the request can be * restarted. */ if (! servicesConnected()) { return ; } // If a request is not already underway if (! mInProgress) { // Indicate that a request is in progress mInProgress = true ; // Request a connection to Location Services mActivityRecognitionClient.connect(); // } else { /* * A request is already underway. You can handle * this situation by disconnecting the client, * re-setting the flag, and then re-trying the * request. */ } ... } ... }
在 onConnected() 方法中,如果請求類型是“ STOP ”,那么調用 removeActivityUpdates() 。將你用來啟動更新的 PendingIntent 作為參數傳遞給 removeActivityUpdates() :
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener { ... public void onConnected(Bundle dataBundle) { switch (mRequestType) { ... case STOP : mActivityRecognitionClient.removeActivityUpdates( mActivityRecognitionPendingIntent); break ; ... } ... } ... }
你不需要修改 onDisconnected() 或 onConnectionFailed() 的實現,因為這些方法并不依賴于該請求類型。
現在你已經有了一個行為認知應用的基本框架了。你可以將行為認知的功能和其它定位相關的功能結合在一起,比如定期的地點更新,地理圍欄等,這些內容都在這系列課程中的其它課中講授過了。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

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