使用 Kotlin 在 Android 10 中接收位置信息更新

借助 Android 10,用户可以更好地控制应用对设备位置信息的访问权限。

当在 Android 10 上运行的应用请求位置信息访问权限时,用户有 3 种选择:

  • 始终允许(在前台和后台运行时)
  • 仅在使用时允许(仅限在前台运行时)
  • 拒绝

在此 Codelab 中,您将学习如何在 Android 10 中快速支持位置信息功能。在此 Codelab 结束时,您应该会得到一个支持“仅限在前台运行时获取位置信息”和“在前台和后台运行时获取位置信息”模式的应用(遵循关于“仅在使用时允许”和“始终允许”位置信息访问权限的最佳做法)。

如果您在学习此 Codelab 时遇到任何问题(代码错误、语法错误、内容含义不清等),请通过 Codelab 左下角的报告错误链接报告相应问题。

前提条件

熟悉 Android 开发,并对位置信息服务有一定了解。

学习内容

  • 关于 Android 10 中的位置信息功能的最佳做法
  • 如何处理仅限在前台运行时获取位置信息的权限
  • 如何处理在前台和后台运行时获取位置信息的权限

您将构建的内容

您将通过以下方式修改一款现有的位置信息应用,使其支持 Android 10:

  • 添加对仅限在前台运行时获取位置信息(或“仅在使用时允许”访问权限)的支持。
  • 添加对在前台和后台运行时获取位置信息(或“始终允许”访问权限)的支持。

所需条件

  • Android Studio 3.5 或更高版本,用于运行代码
  • 搭载 Android 10 开发者预览版的设备/模拟器

该应用的界面非常简单:

  • 支持仅前台位置信息更新(通过点按第一个按钮)。
  • 支持前台和后台位置信息更新(通过点按第二个按钮)。
  • 信息会显示在按钮下方。

概念和设置

概念

此 Codelab 重点介绍 Android 10 的不同之处,以及如何提供相关支持。我们已为您完成了大部分与位置信息跟踪相关的代码,因为这些代码没有发生太大变化。

在完成此 Codelab 之前,您应该对 Android 中的位置信息有基本的了解。不过,查看一下相关基础知识也没有什么坏处。

基础知识

通过 Android,您的应用可以通过 android.location 软件包中的类访问设备支持的位置信息服务。位置框架的核心组件是 LocationManager 系统服务,它提供了用于确定底层设备位置和方位的 API(如果适用)。

若要从 NETWORK_PROVIDER GPS_PROVIDER, 接收位置信息更新,您必须通过在 Android 清单文件中分别声明的 ACCESS_COARSE_LOCATION ACCESS_FINE_LOCATION 权限来请求用户权限。如果没有这些权限,应用将在请求位置信息更新时在运行时失败。

Android 10 强化了这一点,为此,它允许用户选择您何时可以访问这些信息(“仅在使用时允许”、“始终允许”、“禁止”)。

为了让用户更好地控制应用对位置信息的访问权限,Android 10 引入了新的位置信息权限 ACCESS_BACKGROUND_LOCATION.

与现有的 ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION 权限不同,新权限仅会影响应用在后台运行时对位置信息的访问权。除非应用的某个 activity 可见或应用正在运行前台服务,否则应用将被视为在后台运行。

如果您的应用具有前台服务(以及通知),则无需请求后台位置信息访问权限,但您仍需要声明该前台服务的前台服务类型"location"

现在,您已经基本了解我们要完成的任务了,接下来,我们开始处理代码!

克隆入门级项目代码库

为帮助您尽快入门,我们准备了一个入门级项目,您可以在此项目的基础上进行构建。如果您已安装 git,只需运行以下命令即可。(您可以在终端/命令行中输入 git --version 进行检查,验证其是否正确执行。)

 git clone https://github.com/googlecodelabs/while-in-use-location

如果未安装 git,您可以将项目下载为 ZIP 文件:

下载 Zip 文件

导入项目

启动 Android Studio,然后在欢迎屏幕中选择 Open an existing Android Studio project,以打开项目目录。

