后台 GPS 记录从半天掉电 30% 到全天 8%,我的三版方案演进

做了什么

我做了一个 iOS App 叫「雁过留痕」,核心玩法是把你走过的所有路转化成探索面积(km²),地图上像消除战争迷雾一样逐格点亮。底层逻辑是 25m × 25m 网格统计,GPS 轨迹经过的格子标记为已探索,累积计算总面积。

技术上最难啃的是后台 GPS 持续记录的功耗控制。这篇主要聊这个问题的演进过程,顺带说说行政区边界判定和成就系统架构踩过的坑。

后台定位:三版方案的演进

第一版:significantLocationChanges

想法很朴素------系统级显著位置变化回调,功耗几乎为零。问题是精度完全不够用,触发阈值大约 500m,在城市里走小巷子根本不会触发。记录出来的轨迹从一个路口直接跳到另一个路口,中间全丢了。25m 网格根本没法算,pass。

第二版:kCLAccuracyBest + 持续后台

切到高精度持续采样,每秒一个点,精度确实到了 5-10m 级别。代价是有用户反馈半天掉了 30% 电量。我打开 Instruments 的 Energy Log,Location 那栏全程红色,后台 CPU 唤醒频率太高。

第三版(当前):动态精度 + 运动状态判定

核心思路:移动时采样,静止时休眠

swift 复制代码
// 静止判定:连续 8 个点位移总和 < 15m 则进入休眠
func evaluateMotionState(recentLocations: [CLLocation]) -> MotionState {
    guard recentLocations.count >= 8 else { return .moving }
    let totalDisplacement = zip(recentLocations, recentLocations.dropFirst())
        .reduce(0.0) { $0 + $1.0.distance(from: $1.1) }
    if totalDisplacement < 15.0 {
        return .stationary  // 降到 kCLAccuracyHundredMeters
    }
    return .moving          // 维持高精度
}

休眠期间降到 kCLAccuracyHundredMeters,检测到移动再切回高精度。步行状态下采样间隔拉到 5-8 秒,骑行状态缩到 3 秒。

实测数据 :全天开启(早 8 点到晚 10 点),后台 GPS 模块总 CPU 时间约 4 分钟,电量消耗 8-12%(iPhone 14 Pro)。相比第二版降了 60% 以上。

行政区边界判定的三个坑

省市解锁功能要做 point-in-polygon 判断,这里踩了几个实实在在的坑:

1. 坐标系偏移

iOS 的 CLLocation 给的是 WGS-84,中国地图服务用 GCJ-02。省界附近这个偏移(100-600m)会直接判错省份。解法是内置 WGS84→GCJ02 转换,判定前统一坐标系。

2. 边界数据体积

完整的中国省市区三级边界 GeoJSON 有 40 多 MB,启动时全加载不现实。做法是按省拆分文件,根据用户粗略位置按需加载当前省份 + 相邻省份数据。首次判定延迟从 2 秒降到 200ms。

3. 飞地问题

部分地级市辖区不连续,用外接矩形做预筛选会漏判。给每个行政区存 MultiPolygon,预筛选用所有子区域外接矩形的并集。

成就系统:重构三次后的架构

做了四条徽章路线(exploration、consistency、china、world),每条下面多个系列分等级。架构演进了三版,最终方案是单次计算 + 纯函数判定

swift 复制代码
struct BadgeMetrics {
    let totalDistanceKm: Double
    let recordedDays: Int
    let currentStreakDays: Int
    let nightSegmentCount: Int
    let chinaProvinceCount: Int
    let chinaAreaKm2: Double
    
    static func build(
        stats: TraceStats,
        segments: [TraceSegment],
        geo: GeographicProfile
    ) -> BadgeMetrics { /* 所有指标集中计算 */ }
}

所有原始指标在 BadgeMetrics.build() 里一次性算完,每个徽章的 requirement 只是对 metrics 做纯函数比较。加新徽章只需在 catalog 里加一条定义,如果指标已存在连 metrics 都不用动。

之前硬编码版本加一个「夜间探索者」徽章要改四个文件联动,现在两步搞定。

存储方案:SwiftData 扛住日均 3000+ 轨迹点

轨迹点量级:步行日约 2000-4000 点/天,骑行日 8000+。累积半年单用户 50-80 万条记录。

SwiftData 的查询模式比较固定(按时间段、按 segment 分组),实测 50 万条按日期范围查询在 iPhone 12 上耗时约 80ms。

两个关键优化:

  • 分段归档:超过 90 天的轨迹点压缩成 polyline 编码存一条归档记录,原始点删除。地图展示解码 polyline 就够,面积统计从网格缓存读。
  • 网格缓存:探索面积的网格标记单独一张表,新增轨迹点时增量更新,不需要从全量点重算。

总结几个经验

  1. 后台定位不要一刀切精度等级,结合运动状态动态切换是性价比最高的方案
  2. GeoJSON 大文件一定要按需加载,别信「现在手机内存够大」这种话
  3. 成就/徽章系统如果有扩展预期,越早抽象 metrics 层越好,硬编码到第三个徽章就会爆炸
  4. 轨迹类 App 的数据增长比你想象的快,归档策略要在早期就设计好

如果你也在做地理围栏、区域判定或者后台定位相关的需求,坐标转换和边界判定的部分实现我后续打算整理成 gist 放出来,有兴趣的话可以关注后续更新。也欢迎在评论区聊聊你们在后台定位上踩过的坑。

相关推荐
AlienZHOU8 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
Captaincc11 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
计算机魔术师13 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen13 小时前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
IT_陈寒13 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
前端snow14 小时前
ai agent --- 多agent框架之图编排引擎-langgraph
前端
竹林81814 小时前
OmniPic Studio v3.2.1 核心技术架构与全平台发版解析文档
前端·浏览器
JamesZhang8007814 小时前
页面内存只涨不跌? 一次泄漏排查, 牵出 WeakMap 的诞生
前端
Z小明14 小时前
第 6 章 组件进阶
前端·vue.js
江华森14 小时前
HTTP请求的完整过程详解:从DNS解析到TCP挥手的微秒级实战分析
前端