安卓开发中后台定位权限限制详解

在 Android 10 之前,只要用户授予了位置权限,应用就能在任何时候获取位置,包括熄屏或切到后台时。Android 10 引入的后台定位权限(ACCESS_BACKGROUND_LOCATION,让这一切彻底改变——很多应用因此出现“前台有位置、后台位置丢失、轨迹断线”等诡异现象,且毫无崩溃日志,排查极为困难。

下面我将从权限模型的本质变化出发,深度剖析这个疑难杂症,并提供适应 Android 10 至 14 各版本的完整解决方案。


一、问题背景:Android 10 为什么要拆分位置权限?

从 Android 10 (API 29) 开始,系统对隐私做了重大改革,将位置权限一分为二:

  • 前台位置权限ACCESS_FINE_LOCATION / ACCESS_COARSE_LOCATION
    仅允许在应用前台可见时访问位置。
  • 后台位置权限ACCESS_BACKGROUND_LOCATION
    允许应用在后台(即没有任何 Activity 可见)时访问位置。

只有当 AndroidManifest.xml同时声明了大致/精确位置权限和后台位置权限,并且用户授予了“始终允许”,应用才能在后台获取位置。这是一个强制性规定,系统在检测到应用失去前台界面又没有后台权限时,会立即切断位置数据流。


二、问题表现:发生了什么?

当应用缺少 ACCESS_BACKGROUND_LOCATION 权限却尝试后台定位时,常见的现象有:

  1. 静默失败,无崩溃无异常
    使用 FusedLocationProviderClientLocationManager 时,回调突然停止,没有抛出任何异常,日志仅可能有一句 "background location access removed"
  2. 前台服务有通知,但位置停止更新
    即使应用运行着一个带“正在导航”通知的前台服务,一旦按 Home 键或熄屏,地图上的点就停止移动。
  3. 轨迹记录中断、地理围栏失效
    这类需要持续定位的功能在后台直接“定格”。
  4. 权限请求行为不符合预期
    明明每次都申请了位置权限,用户也点了“允许”,但 checkSelfPermission 检查后台权限始终为 PERMISSION_DENIED
  5. Android 11+ 对话框上根本没有“始终允许”选项
    用户想给都无法直接给。

一切症状都指向一个核心:应用没有被授予后台定位权限,且系统没有义务主动提醒开发者


三、根本原因:你没有拿到“后台通行证”

位置权限的状态需要分别检查:

val hasForeground = (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
val hasBackground = (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED)

导致 hasBackground == false 的典型原因:

  • 忘记在 Manifest 中声明
    未写 <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>,系统根本不会弹出“始终允许”选项。
  • 请求方式错误(不同 Android 版本差异巨大)
    • Android 10:直接请求 ACCESS_FINE_LOCATION 只显示“仅在使用时允许/拒绝”,必须同时请求 ACCESS_BACKGROUND_LOCATION,对话框才会出现“始终允许”选项。
    • Android 11+:系统主动移除了权限对话框中的“始终允许”,强制用户去设置页面手动开启,开发者直接请求后台权限会没有任何效果或直接跳转设置。
  • 用户选择了“仅在使用该应用期间允许”
    这仅授予前台权限,后台权限始终为拒绝。
  • 权限被系统自动重置
    Android 11 引入“权限自动重置”,如果应用长时间未使用,后台位置权限会被撤销。

四、解决方案:针对不同版本的正确权限请求流

无论是哪个 Android 版本,都需要遵循**“分步引导,按需请求,透明解释”**的原则。

方案 1:Android 10 专用——一次性请求“始终允许”

目标:在申请同时向用户提供明确的“始终允许”选项。

步骤:

  1. AndroidManifest.xml 中声明:
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
    
  2. 发起请求时,将两个权限放入一个 String 数组
    ActivityCompat.requestPermissions(
        this,
        arrayOf(
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_BACKGROUND_LOCATION
        ),
        REQUEST_CODE_LOCATION
    )
    
    效果:系统会弹出一个包含“始终允许”、“仅在使用期间允许”、“拒绝”的三选项对话框。用户选第一个则前后台权限一次性获得。

注意:如果用户之前已拒绝过,并勾选了“不再询问”,则此对话框不会出现,必须引导去设置。

方案 2:Android 11+ 必须使用的分步请求法

Android 11 开始,系统禁止权限对话框直接授予“始终允许”,requestPermissions 请求后台权限会被悄悄忽略或直接跳转设置。必须分两步走

第一步:先请求前台权限

// 先只请求精确位置(或粗略位置)
requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), REQUEST_FOREGROUND_LOCATION)