项目加载完成后,您可能还会看到一条提醒,指出 Git 将不会跟踪所有本地更改。您可以点击右上角的 IgnoreX。(您所做的任何更改都不会保存到 Git 代码库中。)

如果您采用的是 Android 视图,那么在项目窗口的左上角应该会看到类似下图所示的内容。(如果您采用的是 Project 视图,那么需要展开项目才能看到这些内容。)

您可以看到两个文件夹图标(basecomplete)。它们都称为“模块”。

请注意,首次打开项目时,Android Studio 可能需要数秒时间在后台编译项目。在此期间,您会在 Android Studio 底部的状态栏中看到一个旋转图标:

建议您等待此过程完成后再更改代码。这样,Android Studio 就可以提取所有必要的组件。

此外,如果您看到 Reload for language changes to take effect? 的提示或类似内容,请选择 Yes

了解入门级项目

现在,您已经完成准备工作,可以开始在 Android 10 中支持位置信息功能了。我们将使用 base 模块,并以此为起点添加对仅限在前台运行时使用(或“仅在使用时允许”)位置信息的支持。换句话说,您将在每个步骤向 base 中添加代码。

complete 模块可用于检查您的工作,或在您遇到问题时提供参考。

关键组件概览:

  • MainActivity:用户用于开始/停止跟踪位置信息的界面(仅限在前台运行时以及在前台和后台运行时)。
  • LocationService:用于跟踪仅限在前台运行时使用的位置信息的服务,如果应用进入后台且已启用位置信息跟踪功能,该服务会将自身晋升为前台服务(通过通知)。
  • Util:为 Location 类添加扩展函数,并将位置信息保存在 SharedPreferences(简化数据层)中。

模拟器设置

如果您在设置 Android 模拟器时需要帮助,请参阅运行应用一文。

运行入门级项目

现在,我们来运行应用。

  1. 将您的 Android 设备连接到计算机或启动模拟器。(请注意,这仅适用于搭载 Android 10 及更高版本的设备)。
  2. 在工具栏中,从下拉选择器中选择 base 配置,然后点击旁边的“运行”(绿色三角形)按钮:


  1. 您应该会看到以下应用:

  2. 您可以随意点按位置信息按钮。该应用已正常运行,并可针对前台以及前台和后台位置信息提供位置信息跟踪服务。现在,我们要让该应用能够在 Android 10 上运行。

在本部分中,您将以 Android 10 为目标平台,并添加对仅限在前台运行时跟踪位置信息的功能(或“仅在使用时允许”访问权限)的支持。

仅限在前台运行时是什么意思?这意味着在前台运行时,应用会跟踪位置信息。也就是说,有某个 activity 位于前台,或者该应用的某项服务正在前台运行(通过通知)。

我们的应用已经提供了一项功能,能够使用在前台运行的 activity 或服务来跟踪用户,因此您无需执行大量操作。您的应用可能也是这样的,因为这是频繁获取位置信息更新的最佳做法。

实际上,您只需指明您的前台服务会被用于位置用途即可。

现在,我们来添加相关支持。

以 SDK 29 为目标版本

base 模块的 build.gradle 文件中,搜索 TODO: Step 2.1, Target SDK 29

执行以下更改:

  1. 将 compileSdkVersion 更改为 29
  2. 将 buildToolsVersion 更改为 "29.0.2"(带引号)
  3. 将 targetSdkVersion 更改为 29

现在,您的代码应如下所示:

android {
   // TODO: Step 2.1, Target Android 10.
   compileSdkVersion 29
   buildToolsVersion "29.0.2"
   defaultConfig {
       applicationId "com.example.android.whileinuselocation"
       minSdkVersion 26
       targetSdkVersion 29
       versionCode 1
       versionName "1.0"
   }
...
}

之后,系统会要求您同步项目。点击 Sync Now

之后,您的应用就支持 Android 10 了。

添加前台服务类型

在 Android 10 中,如果您需要“仅在使用时允许”位置信息权限,则需要添加前台服务的类型。在我们的示例中,系统会利用该类型来跟踪位置信息

base 模块的 AndroidManifest.xml 中,搜索 TODO: 2.2, Add foreground service type,并将以下代码添加到 <service> 元素中:

