注:本文翻譯自Google官方的Android Developers Training文檔,譯者技術(shù)一般,由于喜愛安卓而產(chǎn)生了翻譯的念頭,純屬個(gè)人興趣愛好。
原文鏈接: http://developer.android.com/training/location/activity-recognition.html
樣例代碼:
行為認(rèn)知會(huì)嘗試檢測(cè)當(dāng)前用戶的物理行為,比如:行走,駕駛或者靜止站立。從一個(gè)行為認(rèn)知客戶端發(fā)出更新信息的請(qǐng)求,同之前的定位或者地理圍欄所使用的定位客戶端相比有所不同,但大致思路是一致的。基于你所選擇的的更新間隔,定位服務(wù)會(huì)發(fā)出一個(gè)或更多個(gè)用戶當(dāng)前可能的行為信息,同時(shí)每一個(gè)信息都會(huì)有一個(gè)可能性級(jí)別。這節(jié)課將向你展示如何從定位服務(wù)實(shí)現(xiàn)用戶的行為識(shí)別。
一). 請(qǐng)求行為認(rèn)知更新
從定位服務(wù)請(qǐng)求一個(gè)行為認(rèn)知更新與請(qǐng)求定期的地點(diǎn)更新是比較類似的。你通過(guò)客戶端發(fā)出請(qǐng)求,然后定位服務(wù)通過(guò)一個(gè) PendingIntent 將更新信息發(fā)回給你的應(yīng)用。然而,在你請(qǐng)求行為識(shí)別更新之前,你需要申請(qǐng)一些特別的權(quán)限,然后你使用不同類型的客戶端發(fā)出申請(qǐng)。下面各個(gè)章節(jié)將會(huì)講解如何請(qǐng)求權(quán)限,連接客戶端并請(qǐng)求更新。
申請(qǐng)就收更新的權(quán)限
一個(gè)應(yīng)用如果想要獲取行為認(rèn)知更新,必須有“ com.google.android.gms.permission.ACTIVITY_RECOGNITION ”的權(quán)限。要為你的權(quán)限請(qǐng)求這一權(quán)限,將下列的XML標(biāo)簽作為 <manifest> 標(biāo)簽的子標(biāo)簽添加到你的清單列表當(dāng)中:
< uses-permission android:name ="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
另外,行為認(rèn)知不需要 ACCESS_COARSE_LOCATION 權(quán)限和 ACCESS_FINE_LOCATION 權(quán)限。
檢查Google Play服務(wù)
位置服務(wù)是Google Play服務(wù)APK的其中一部分。由于用戶設(shè)備的狀態(tài)時(shí)難以預(yù)料的,你應(yīng)該一直在你嘗試連接定位服務(wù)之前,檢查APK是否已經(jīng)安裝。要檢查APK是否安裝,可以調(diào)用 GooglePlayServicesUtil.isGooglePlayServicesAvailable() ,它會(huì)返回一個(gè)整形的結(jié)果碼,其含義可以參閱: ConnectionResult 。如果你遇到了一個(gè)錯(cuò)誤,可以調(diào)用 GooglePlayServicesUtil.getErrorDialog() ,來(lái)獲取一個(gè)本地的對(duì)話框,引導(dǎo)用戶執(zhí)行正確地行為,之后將這一對(duì)話框顯示在一個(gè) DialogFragment 上。這一對(duì)話框可能允許用戶解決當(dāng)前的問(wèn)題,此時(shí)Google Play服務(wù)會(huì)發(fā)回一個(gè)結(jié)果到你的activity中。要處理這一結(jié)果,需要覆寫 onActivityResult() 方法。
Note:
要使你的應(yīng)用可以兼容1.6及以后版本的系統(tǒng),顯示 DialogFragment 的activity必須是 FragmentActivity 的子類,而非 Activity 。使用 FragmentActivity 還可以允許你調(diào)用 getSupportFragmentManager() 方法來(lái)顯示 DialogFragment 。
由于你一直需要在你的代碼多個(gè)地方檢查Google Play服務(wù),所以應(yīng)該定義一個(gè)方法將檢查行為進(jìn)行封裝,之后在每次連接嘗試之前進(jìn)行檢查。下面的代碼片段包含了檢查Google Play服務(wù)所需要的代碼:
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 ; } } ... }
在后續(xù)章節(jié)的代碼片段中,都會(huì)調(diào)用這一方法來(lái)驗(yàn)證是否可獲取Google Play服務(wù)。
發(fā)送行文更新請(qǐng)求
從一個(gè)實(shí)現(xiàn)了定位服務(wù)所需要額回調(diào)函數(shù)的 Activity 或者 Fragment 發(fā)送更新請(qǐng)求。當(dāng)你請(qǐng)求連接到一個(gè)行為認(rèn)知客戶端時(shí),最好將請(qǐng)求做成異步的進(jìn)程。當(dāng)客戶端已經(jīng)連接了,定位服務(wù)會(huì)調(diào)用你的 onConnected() 實(shí)現(xiàn)。在這個(gè)方法中,你可以將更新請(qǐng)求發(fā)送給定位服務(wù);該請(qǐng)求是同步的。一旦你發(fā)出了請(qǐng)求,你可以關(guān)閉客戶端的連接。
整個(gè)過(guò)程會(huì)在下面的各個(gè)代碼片段中展開。
定義Activity或者Fragment
定義一個(gè) FragmentActivity 或者 Fragment 實(shí)現(xiàn)下列的接口:
當(dāng)客戶端連接或者斷開連接時(shí)定位服務(wù)調(diào)用的方法。
當(dāng)請(qǐng)求連接到客戶端時(shí)發(fā)生錯(cuò)誤,定位服務(wù)調(diào)用的方法。
例如:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener { ... }
之后定義全局變量和常量。給更新間隔定義常量,為行為識(shí)別客戶端添加變量,同時(shí)還需要為定位服務(wù)發(fā)送更新時(shí)使用的
PendingIntent
添加另一個(gè)變量:
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() 中,實(shí)例化行為認(rèn)知客戶端以及 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); ... } ... }
開始請(qǐng)求進(jìn)程
定義一個(gè)方法來(lái)請(qǐng)求行為識(shí)別更新。在該方法中,請(qǐng)求一個(gè)到定位服務(wù)的連接。你可以在你的activity中任何地方調(diào)用該方法;其目標(biāo)是啟動(dòng)請(qǐng)求更新的一系列操作。
要保證競(jìng)爭(zhēng)情況的發(fā)生(當(dāng)你的應(yīng)用嘗試在第一個(gè)請(qǐng)求沒(méi)有執(zhí)行完畢時(shí)就啟動(dòng)另一個(gè)請(qǐng)求),定義一個(gè)布爾標(biāo)記變量,它跟蹤當(dāng)前請(qǐng)求的狀態(tài)。當(dāng)你開始請(qǐng)求時(shí)將它設(shè)置為 True ,當(dāng)請(qǐng)求完畢后,將它設(shè)置為 false 。
下面的代碼片段展示了如何啟動(dòng)一個(gè)更新請(qǐng)求:
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. */ } } ... }
實(shí)現(xiàn) onConnected() 。在該方法中,從定位服務(wù)請(qǐng)求行為識(shí)別更新。當(dāng)定位服務(wù)完成了連接,并調(diào)用了 onConnected() ,更新請(qǐng)求會(huì)被立即調(diào)用:
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(); } ... }
處理連接斷開的情況
在一些情況下,定位服務(wù)可能會(huì)在你調(diào)用 disconnect() 之前就從行為識(shí)別客戶端就端開了連接。要處理這一情況,實(shí)現(xiàn) onDisconnected() 方法。在該方法中,對(duì)請(qǐng)求標(biāo)記變量進(jìn)行設(shè)置,來(lái)指出目前請(qǐng)求并不在流程中,然后刪除客戶端:
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 ; } ... }
處理連接錯(cuò)誤
除了處理定位服務(wù)的常規(guī)回調(diào)函數(shù)外,你還需要提供一個(gè)回調(diào)函數(shù),該函數(shù)會(huì)在連接錯(cuò)誤發(fā)生的時(shí)候被定為服務(wù)調(diào)用。該回調(diào)函數(shù)可以重用 DialogFragment 類(你在檢查Google Play服務(wù)時(shí)所定義的類)。同時(shí)它也可以重用當(dāng)用戶與錯(cuò)誤對(duì)話框交互時(shí),接收任何由Google Play服務(wù)返回的結(jié)果的 onActivityResult() 函數(shù)。下面的代碼片段展示了該回調(diào)函數(shù)的一個(gè)例子:
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" ); } } ... } ... }
二). 處理行為更新
要處理每個(gè)更新間隔中定位服務(wù)發(fā)送的
Intent
,定義一個(gè)
IntentService
以及它所需要的方法
onHandleIntent()
。定位服務(wù)會(huì)將行為認(rèn)知更新以
Intent
對(duì)象的形式發(fā)出,當(dāng)你調(diào)用了
requestActivityUpdates()
后使用你提供的
PendingIntent
。一旦你為
PendingIntent
提供了一個(gè)顯式地intent,只有你定義的
IntentService
會(huì)接收你的intent。
下面的代碼片段闡述了如何在一個(gè)行為認(rèn)知更新中處理數(shù)據(jù)。
定義一個(gè)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中的數(shù)據(jù)。從更新中,你可以獲取一個(gè)可能行為的列表以及每個(gè)行為的可能性級(jí)別。下面的代碼片段展示了如何獲取最可能的行為信息,行為的可能性級(jí)別和它的類型:
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()會(huì)將activity類型轉(zhuǎn)換為帶有描述性的字符串。在一個(gè)需要發(fā)布的應(yīng)用中,你應(yīng)該從資源文件中獲取字符串而非使用固定的變量值:
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
要在系統(tǒng)中指明 IntentService ,需要再應(yīng)用的清單文件中添加一個(gè) <service> 標(biāo)簽,例如:
< service android:name ="com.example.android.location.ActivityRecognitionIntentService" android:label ="@string/app_name" android:exported ="false" > </ service >
注意,你不需要為該服務(wù)指定intent過(guò)濾器,因?yàn)樗鼉H會(huì)接收顯式的intent。如何創(chuàng)建接收的行為更新intent在之前的章節(jié)中已經(jīng)說(shuō)過(guò)了。
三). 停止行為認(rèn)知更新
要停止行為認(rèn)知更新,其思路和請(qǐng)求更新是一致的,但是調(diào)用的函數(shù)是 removeActivityUpdates() 而不是 requestActivityUpdates() 。
由于移除更新會(huì)使用一些你在添加更新時(shí)所用到的方法,我們首先為兩個(gè)操作定義請(qǐng)求類型:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener { ... public enum REQUEST_TYPE {START, STOP} private REQUEST_TYPE mRequestType; ... }
修改啟動(dòng)行為認(rèn)知的代碼,這樣它就能使用“ START ”請(qǐng)求類型:
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 ; } ... } ... }
開始過(guò)程
定義一個(gè)方法,用來(lái)請(qǐng)求停止行為認(rèn)知更新。在該方法中,設(shè)置請(qǐng)求類型,并請(qǐng)求一個(gè)到定位服務(wù)的連接。你可以在你的activity的任何地方調(diào)用該方法;其目的是要開始一系列方法的調(diào)用來(lái)停止更新:
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() 方法中,如果請(qǐng)求類型是“ STOP ”,那么調(diào)用 removeActivityUpdates() 。將你用來(lái)啟動(dòng)更新的 PendingIntent 作為參數(shù)傳遞給 removeActivityUpdates() :
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener { ... public void onConnected(Bundle dataBundle) { switch (mRequestType) { ... case STOP : mActivityRecognitionClient.removeActivityUpdates( mActivityRecognitionPendingIntent); break ; ... } ... } ... }
你不需要修改 onDisconnected() 或 onConnectionFailed() 的實(shí)現(xiàn),因?yàn)檫@些方法并不依賴于該請(qǐng)求類型。
現(xiàn)在你已經(jīng)有了一個(gè)行為認(rèn)知應(yīng)用的基本框架了。你可以將行為認(rèn)知的功能和其它定位相關(guān)的功能結(jié)合在一起,比如定期的地點(diǎn)更新,地理圍欄等,這些內(nèi)容都在這系列課程中的其它課中講授過(guò)了。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

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