为了挪动地图上的一个点, 我写了一个 App
写这篇前, 我还以为这个修改手机定位的项目有 117 颗星. 查了一下 GitHub, 已经是 118 颗了.
数字不能说明每个人都安装过或用得满意, 但有人愿意点一下 Star, 我还是很高兴. 记在这里: 2026 年 9 月 20 日, 118 stars.
项目叫 Modify Positioning, 是一个无 Root 的 Android 定位模拟 App. 在地图上选个地方, 或填一组经纬度, 系统的标准定位接口就能收到模拟位置. 固定停留, 随机步行, 沿路线移动, 都围绕这件事展开.
先把地图做顺手
手机修改定位, 很容易让人先想到一排参数和几个开关. 我自己每天要看, 还是希望它顺眼一点.
界面用 Kotlin 和 Jetpack Compose 写在 Android 上, 我把它调整成自己喜欢的分组列表: 浅灰背景, 白色分组, 蓝色主按钮, 地图上留出清楚的选点位置. 搜索, 收藏, 路线和诊断各有去处, 不把所有东西挤在第一屏.
有些修改很细. 真机上看一眼, 才知道主按钮的字会不会换行. 在代码里看着整齐, 到手机上多折一行, 整个界面就显得局促.
后来又补了手动输入经纬度. 地图拖来拖去很直观, 手里已经有一组坐标时, 再放大, 缩小, 挪准星就有点多余. 填进去, 确认, 沿用同一套选点流程, 用起来干脆得多.
默认地图用 OSM, 高德作为可选底图. 两种地图涉及不同的坐标表示, 选完后要统一转换到内部使用的目标坐标. 这里没理顺, 页面上点得再准, 后面收到的也可能是另一个地方.
三路定位, 得说同一件事
往下做, 才开始处理那个点背后的数据.
项目里接了 GPS, Network 和 Google Play services 的 Fused 定位通道. 如果各自生成位置, 速度和时间, 就容易出现一边已经往前走, 另一边还停在原处的情况.
于是把模拟样本集中到同一层生成, 再交给各个通道. 坐标在哪里, 速度是多少, 朝哪个方向移动, 都从同一份样本出发; 通道适配时再处理各自需要的差异.
固定在一个点附近, 也有要琢磨的地方. 可以有小幅, 平滑的漂移, 但不能一边说自己站着, 一边还带着刚才步行的速度和方向. 路线走到终点或按下暂停, 运动字段要跟着收住.
这些事情普通使用时不太看得见. 只盯着地图上的点, 会觉得差不多就行. 可打开诊断页, 时间, 速度和坐标各自说各自的话, 马上就知道哪里还没收拾好.
暂停和停止也得分开. 暂停是留在当前模拟位置, 停止则要关闭模拟通道. 后者不能只把按钮改回开始, 还得等底下的工作真的结束.
最磨人的, 是它晚回来的那一下
有一类问题很难从截图里看出来.
我点了停止, 又很快点开始, 上一次操作的结果却在这时候回来. 如果不区分它属于哪一次运行, 旧任务可能把新任务的状态改掉, 甚至把刚启动的东西清理掉.
这在异步定位接口里尤其烦. 发出请求不代表马上完成, 也不能靠不断多发几次来催它.
后来 Fused 通道一次只保留一个正在发送的请求, 等待期间收到的新样本合并到最新值. 回调回来时, 还要核对它属于哪一轮运行, 哪一次目标选择. 过期的结果, 就不要再插手现在的事情.
另外, 一路失败也不能随手把其他路全关掉. GPS, Network, Fused 各有状态, 还能正常工作的继续工作, 页面把不完整的情况显示出来.
这部分没有新按钮可展示, 却花了很多时间. 有些下午看起来没增加功能, 其实是在解决快速停止再开始时的一种状态错乱.
手机说还在运行, 我却看不到新位置
在小米上测的时候, 又遇到了后台的问题.
记录里有过这样一次排查: HyperOS 冻结了后台进程, 原先申请的 WakeLock 也被系统禁用了. 后来调整电池优化限制, 观察到定位样本的时间重新持续更新.
看似只是去设置里打开开关, 排查时却没这么省事. 通知栏还挂着运行提示, 很容易先怀疑是坐标没选好, 或目标 App 没刷新. 看运行状态和时间后, 才发现底下已经没在继续送新样本.
所以诊断页后来越做越具体. 选中的目标, 最近送出的坐标, 系统最近返回的位置, 放在一起看. 哪一段还在动, 哪一段已经停了, 至少不用全靠猜.
我不想让使用者遇到问题后只得到一句”重启试试”, 自己也不想每次都从重启开始.
点真的挪了, 微信却没跟着走
这部分得说实话.
有过真机检查, 系统的 GPS 和 Network 通道已经收到模拟坐标, 但打开微信, 它仍然显示真实位置. 也出现过明确提示正在提供模拟位置的情况.
诊断页已经有成功结果, 真正想看的页面却没有变化, 当时很难接受.
继续查, 能看到目标应用还在请求其他定位相关信息, 包括原始 GNSS 测量和 Wi-Fi 相关信号. 这些观察提醒我, 系统里某一路位置写进去了, 并不能直接推出另一个 App 最后一定采用它. 仅凭这些日志, 也无法还原那个 App 完整的判断过程.
Android 官方的模拟位置本来就带有可识别的标记. 官方接口文档对此写得很明确: 通过测试 provider 提供的位置, 可以用 Location.isMock() 识别出来.
这个项目走的就是官方无 Root 路线, 没有把它改造成隐藏模拟标记的工具. 多加一点漂移, 多送几次坐标, 都不会改变这个前提. 网络出口的 IP 与手机定位也不是一回事, 换 VPN 不能替代修改系统位置.
最后我把这些限制写进了说明. 它能做标准定位模拟, 可用于测试地图, 路线和定位流程, 但不能保证任意第三方 App 都接受它.
放到 GitHub 以后, 还得替下载的人想一点
自己手机上能装, 和别人下载以后能装, 又隔着一些小事.
地图服务的 Key 不能随着自己的构建配置一起发出去. 可选的高德 Key 后来改为由使用者在本机填写, 公开 APK 不带我自己的 Key. 发布包也得分清, 没签名的 release 文件不能因为名字里有 release, 就当成可以直接安装的成品.
README 里因此多了使用步骤, 诊断说明和下载提示, 让下载者不至于拿错文件.
仓库里还有 iPhone 相关的入口, 这里也顺便分清: 本篇主要写 Android 的 Modify Positioning. 我的 iPhone 项目是独立的 Aurora Location, 采用另一套实现, 使用条件看它自己的文档. 仓库里保留的旧 WLOC 引用来自 Yu9191/wloc, 原作者的工作当然要把名字和出处留下.
这样打开仓库的人能弄清该看哪一部分, 哪些是这个项目做的, 哪些来自别人的成果.
118 颗星
项目仍有没解决的限制, 但做它的过程让我弄明白了地图选点, 多通道一致性, 后台停摆, 旧回调干扰新任务, 以及公开安装包该带什么, 不该带什么.
最后留下的是一个能打开, 能操作, 能看诊断, 也能让别人研究的 App. 有代码, 有安装包, 也有未解决的问题. GitHub 上的 118 颗星, 说明这个原本只在我手机和电脑之间折腾的项目, 已经被别人看见了.
项目地址: AuroraNest / Modify_Positioning. Stars 为 2026-09-20 写作时通过 GitHub API 核对的快照. 本文结合当前源码与既有真机记录整理, 未在写作时重新进行手机验收.