android:foregroundServiceType="location"

现在,您的代码应如下所示:

<application>
   ...

   <!-- Foreground services in Android 10+ require type. -->
   <!-- TODO: 2.2, Add foreground service type. -->
   <service
       android:name="com.example.android.whileinuselocation.ForegroundOnlyLocationService"
       android:enabled="true"
       android:exported="false"
       android:foregroundServiceType="location" />
</application>

大功告成!现在,只需遵循 Android 位置信息功能方面的的最佳做法,即可让您的应用支持 Android 10 的“仅在使用时允许”位置信息功能。

运行应用

从 Android Studio 运行应用,并尝试点按仅限在前台运行时位置信息功能按钮。

所有功能都应该像以前一样工作,只不过现在可以在 Android 10 上运行了。如果您之前未接受过关于位置信息的权限请求,您现在应该会看到新的权限屏幕!

在本部分中,您将添加对在 Android 10 中从后台检索位置信息的支持。通常情况下,您应该优先考虑支持仅限在前台运行时获取位置信息,但在一些用例(例如地理围栏)中,您确实需要在后台获取位置信息。

在清单中添加后台权限

Android 10 包含一项关于在后台获取位置信息的新权限,您必须将该权限添加到清单。

base 模块的 AndroidManifest 中,搜索 TODO: 3.1, Add background uses-permission to manifest

在注解下方添加新的 <uses-permission> 元素:

<!-- TODO: 3.1, Add background uses-permission to manifest. -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

现在,您即可请求始终获取设备位置信息(包括在后台运行时)。现在,我们来添加关于该权限的检查和请求。

针对 Android 10 添加代码检查

base 模块的 MainActivity.kt 中,搜索 Step 3.2, add check for devices with Android 10,并将当前版本替换为以下代码。

// TODO: Step 3.2, add check for devices with Android 10.
private val runningQOrLater =
   android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q

我们将添加代码以检查并请求新的后台位置权限。不过,除非您是在 Android 10 或更高版本上运行,否则,您并不需要使用这些代码。

现在,您可以仅在 Android 10 中包含这些新的检查/请求了。首先,我们要添加一些代码。

添加关于后台位置信息访问权限的检查

MainActivity.kt 中,搜索 TODO: Step 3.3, Add permission check for background permission,然后将注解正下方的代码行替换为以下代码:

// TODO: Step 3.3, Add check for background permission.
val backgroundPermissionApproved =
   if (runningQOrLater) {
       ActivityCompat.checkSelfPermission(
           this, Manifest.permission.ACCESS_BACKGROUND_LOCATION
       ) == PackageManager.PERMISSION_GRANTED
   } else {
       true
   }

如果您习惯于在 Android 中检查权限,应该会觉得这些代码非常眼熟。唯一的区别在于,您现在要检查的是 ACCESS_BACKGROUND_LOCATION 权限。

我们将使用 Kotlin 魔法命令来实现以下目的:

  • 检查我们搭载的系统是不是 Android 10(通过上一步)。
  • 如果是,则照常检查该权限。
  • 如果不是,我们会返回 true,因为 Android 10 之前的版本中不存在后台位置信息权限。

在以下代码行(已包含在项目中)中,您可以看到,我们只需针对前台 (ACCESS_FINE_LOCATION) 和后台 (ACCESS_BACKGROUND_LOCATION) 位置信息权限使用 && 运算符,即可确定应用是否具备始终获取设备位置信息的权限。

现在,我们知道了用户是否已批准在后台获取位置信息,但我们还需要让用户能够实际批准访问权限。

添加关于后台位置信息访问权限的请求

MainActivity.kt 中,搜索 TODO: Step 3.4, Add another entry to permission request array,并添加以下代码:

// TODO: Step 3.4, Add another entry to permission request array.
if (runningQOrLater) {
   permissionRequests.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
}

现在,如果您查看完整的代码库,它应该如下所示:

val permissionRequests = arrayListOf(Manifest.permission.ACCESS_FINE_LOCATION)
// TODO: Step 3.4, Add another entry to permission request array.
if (runningQOrLater) {
   permissionRequests.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
}

