Android 中的活动识别——静止、步行、跑步、驾驶等等
前言
如今,每个人都拥有智能手机,我们用它们来完成我们的日常生活。手机中存在的 Android 应用程序最好的部分是这些应用程序试图越来越多地了解他们的用户。今天,许多应用程序正在获取用户的位置以向用户提供与位置相关的提要。一个常见的例子是普通的新闻应用程序,该应用程序获取您当前的位置并根据位置呈现新闻。
如果您是一名 Android 开发人员,那么为了给您的用户更好的应用程序体验,您必须以更好的方式了解您的用户。您应该随时了解您的用户在做什么。您对用户了解得越多,就可以为用户构建更好的应用程序。因此,有许多应用程序使用用户的这种活动识别。例如,公里查找器应用程序在您开始驾驶汽车或自行车时开始运行,并在您停止驾驶时停止。通过这样做,该应用程序可以找到您在特定日期旅行的距离。此活动识别的其他应用程序可以是任何健康和健身应用程序,它可以确定您在特定日期跑步或步行多少米或公里,之后,您可以找到当天燃烧的卡路里。
因此,在本文中,我们将学习如何使用 Android 应用程序中的活动识别功能来查找用户是否在静止、跑步、步行、驾驶或其他状态。那么,让我们开始吧。
活动识别客户端
为了找到用户在特定时刻正在进行的活动,您必须不断与移动设备的传感器进行通信,并且在收集数据后,您必须使用一些机器学习算法来查找用户当前正在执行的活动. 但是坚持住!我们是否需要学习机器学习算法来识别用户正在进行的活动?不不,您不需要学习任何机器学习算法来检测活动。
在 Android 中,我们有一个 Activity Recognition Client,它会定期唤醒您的设备,然后从设备的传感器收集数据,然后这些收集的数据将用于在某些机器学习算法的帮助下确定活动。您需要做的就是使用Activity Recognition Client,然后 API 将为您完成剩下的工作。
Activity Recognition Client 返回一个用户可能正在执行的活动列表,这些活动具有一定的置信度百分比。这个置信度百分比告诉您活动的确定性。例如,置信度超过 75% 的活动,则用户可能正在执行该活动。因此,置信度参数告诉您用户完成某项活动的概率。
活动识别客户端检测到的活动
活动识别客户端确定用户可以执行并且 API 可以检测到的活动列表。以下是活动识别客户端可以检测到的活动:
-
STILL:当移动设备静止时,即用户坐在某个地方或移动设备没有运动,那么活动识别客户端将检测到静止活动。
-
ON_FOOT:当移动设备以正常速度移动时,即携带移动设备的用户正在步行或跑步时,Activity Recognition Client将检测到ON_FOOT活动。
-
WALKING:这是ON_FOOT活动的子活动,活动识别客户端在携带移动设备的用户走路时检测到该活动。
-
RUNNING:这也是ON_FOOT活动的一个子活动,当携带移动设备的用户在跑步时被Activity Recognition Client检测到。
-
IN_VEHICLE:当移动设备在公共汽车或汽车或某种其他类型的车辆中或持有移动设备的用户出现在车辆中时检测到此活动。
-
ON_BICYCLE:当设备在自行车上或携带手机的用户在自行车上时,将检测到此活动。
-
倾斜:当移动设备被抬起并与平面有一定角度时,活动识别客户端将检测到此活动。
-
未知:当设备无法检测到移动设备上的任何活动时,活动识别客户端将显示此结果。
活动识别示例
我们已经学习了活动识别客户端的概念。现在,是时候进行一些实践课程了:) 让我们举一个 Activity Recognition Client 的例子来更好地理解 Activity Recognition 的概念。所以,让我们举一个例子。
打开 Android Studio 并使用 Empty Activity 模板创建一个项目。
添加依赖项和权限
Activity Recognition Client 需要 Google Play Services 的依赖。因此,要添加 Google Play 服务的依赖项,请在您的应用级build.gradle文件中添加以下行:
implementation 'com.google.android.gms:play-services-location:16.0.0'
添加Google Play Services的依赖后,在AndroidManifest.xml文件中添加ACTIVITY_RECOGNITION的权限:
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
添加应用程序的 UI
至此,添加依赖和权限的过程就完成了。现在下一步是为我们的 Main Activity 添加 UI。在我们的应用程序中,我们将有一个TextView来显示当前 Activity 的名称和一个TextView来显示 Activity 的置信度百分比。因此,activity_main.xml文件看起来像这样:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:id="@+id/txt_activity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="24dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="48dp"
android:textAllCaps="true"
android:textColor="@color/colorPrimary"
android:textSize="18dp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/txt_confidence"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/txt_confidence"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="24dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:textAllCaps="true"
android:textSize="14dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_start_tracking"
android:layout_width="240dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
android:text="Start Tracking"
app:layout_constraintBottom_toTopOf="@+id/btn_stop_tracking"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/btn_stop_tracking"
android:layout_width="240dp"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
android:text="Stop Tracking"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</android.support.constraint.ConstraintLayout>
现在,在res/values/strings.xml文件中添加字符串值:
<resources>
<string name="app_name">Activity Recognition</string>
<string name="activity_in_vehicle">In Vehicle</string>
<string name="activity_on_bicycle">On Bicycle</string>
<string name="activity_on_foot">On Foot</string>
<string name="activity_running">Running</string>
<string name="activity_still">Still</string>
<string name="activity_tilting">Tilting</string>
<string name="activity_walking">walking</string>
<string name="activity_unknown">Unknown</string>
</resources>
现在,我们完成了 Main Activity 的 UI 部分。现在,让我们继续我们应用程序的编码部分。
因此,我们完全完成了应用程序的 UI 部分。现在让我们继续进行编码部分。
创建一个 IntentService
在制作应用程序的 UI 之后,我们的下一个任务是创建一个将从 IntentService 扩展的类。此类将返回用户可以执行的可能活动列表或用户当前正在执行的活动,即 WALKING、RUNNING、ON_FOOT 等。我的 DetectedActivitiesIntentService 的代码是:
class DetectedActivitiesIntentService : IntentService(TAG) {
override fun onCreate() {
super.onCreate()
}
override fun onHandleIntent(intent: Intent?) {
val result = ActivityRecognitionResult.extractResult(intent) // Get the list of the probable activities associated with the current state of the // device. Each activity is associated with a confidence level, which is an int between // 0 and 100. val detectedActivities = result.probableActivities as ArrayList<*>
for (activity in detectedActivities) {
broadcastActivity(activity as DetectedActivity)
}
}
private fun broadcastActivity(activity: DetectedActivity) {
val intent = Intent(MainActivity.BROADCAST_DETECTED_ACTIVITY)
intent.putExtra("type", activity.type)
intent.putExtra("confidence", activity.confidence)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
}
companion object {
protected val TAG = DetectedActivitiesIntentService::class.java.simpleName
}
}// Use the TAG to name the worker thread.
在后台运行的活动
我们的下一步是编写MainActivity.kt文件的代码。但在此之前,应该牢记的是电池性能。如果您希望您的活动识别客户端定期或频繁更新活动,那么这将降低您的移动设备的电池性能。此外,如果您希望您的应用程序持续跟踪正在进行的活动,那么在后台运行您的应用程序是一项不错的任务。但同时,也要注意电池的消耗。
因此,创建一个将在后台检测活动的类。这是代码:
class BackgroundDetectedActivitiesService : Service() {
private lateinit var mIntentService: Intent
private lateinit var mPendingIntent: PendingIntent
private lateinit var mActivityRecognitionClient: ActivityRecognitionClient
internal var mBinder: IBinder = LocalBinder()
inner class LocalBinder : Binder() {
val serverInstance: BackgroundDetectedActivitiesService
get() = this@BackgroundDetectedActivitiesService
}
override fun onCreate() {
super.onCreate()
mActivityRecognitionClient = ActivityRecognitionClient(this)
mIntentService = Intent(this, DetectedActivitiesIntentService::class.java)
mPendingIntent = PendingIntent.getService(this, 1, mIntentService, PendingIntent.FLAG_UPDATE_CURRENT)
requestActivityUpdatesButtonHandler()
}
override fun onBind(intent: Intent): IBinder? {
return mBinder
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
return Service.START_STICKY
}
fun requestActivityUpdatesButtonHandler() {
val task = mActivityRecognitionClient?.requestActivityUpdates(
MainActivity.DETECTION_INTERVAL_IN_MILLISECONDS,
mPendingIntent)
task?.addOnSuccessListener {
Toast.makeText(applicationContext,
"Successfully requested activity updates",
Toast.LENGTH_SHORT)
.show()
}
task?.addOnFailureListener {
Toast.makeText(applicationContext,
"Requesting activity updates failed to start",
Toast.LENGTH_SHORT)
.show()
}
}
fun removeActivityUpdatesButtonHandler() {
val task = mActivityRecognitionClient?.removeActivityUpdates(
mPendingIntent)
task?.addOnSuccessListener {
Toast.makeText(applicationContext,
"Removed activity updates successfully!",
Toast.LENGTH_SHORT)
.show()
}
task?.addOnFailureListener {
Toast.makeText(applicationContext, "Failed to remove activity updates!",
Toast.LENGTH_SHORT).show()
}
}
override fun onDestroy() {
super.onDestroy()
removeActivityUpdatesButtonHandler()
}
companion object {
private val TAG = BackgroundDetectedActivitiesService::class.java?.getSimpleName()
}
}
MainActivity.kt 的代码
所以,我们最后的任务是编写MainActivity.kt文件的代码。在这里,BroadCastReceiver()用于接收来自用户的活动更新,即当活动发生变化时,活动将被接收。以下是MainActivity.kt文件的代码:
class MainActivity : AppCompatActivity() {
private val TAG = MainActivity::class.java.simpleName
internal lateinit var broadcastReceiver: BroadcastReceiver
private lateinit var txtActivity: TextView
private lateinit var txtConfidence: TextView
private lateinit var btnStartTrcking: Button
private lateinit var btnStopTracking: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
txtActivity = findViewById(R.id.txt_activity)
txtConfidence = findViewById(R.id.txt_confidence)
btnStartTrcking = findViewById(R.id.btn_start_tracking)
btnStopTracking = findViewById(R.id.btn_stop_tracking)
btnStartTrcking?.setOnClickListener { startTracking() }
btnStopTracking?.setOnClickListener { stopTracking() }
broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == MainActivity.BROADCAST_DETECTED_ACTIVITY) {
val type = intent.getIntExtra("type", -1)
val confidence = intent.getIntExtra("confidence", 0)
handleUserActivity(type, confidence)
}
}
}
startTracking()
}
private fun handleUserActivity(type: Int, confidence: Int) {
var label = getString(R.string.activity_unknown)
when (type) {
DetectedActivity.IN_VEHICLE -> {
label = "You are in Vehicle"
}
DetectedActivity.ON_BICYCLE -> {
label = "You are on Bicycle"
}
DetectedActivity.ON_FOOT -> {
label = "You are on Foot"
}
DetectedActivity.RUNNING -> {
label = "You are Running"
}
DetectedActivity.STILL -> {
label = "You are Still"
}
DetectedActivity.TILTING -> {
label = "Your phone is Tilted"
}
DetectedActivity.WALKING -> {
label = "You are Walking"
}
DetectedActivity.UNKNOWN -> {
label = "Unkown Activity"
}
}
Log.e(TAG, "User activity: $label, Confidence: $confidence")
if (confidence > MainActivity.CONFIDENCE) {
txtActivity?.text = label
txtConfidence?.text = "Confidence: $confidence"
}
}
override fun onResume() {
super.onResume()
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver,
IntentFilter(MainActivity.BROADCAST_DETECTED_ACTIVITY))
}
override fun onPause() {
super.onPause()
LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver)
}
private fun startTracking() {
val intent = Intent(this@MainActivity, BackgroundDetectedActivitiesService::class.java)
startService(intent)
}
private fun stopTracking() {
val intent = Intent(this@MainActivity, BackgroundDetectedActivitiesService::class.java)
stopService(intent)
}
companion object {
val BROADCAST_DETECTED_ACTIVITY = "activity_intent"
internal val DETECTION_INTERVAL_IN_MILLISECONDS: Long = 1000
val CONFIDENCE = 70
}
}
现在,运行应用程序,您将获得活动列表以及活动的置信度。
结论
在这篇文章中,我们学习了如何在我们的应用程序中使用 Activity Recognition Client 来确定用户在任何时刻正在进行的活动。Activity Recognition Client 以一定的置信度百分比确定正在进行的活动,该百分比告诉您当前正在进行的活动。
作者:Admin MindOrks
链接:Activity Recognition in Android — Still, Walking, Running, Driving and much more