← Kotlin / Android Course

ANDROID CORE · 111

Manifest / Runtime Permission / Location:不是「有寫 permission 就有 GPS」

Location 是多層問題:Manifest 宣告、runtime grant、provider 狀態、device sensor/network、accuracy、timestamp、lifecycle。正確設計要把每一層的 state 與 evidence 分開。

Learning outcomes

1. Manifest 是 capability declaration,不是 runtime grant

<uses-permission
    android:name=
      "android.permission.ACCESS_FINE_LOCATION"
/>

這表示 app 宣告可能使用該 capability;是否在 runtime 被允許,仍要依 permission model 與使用者決定。

2. Permission 是 state

sealed interface
LocationPermissionState {
    data object Unknown :
        LocationPermissionState

    data object Granted :
        LocationPermissionState

    data object Denied :
        LocationPermissionState

    data object
    NeedsRationale :
        LocationPermissionState
}

實際 Android API / UI flow 依平台版本與 app target 而不同,但設計上不要只保留一個 hasPermission: Boolean 就假裝所有情境相同。

3. 請求 permission 必須由明確 user action/context 驅動

User taps Start→ check permissiongranted?yesstart locationnorequest / explain

不要在 app 啟動時無脈絡地一次要求所有 permissions。

4. Location update 不只有 lat/lng

data class
LocationSample(
    val latitude: Double,
    val longitude: Double,
    val accuracyMeters: Float?,
    val speedMps: Float?,
    val timestampMs: Long
)

室內、多路徑反射、省電策略、provider 狀態,都可能讓座標或速度品質不同。至少保留 accuracy / timestamp 才有 debugging evidence。

5. Data flow

Permission state
   ↓
Location provider
   ↓
LocationSample
   ↓ validate quality
TripRepository / Domain
   ↓
TripState
   ↓
ViewModel
   ↓
UI

fare / distance business rule 不應直接綁死在 Android location callback 裡,否則難測也難重播。

6. Poor location ≠ Permission denied

現象可能 evidence
Permission deniedruntime permission result
Provider off/unavailableprovider/settings state
有座標但飄移accuracy、timestamp、速度跳動
更新太慢request interval / device power / provider behavior
背景停止lifecycle / background execution policy

7. Project checkpoint:Trip Meter Android Shell v2

fun onStartTrip() {
    when (
        permissionState
    ) {
        Granted ->
            startLocation()

        NeedsRationale ->
            showExplanation()

        Denied,
        Unknown ->
            requestPermission()
    }
}

真正 location callback 再轉成 domain LocationSample,讓計程邏輯可以脫離裝置重播測試。

8. Background location / execution

這一塊受 Android 版本、target SDK、foreground service 與平台政策持續影響。教材只建立 architecture mental model;實作時必須再查當前 Android 官方文件,不能把舊規則硬編成永遠有效。

9. Android 12+:Approximate / Precise 是使用者選擇

在 Android 12(API 31)以上,app 即使要求 precise location,使用者仍可以只授予 approximate location。若功能確實需要 precise access,runtime request 應同時包含 ACCESS_COARSE_LOCATION 與 ACCESS_FINE_LOCATION,並且 UI/logic 必須能接受只拿到 coarse 的結果。

Precise choice
  → FINE + COARSE granted

Approximate choice
  → COARSE granted only

因此「Manifest 有 FINE」或「程式 request FINE」都不等於 runtime 一定得到 precise location。

10. Foreground / Background Location 要分階段

Foreground location 與 background location 不是一次全部要求的 permission bundle。對 Android 11+ target,應先建立 foreground access,再在真正需要背景功能時個別導引 background permission;同時要求 foreground + background 的 flow 會被系統拒絕。

User uses foreground feature
  ↓
request COARSE / FINE
  ↓ foreground granted
Later, user enables background feature
  ↓
request background access separately

背景定位還牽涉 foreground service、target SDK 與平台政策;不要把「有 background permission」簡化成「任何時候都能任意啟動定位」。

11. Last known location ≠ Current location

getLastLocation() 很快、耗電低,但結果可能很舊;需要「現在」的位置時,應把 timestamp/accuracy 納入判斷,必要時使用 getCurrentLocation() 取得較新的估計。

LocationSample
  latitude
  longitude
  accuracy
  timestamp / elapsed realtime
  provider/source context

所以 Debug 流程應先問「sample 多舊?accuracy 多差?」而不是看到有 lat/lon 就直接接受。

12. Android 14+ 的 location foreground service boundary

長時間 navigation / location sharing 若使用 foreground service,現代 Android 還要求正確的 foreground service type 與對應 permission。以 Android 14 target 為例,location foreground service 要宣告 android:foregroundServiceType="location" 與對應的 foreground-service permission,而且 while-in-use location restrictions 仍然存在。

因此「Manifest location permission + startForeground()」不是完整條件;實作時必須一起檢查 target SDK、FGS type、runtime location grant、app 是否在允許啟動的位置與背景狀態。

Knowledge check

  1. Manifest 宣告與 runtime grant 差在哪?
  2. 為什麼 location sample 要保留 accuracy/timestamp?
  3. 設計一個 permission denied 但 app 仍可使用其他功能的 UI state。
  4. 收到「室內定位跳動」時,你會蒐集哪四種 evidence?