从第一行可以看到,我们将初始化一个 ArrayList 以传递给 ActivityCompat.requestPermissions()

在新代码中,我们会检查我们是不是在 Android 10 上运行;如果是,只需向 ArrayList 再添加一个条目,以便在后台获取位置信息即可。

这样就会触发用户选择他们所需的访问权限级别:仅在使用时访问(仅限在前台运行时获取位置信息的权限)或始终访问(在前台和后台运行时获取位置信息的权限)。

还要注意,ActivityCompat.requestPermissions() 实际上并不会获取 ArrayList,因此我们需要使用 toTypedArray() 将其转换为实际的 Array

在 onRequestPermissionsResult() 中检查后台位置信息是否已获批准

即将大功告成!

MainActivity.kt 中,再次搜索 TODO: Step 3.5, For Android 10, check if background permissions approved in request code,然后添加以下代码块:

// TODO: Step 3.5, For Android 10, check if background permissions approved in request code.
if(runningQOrLater) {
   foregroundAndBackgroundLocationApproved =
       foregroundAndBackgroundLocationApproved &&
               (grantResults[1] == PackageManager.PERMISSION_GRANTED)
}


此时,我们要检查我们是不是在 Android 10 上运行。如果是,我们将检查第二项权限是否已获批准。您应该还记得,在上一步中,我们的数组中的第二项是 ACCESS_BACKGROUND_LOCATION

由于我们已针对 Android 9 或更低版本在代码中检查第一个结果,因此,我们可以利用这一点,只需重复使用同一变量 foregroundAndBackgroundLocationApproved 并借助新权限对其使用 && 即可。毕竟,除非获得这两种权限,否则我们无法确定其已获批准。

完整代码应如下所示:

var foregroundAndBackgroundLocationApproved =
   grantResults[0] == PackageManager.PERMISSION_GRANTED

// TODO: Step 3.5, For Android 10, check if background permissions approved in request code.
if(runningQOrLater) {
   foregroundAndBackgroundLocationApproved =
       foregroundAndBackgroundLocationApproved &&
               (grantResults[1] == PackageManager.PERMISSION_GRANTED)
}

查看前台和后台对位置信息的调用

该步骤中无需添加任何代码。我们只需再查看一行代码即可。

搜索 TODO: Step 3.6, review method call for foreground and background location(就在您刚刚添加的代码下方,间隔几行代码)。

这就是在确认已获得所有权限后,用于开始跟踪位置信息的调用。

// TODO: Step 3.6, review method call for foreground and background location.
foregroundAndBackgroundLocationApproved ->
   startForegroundAndBackgroundLocation()

好了,大功告成!

现在,我们来试用一下我们的应用。请注意,如果您在操作过程中遇到问题,请查看 complete 模块。其中包含所有已完成/可运行的代码。

运行应用

现在,您的应用已完全支持 Android 10 位置信息功能。我们来试用一下吧!

从 Android Studio 运行您的应用。现在,如果您点击在前台和后台使用位置信息,您应该会看到以下屏幕之一(具体取决于已获批准的权限):

您会看到,在获得权限后,您的应用会开始显示相关更新。

通过按照本指南中显示的方法来检查和请求位置权限,您的应用可以成功跟踪其有关设备位置信息的访问权限等级。

若要详细了解如何确保用户数据安全,请参阅权限最佳做法指南

仅请求您所需的权限

仅在需要时请求权限。例如:

  • 请勿在应用启动时请求位置权限(除非绝对有必要)。
  • 如果您的应用以 Android 10 为目标平台,并且仅在前台运行时需要访问位置信息,请不要请求 ACCESS_BACKGROUND_LOCATION

在未授予权限时支持优雅降级

为了保持良好的用户体验,请在设计应用时确保它可以优雅地处理以下情况:

  • 应用无法访问位置信息。
  • 应用在后台运行时无法访问位置信息。

总结

在这一步骤中,您学习到了以下内容:

  • 关于 Android 中的位置信息功能的最佳做法

您已经学习了如何针对 Android 10 接收位置信息更新,并牢记平台的相关最佳做法!

如需了解详情,请访问以下资源: