后台 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 放出来,有兴趣的话可以关注后续更新。也欢迎在评论区聊聊你们在后台定位上踩过的坑。

相关推荐
lichenyang45310 小时前
Docker 学习笔记(一):为什么需要镜像、容器和仓库?
前端
kyriewen10 小时前
别再对着 TypeScript 报错发呆了:我把 10 个最常见的红色波浪线翻译成了人话
前端·javascript·typescript
IT_陈寒10 小时前
SpringBoot自动配置的坑,我的API突然就404了
前端·人工智能·后端
奇奇怪怪的11 小时前
Embedding 模型 10+ 横向评测
前端
陈广亮11 小时前
Monorepo 从 0 到 1 实操指南 2026 版:pnpm catalogs + Turborepo 2.x + changesets 全链路
前端
子兮曰11 小时前
OpenMontage 深度解剖:你的 AI 编程助手,其实是个视频工作室
前端·后端·ai编程
敲代码的鱼11 小时前
PDF 预览与签名批注写回 支持安卓 iOS 鸿蒙 UTS插件
android·前端·ios
子兮曰11 小时前
前端工具链的「Rust 化」:一场没有赢家的军备竞赛?
前端·后端·rust
Hyyy12 小时前
Function Calling / Tool Use的原理和实现模式
前端·llm·ai编程