如果用户授予,应用可以在前台正常获取位置。

第二步:当需要后台定位时,检查并引导至设置

if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED) {
    // 展示解释性对话框,说明为什么需要“始终允许”
    // 用户确认后,打开应用设置页面
    val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
        data = Uri.fromParts("package", packageName, null)
    }
    startActivity(intent)
}

在设置页面,用户可手动将位置许可改为“始终”。

Android 13 (API 33) 增强:更务实的做法是使用 Intent 直接跳转到位置权限管理页:

startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))

或结合 ActivityResultLauncher 检查返回结果。

方案 3:合理利用前台服务维持“合法后台定位”

关键点:前台服务本身并不豁免 ACCESS_BACKGROUND_LOCATION。即使你启动了 foregroundServiceType="location" 的前台服务,如果权限只有前台的,当应用进入后台(如屏幕关闭),系统照样切断位置更新。

但前台服务有一个积极意义:它可以作为获取后台权限失败时的降级辅助。因为有些情景下,系统会觉得“有前台服务且通知可见”算是前台,位置更新可能短时间保留,不能依赖它保证可靠性。正确做法是:

  • 在前台服务启动的同时,确保拥有了后台权限。
  • 如果没有获得后台权限,则应停止前台服务,或转为轻量不依赖位置的任务。

AndroidManifest 中需声明前台服务类型:

<service 
    android:name=".LocationService"
    android:foregroundServiceType="location"
    android:exported="false"/>

启动时指定类型:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    startForegroundService(Intent(this, LocationService::class.java).apply {
        // 可选传参
    })
}
方案 4:替代技术——能不用后台权限吗?

并不是所有后台定位场景都必须持有 ACCESS_BACKGROUND_LOCATION

  • 地理围栏(Geofence)
    通过 GeofencingClient 注册围栏,系统代劳监听,应用进程即使被杀死也能收到 Intent不需要后台位置权限(但需要前台位置权限用于注册)。
  • 活动识别(Activity Recognition)
    检测用户步行、驾车等状态,无需位置权限,非常适合运动健康类应用。
  • 被动定位
    监听其他应用广播的位置(很少用且受限)。

如果你的需求只是“用户离开某地时报警”,优先使用 Geofence,完全绕开后台定位权限的复杂性和耗电问题。


五、特别注意事项与版本陷阱

1. Android 12 精确位置 vs 大致位置

Android 12 用户在授权时可以选择“大致位置”(仅提供粗略坐标)。如果你的应用必须精确位置,应同时声明 ACCESS_FINE_LOCATION,并在运行时检查是否获得了精确权限。可在权限请求时通过 shouldShowRequestPermissionRationale 解释为何需要精确位置。

2. 权限自动重置(Android 11+)

如果用户几个月未使用应用,后台位置权限会被自动重置为“仅在使用时允许”。应用需在每次启动或关键功能使用时重新检查并引导。

3. 国产 ROM 的“阉割”与“智能授权”

部分厂商系统可能完全不提供“始终允许”的选项,或者将后台定位直接与“自启动”绑定。需额外适配引导用户打开后台活动、电池优化白名单等。

4. 模拟测试

可以使用 ADB 直接赋权来验证代码逻辑:

# 授予后台位置权限
adb shell pm grant <包名> android.permission.ACCESS_BACKGROUND_LOCATION
# 模拟应用进入后台
adb shell am force-stop <包名>  # 或手动退到桌面

六、最佳实践总结

  • 永远显式检查后台权限,不要假设给了前台就一定有后台。
  • 在合适的时机请求,而非应用启动即要:当用户开启导航、运动记录等需要后台定位的功能时再引导应用,并提供清晰的文字解释(“我们后台运行是为了记录您的跑步路线,不会泄露隐私”)。
  • 尊重用户选择,如果用户拒绝“始终允许”,要设计降级方案(例如只能在前台记录,切后台时暂停并提示“需要后台定位权限”)。
  • Android 10 和 11+ 必须分流处理:10 使用一次性组合请求;11+ 务必引导设置。
  • 优先使用 Geofence、Activity Recognition 等系统托管 API,降低对后台位置的硬依赖。
  • 记录权限状态变化,监听 onRequestPermissionsResult 并适时提醒用户。

掌握以上方案,你就可以让应用在 Android 10+ 的严苛隐私限制下,依然稳定、合规地实现后台位置追踪,彻底告别那个“后台轨迹突然静止”的离奇 bug。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