从 AOSP 源码看 Android 定位性能问题
源码基准:AOSP
android-17.0.0_r1
platform/frameworks/base@23149ba144e8platform/hardware/interfaces@32a116715a69写作日期:2026-10-10。文中所有类名、方法名、常量值、行号均已对照该标签实际源码逐条核实;行号以
(文件:行号)形式标注,便于直接跳转查证。
1. 为什么定位性能问题难排查
定位链路横跨应用进程、system_server、GNSS HAL 进程和基带/芯片四层,中间夹杂 Binder IPC、请求合并、权限消毒、省电策略、占空比调度等多重机制。logcat 里能看到的 LocationManager 调用栈只是入口,真正的瓶颈往往在更深的位置:LPM 的合并请求形态、GNSS Provider 的串行线程、HAL 的同步 Binder 调用,或者干脆是应用自己的注册模式。
本文完全依据 android-17.0.0_r1 源码,按「调用链逐层下钻 → 每层的性能热点 → 排查手段」的结构展开。
2. 定位链路全景(源码结构)
App 进程: LocationManager.requestLocationUpdates()
│ Binder(ILocationManager.aidl)
▼
system_server: LocationManagerService(权限校验、请求消毒、Provider 装配)
│ per-provider
▼
LocationProviderManager(请求 N→1 合并、分发管线、模糊化、节流)
│
▼
AbstractLocationProvider 体系
├─ GnssLocationProvider(GPS,进程内)
├─ ProxyLocationProvider(network / fused,绑定外部服务,通常为 GMS)
└─ PassiveLocationProvider / PassiveLocationProviderManager(被动监听)
│
▼
GnssNative(Java 单例)──JNI──▶ libservices.core-gnss
│(静态库,经 libservices.core 整体链入 libandroid_servers.so)
▼
Binder ──▶ IGnss HAL 进程(AIDL,冻结至 V7)
几个容易被旧资料误导、但源码里很清楚的事实:
- 不存在
ServiceManager.getService("gps")这类服务名寻址 。GNSS HAL 连接由 JNI 层(services/core/jni/gnss/)建立,Java 侧只面对GnssNative。JNI 静态库名为libservices.core-gnss(services/core/jni/gnss/Android.bp:11),经libservices.core以whole_static_libs链入libandroid_servers(services/Android.bp:462-465)。 - GPS Provider 可以被 overlay 替换 。
config_useGnssHardwareProvider为 false 时,LMS 先尝试用ProxyLocationProvider.create(..., ACTION_GNSS_PROVIDER, ...)绑定外部 GNSS Provider;未找到才回退到进程内GnssLocationProvider。overlay 存在时,原始 HAL 以GPS_HARDWARE_PROVIDER(需LOCATION_HARDWARE权限)单独暴露,源码注释明确说明"GNSS HAL 只支持单一客户端"(LocationManagerService.java:543-576)。 - network 必须先于 GNSS 初始化 。
onSystemThirdPartyAppsCanStart()的注释写明 GPS Provider 对 network Provider 有硬依赖(LocationManagerService.java:496-497)。完整启动顺序:network → fused → GnssNative + GnssManagerService → GnssLocationProvider(或 overlay)→ geocode → population density → hardware AR(Flags.disableHardwareAr()门控)→ geofence(LocationManagerService.java:495-615)。 GnssManagerService是 GNSS 专属 API 的汇聚点 :状态回调、NMEA、原始测量(GnssMeasurementsProvider)、导航电文、天线信息、硬件地理围栏(内部类GnssGeofenceHalModule→GeofenceHardwareImpl)、GnssMetrics健康监控,并在onSystemReady()中无条件绑定ProxyGnssAssistanceProvider注册结构化 assistance 回调(GnssManagerService.java:106-113)。
3. 应用层入口:一次注册到底发生了什么
3.1 真实调用链
LocationManager.requestLocationUpdates(provider, request, executor, listener)(LocationManager.java:1579-1607)做三件事:
-
参数校验(provider / request 非空);
-
Transport 复用 :静态表
sLocationListeners(listener → WeakReference<LocationListenerTransport>)查找该 listener 是否已有包装,没有才new LocationListenerTransport(listener, executor)------同一个 listener 重复注册会复用同一个 Binder Stub 并更新 executor(LocationManager.java:1587-1602); -
跨进程调用:
mService.registerLocationListener(provider, locationRequest, transport,
packageName, attributionTag, AppOpsManager.toReceiverId(listener))
对应 AIDL(ILocationManager.aidl:57-61):
aidl
void registerLocationListener(String provider, in LocationRequest request,
in ILocationListener listener, String packageName,
@nullable String attributionTag, String listenerId);
void registerLocationPendingIntent(String provider, in LocationRequest request,
in PendingIntent pendingIntent, String packageName,
@nullable String attributionTag);
注意:Binder 接口上没有
requestLocationUpdates,PendingIntent 变体叫registerLocationPendingIntent(不是registerLocationListenerPendingIntent)。
3.2 应用层的性能含义
- 每次注册/注销都是一次 Binder 事务 。在
onResume/onPause中反复注册注销,事务量线性累积,且会推动 GNSS 会话反复 start/stop(见 §6.3)。 - Transport 泄漏 :不调用
removeUpdates时,Stub 驻留在 system_server 的注册表里,回调持续投递,AppOps 持续记账。 - PendingIntent 请求的系统 API 禁令 :
BLOCK_PENDING_INTENT_SYSTEM_API_USAGE(change id169887240L)生效时,PendingIntent 请求禁止isLowPower、isHiddenFromAppOps、isLocationSettingsIgnored和非空 WorkSource,违反直接抛SecurityException。源码注释解释了原因:PendingIntent 没有可随权限回收而被杀掉的宿主进程(LocationManagerService.java:957-971)。
4. LocationManagerService:并发模型与校验开销
4.1 真实的锁模型
java
// LocationManagerService.java:257
final Object mLock = new Object();
// LocationManagerService.java:287
final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers = ...;
mLock是普通对象锁 +synchronized,不是 ReentrantLock;它保护的是 LMS 自身的少量状态(如mLocationUsageLogger等@GuardedBy("mLock")字段)。- Provider 查找走
mProviderManagers的遍历------CopyOnWriteArrayList读无锁,只有增删 Provider(addLocationProviderManager)才synchronized (mProviderManagers)(LocationManagerService.java:377-405)。 - 结论 :LMS 层的锁竞争比直觉轻得多。真正的串行点在 LPM 内部(
ListenerMultiplexer的mMultiplexerLock与各 Registration 状态锁)和 GNSS Provider 的单线程 executor。
4.2 请求消毒(每次注册都执行)
registerLocationListener() → validateLocationRequest()(LocationManagerService.java:981-1055)逐项处理:
| 请求属性 | 要求 | 不满足时 |
|---|---|---|
| 非空 WorkSource | UPDATE_DEVICE_STATS |
SecurityException |
isLowPower |
LOCATION_HARDWARE(LOW_POWER_EXCEPTIONS 变更生效时强制;否则静默置 false) |
SecurityException / 消毒 |
isHiddenFromAppOps |
UPDATE_APP_OPS_STATS |
SecurityException |
isAdasGnssBypass |
automotive 设备 + GPS Provider + bypass 权限 | IllegalArgumentException |
isLocationSettingsIgnored |
bypass 权限 | SecurityException |
bypass 权限校验本身已是纯权限检查------LocationPermissions.enforceBypassPermission() 只查 LOCATION_BYPASS,无 flag 参与(LocationPermissions.java:140-146)。注意 location_bypass flag 仍保留在 location.aconfig 中(is_exported),只是定位服务代码内已无调用点。
4.3 后台与省电限制的三套机制
- 后台节流白名单 :
SettingsHelper.getBackgroundThrottlePackageWhitelist(),豁免包不受后台限制(LocationManagerService.java:855)。 - 省电模式状态机 :LPM 监听
LocationPowerSaveModeHelper,五种模式在源码中的常量为PowerManager.LOCATION_MODE_NO_CHANGE / LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF / LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF / LOCATION_MODE_FOREGROUND_ONLY / LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF(Android 15 起迁入android.os.PowerManager,LPM 在LocationProviderManager.java:2361, 2561处分支处理)。 - COARSE 硬下限 :
MIN_COARSE_INTERVAL_MS = 10 * 60 * 1000,仅持粗略权限的注册,间隔与最小间隔都被钳制到 10 分钟(LocationProviderManager.java:161, 713-718)。
4.4 静止节流(Android 17 已大幅收窄)
addLocationProviderManager() 中只有同时满足三个条件才会包 StationaryThrottlingLocationProvider:Flags.keepGnssStationaryThrottling() 开启 且 Settings.Global.LOCATION_ENABLE_STATIONARY_THROTTLE == 1(手表默认 0、手机默认 1)且 目标是 GPS Provider(LocationManagerService.java:384-400)。network/fused 不再被包裹。
5. LocationProviderManager:性能问题的核心战场
LPM(3123 行)是 system_server 侧定位代码中最大的类 (作为参照,LocationManager.java 为 3745 行但运行在应用进程)。每个命名 Provider 一个实例,继承 ListenerMultiplexer。
5.1 关键常量(全部位于 155--180 行,逐字引用)
java
private static final long WAKELOCK_TIMEOUT_MS = 30 * 1000; // :155
private static final long MIN_COARSE_INTERVAL_MS = 10 * 60 * 1000; // :161
private static final long MAX_HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000; // :164
private static final long MAX_CURRENT_LOCATION_AGE_MS = 30 * 1000; // :167
private static final long MAX_GET_CURRENT_LOCATION_TIMEOUT_MS = 30 * 1000; // :170
private static final float FASTEST_INTERVAL_JITTER_PERCENTAGE = .10f; // :173
private static final int MAX_FASTEST_INTERVAL_JITTER_MS = 30 * 1000; // :176
private static final long MIN_REQUEST_DELAY_MS = 30 * 1000; // :180
5.2 请求合并:木桶效应依然成立
mergeRegistrations()(LocationProviderManager.java:2405)把 N 个注册合并成一个 ProviderRequest,经 AbstractLocationProvider.onSetRequest(ProviderRequest)(AbstractLocationProvider.java:338)下发。合并取"最严格"需求:一个高频 FINE 注册会把合并后的请求顶到高频,GNSS 芯片随之高频工作,全体 FINE 监听者收到高频回调。COARSE 注册被 10 分钟下限保护,不受拖累。
合并还叠加 10% 最快间隔抖动(上限 30 秒,LocationProviderManager.java:973-975),避免多设备同时唤醒芯片------这是平台级的防"惊群"设计。
5.3 分发管线(真实路径)
AbstractLocationProvider.reportLocation() → LocationProviderManager.onReportLocation()(LocationProviderManager.java:2657):
processReportedLocation():校验合法性;若位置缺 MSL 海拔且有椭球高,经AltitudeConverter.tryAddMslAltitudeToLocation()补海拔(DeviceConfigenable_location_provider_manager_msl门控,默认开;异步回填补缓存)(LocationProviderManager.java:2700-2735);setLastLocation()更新 last known(:2685);deliverToListeners()逐注册过滤:激活状态 → 权限等级 → COARSE 模糊化 → 最小间隔(含抖动)→ maxUpdates;- 经
LocationTransport(listener 回调 / PendingIntent / getCurrentLocation 三种实现)投递,投递期间持 30 秒超时唤醒锁(:1035)。
5.4 粗略位置模糊化的开销
LocationFudger(fudger/LocationFudger.java):
- 随机偏移每 1 小时 更新一次,每次只变化 3% (
CHANGE_PER_INTERVAL = 0.03,:47-51),再吸附网格------偏移"缓慢漂移"是为了防止多次采样取平均还原精确位置; - 网格边长默认 2 km (
DEFAULT_COARSE_LOCATION_ACCURACY_M = 2000.0f,SystemSettingsHelper.java:74),经Settings.Secure.LOCATION_COARSE_ACCURACY_M可配,下限 200 m (MIN_ACCURACY_M,LocationFudger.java:43); - 人口密度模式:
LocationFudgerCache咨询ProxyPopulationDensityProvider,城区用更细的 S2 单元、农村用更粗的单元。density_based_coarse_locations/population_density_provider两个 flag 已从location.aconfig移除,该路径不再受门控。
每次向 COARSE 注册投递都要过一次模糊化(带缓存),高频 + 大量 COARSE 注册时这部分 CPU 开销可观。
6. GnssLocationProvider 与 HAL:时延与功耗的决定层
6.1 线程模型
GnssLocationProvider 的所有 HAL 回调与请求处理串行在 provider 自己的 executor 线程上。GnssNative 定义 16 组回调接口(Base/Status/SvStatus/Location/Nmea/Measurement/AntennaInfo/NavigationMessage/Geofence/Time/LocationRequest/Psds/GnssAssistance/AGps/Notification/PowerStats,GnssNative.java:159-309),GnssLocationProvider 与 GnssManagerService 各取所需。任何一次慢操作(PSDS 下载、同步 Binder 阻塞)都会延迟其后所有位置上报------这是"定位慢"最常见的系统侧根因。
6.2 关键时序常量(GnssLocationProvider.java:170-237,逐字引用)
java
private static final long LOCATION_UPDATE_MIN_TIME_INTERVAL_MILLIS = 1000; // 1 Hz 下限
private static final long LOCATION_UPDATE_DURATION_MILLIS = 10 * 1000; // 位置注入会话时长
private static final int EMERGENCY_LOCATION_UPDATE_DURATION_MULTIPLIER = 3; // 紧急模式 ×3
private static final long MAX_BATCH_LENGTH_MS = DateUtils.DAY_IN_MILLIS; // 批量上限 1 天
private static final int NO_FIX_TIMEOUT = 60 * 1000; // 60s 无 fix 即停
private static final int GPS_POLLING_THRESHOLD_INTERVAL = 10 * 1000; // 占空比阈值
private static final long RETRY_INTERVAL = 5 * 60 * 1000; // PSDS 退避起点
private static final long MAX_RETRY_INTERVAL = 4 * 60 * 60 * 1000; // 退避上限
6.3 占空比:一个常被忽略的前提
源码里占空比逻辑都带 !mGnssNative.getCapabilities().hasScheduling() 前缀:
java
// GnssLocationProvider.java:1419-1422
if (!mGnssNative.getCapabilities().hasScheduling() && mStarted
&& mFixInterval > GPS_POLLING_THRESHOLD_INTERVAL) {
hibernate(); // 拿到 fix 且间隔 >10s → 休眠,AlarmManager 下次唤醒
}
也就是说:
- HAL 声明了调度能力(CAPABILITY_SCHEDULING)时 ,合并后的间隔直接经
setPositionMode下发(:1065-1067, 1251-1252),占空比由芯片固件自行实现,框架不干预; - HAL 不具备调度能力时 ,框架在 >10 秒间隔的场景下手工执行 Navigating → Hibernate 循环,每次唤醒伴随一次热启动。此场景下应用频繁注册/注销会把 Provider 推进反复 start/stop/再捕获循环,宏观表现为"定位慢、首 fix 时间长"。
注意现行代码没有显式的状态枚举,Idle/Navigating/Hibernate 是由 mStarted、hibernate() 与 AlarmManager 闹钟组合出的隐式状态机。
6.4 辅助数据(A-GNSS)链路
- PSDS :HAL 经
PsdsCallbacks.onRequestPsdsDownload(int psdsType)请求(GnssNative.java:275-277),GnssPsdsDownloader从GnssConfiguration中的LONGTERM_PSDS_SERVER_1..3 / NORMAL_PSDS_SERVER / REALTIME_PSDS_SERVER(GnssConfiguration.java:84-88)下载注入,失败按 5 分钟→4 小时指数退避。PsdsType为 1-based:LONG_TERM=1 / NORMAL=2 / REALTIME=3;按 AIDL 注释,三类数据的有效期量级分别是"数小时至数天"、"数小时"、"分钟级"(PsdsType.aidl)。 - 配置加载顺序 :CarrierConfig → 资源 overlay →
/vendor/etc/gps_debug.conf→/etc/gps_debug.conf,后加载者覆盖先加载者,即/etc/gps_debug.conf优先级最高(GnssConfiguration.java:278-296)。SIM/运营商配置变化触发subscriptionOrCarrierConfigChanged()重载。 - 时间注入 :
NetworkTimeHelper(NTP)→injectTime();位置注入 :network fix 经injectLocation()/injectBestLocation()回灌(GnssLocationProvider.java:559, 660-662, 714, 742-751)。 - 结构化 assistance :
GnssManagerService.onSystemReady()无条件绑定ProxyGnssAssistanceProvider并注册GnssAssistanceCallbacks(GnssManagerService.java:106-113);gnss_assistance_interface_jniflag 已移除。
6.5 已废弃的批次 API
startGnssBatch()(@SystemApi,已废弃)内部就是一次普通的 registerLocationListener,用 setMaxUpdateDelayMillis(intervalMs × 批次大小) 实现 on-chip batching(LocationManagerService.java:723-749)。这从侧面说明:应用层想要批量定位,直接用 LocationRequest.Builder.setMaxUpdateDelayMillis() 即可。
7. HAL 层:IGnss AIDL V7
7.1 版本与方法面
- AIDL 冻结至 V7 (
aidl_api/android.hardware.gnss/下有 1--7 + current;JNI 链接android.hardware.gnss-V7-cpp);Android 17 的 FCM 声明接受 2--7。 - 根接口方法:
setCallback / close / start / stop / startSvStatus / stopSvStatus / startNmea / stopNmea / setPositionMode(PositionModeOptions) / injectTime / injectLocation / injectBestLocation / deleteAidingData及一组getExtension*()。PositionModeOptions把 mode/recurrence/minIntervalMs/preferredAccuracyMeters/preferredTimeMs/lowPowerMode 打包为单一 parcelable(IGnss.aidl:281-314)。 - 扩展接口多数
@nullable(未实现返回 null),但getExtensionGnssConfiguration / getExtensionGnssDebug / getExtensionGnssAntennaInfo / getExtensionGnssVisibilityControl未标@nullable,HAL 必须实现。 - 旧资料里的
IGnss.update()/updateConstellation()不存在。
7.2 V7 新增与性能相关的能力位
IGnssCallback 新增 bit 16:CAPABILITY_ENGINE_RESTART_AFTER_POWER_MODE_CHANGE(IGnssCallback.aidl:100),SDK 侧由 GnssCapabilities.hasGnssEngineRestartAfterPowerModeChange() 暴露(flag gnss_capability_restart_engine_after_power_mode_change 门控)。它的性能含义:低功耗模式切换后引擎是否需要重启------需要重启的设备上,频繁在低功耗/普通模式间切换的代价接近重新定位,应用层应避免 interval/quality 的来回抖动。
同期转正的还有:GnssNavigationMessage.TYPE_IRN_L1(NavIC L1,flag gnss_api_navic_l1)、GnssStatus 的 codeType/elapsedRealtime(flag support_codetype_in_gnss_status)、QZSS SVID 范围 183,206→183,212(flag gnss_qzss_svid_range_extension)、结构化 assistance 的 IonexAssistance 与历书 toaSeconds 字段。
7.3 Binder 开销的三个断面
- system_server → 应用:每次位置投递一次事务;高频 × 多监听者场景下带宽可观。
- GnssNative → HAL :JNI 发起跨进程 Binder;
start()/stop()/setPositionMode()为同步调用,HAL 实现缓慢会直接阻塞 provider 线程(§6.1),放大为全局上报延迟。 - system_server ↔ fused/network(GMS) :
ProxyLocationProvider经ServiceWatcher绑定,服务死亡自动重绑;断连期间 Provider 上报 not-allowed,LPM 停止向客户端分发------表现为"定位突然没回调"。
8. 性能问题根源速查表
| 现象 | 最可能的层 | 源码机制 | 确认手段 |
|---|---|---|---|
| 首 fix 慢、定位时好时坏 | GNSS Provider | 占空比状态机被频繁注册/注销打乱(§6.3) | 统计应用注册/注销频率;dumpsys location 看注册表变化 |
| 全系统定位都变慢 | GNSS Provider 线程 | provider 单线程串行,某个同步调用阻塞(§6.1、§7.3-2) | kill -3 $(pidof system_server) 看线程栈 |
| 耗电高 | LPM 合并 | 一个高频 FINE 注册顶高合并请求(§5.2) | dumpsys location 看合并后的 ProviderRequest |
| 后台定位被掐断 | 省电策略 | LOCATION_MODE_FOREGROUND_ONLY 等(§4.3-2) |
前台服务 + FOREGROUND_SERVICE_TYPE_LOCATION |
| COARSE 应用"不更新" | 设计如此 | 10 分钟硬下限(§4.3-3) | 检查权限等级与合并间隔 |
| fused 定位突然无回调 | 绑定断连 | ServiceWatcher 重绑期间 not-allowed(§7.3-3) | dumpsys location 看 Provider 状态 |
9. 排查命令(均已在源码中核实)
bash
# 全局视图:Provider 状态、全部注册、合并请求、事件日志
adb shell dumpsys location
# GNSS 指标(proto 输出;LocationManagerService.java:1640)
adb shell dumpsys location --gnssmetrics
# GNSS 配置(注意 /etc/gps_debug.conf 优先级最高)
adb shell cat /vendor/etc/gps_debug.conf
# 开关定位(LocationShellCommand.java:60)
adb shell cmd location set-location-enabled true
# 删除辅助数据做冷启动 TTFF 基准(GnssLocationProvider.java:1175)
adb shell cmd location send-extra-command gps delete_aiding_data
# system_server 线程栈
adb shell kill -3 $(pidof system_server)
10. 优化建议
应用侧
- 导航级场景才用 1 秒 +
QUALITY_HIGH_ACCURACY;普通场景间隔 ≥30 秒或 balanced。 - 理解 10 秒阈值:间隔 >10 秒时,无调度能力的设备会进入框架占空比,省电但引入周期性热启动时延------按业务权衡,避免在 10 秒上下抖动 interval。
- 及时
removeUpdates;利用sLocationListeners的复用语义(同 listener 重复注册不新增 Stub),但仍应避免高频注册/注销。 - 批量场景用
setMaxUpdateDelayMillis()(§6.5),让芯片 on-chip batching,减少 AP 唤醒。 - 后台持续定位必须前台服务 +
FOREGROUND_SERVICE_TYPE_LOCATION。 - 关注
hasGnssEngineRestartAfterPowerModeChange()为 true 的设备:避免低功耗模式反复横跳。
系统/ROM 侧
- 给 GNSS provider 线程的
onReportLocation/onSetRequest入口出口打点,定位同步 Binder 阻塞源。 - 监控
GnssMetrics(TTFF、CN0 已内建);必要时对 HALsetPositionMode耗时设告警。 - COARSE 注册占比高的产品,注意
LocationFudger+LocationFudgerCache的投递时开销,确保密度缓存预热。 - 调整合并策略需谨慎:10% 间隔抖动、10 分钟 COARSE 下限都是隐私/功耗的硬约束,不是可调参数。
11. 参考文件(android-17.0.0_r1)
| 文件 | 路径 |
|---|---|
| LocationManager | frameworks/base/location/java/android/location/LocationManager.java(3745 行) |
| ILocationManager | frameworks/base/location/java/android/location/ILocationManager.aidl |
| LocationManagerService | frameworks/base/services/core/java/com/android/server/location/LocationManagerService.java(2073 行) |
| LocationProviderManager | .../location/provider/LocationProviderManager.java(3123 行) |
| AbstractLocationProvider | .../location/provider/AbstractLocationProvider.java |
| GnssLocationProvider | .../location/gnss/GnssLocationProvider.java(1883 行) |
| GnssManagerService / GnssConfiguration / GnssPsdsDownloader | .../location/gnss/ |
| GnssNative(Java)+ JNI | .../location/gnss/hal/GnssNative.java、services/core/jni/gnss/ |
| LocationFudger / LocationFudgerCache | .../location/fudger/ |
| 功能 flag | location/java/android/location/flags/location.aconfig |
| IGnss 及子接口 | hardware/interfaces/gnss/aidl/android/hardware/gnss/ |