资讯详情

RK3566 android12 虚拟GPS定位实现

📅 2026/10/15 1:48:27 | 华诺云谱 👁 阅读
RK3566 android12 虚拟GPS定位实现
RK3566 android12 虚拟GPS定位实现不改 App、不 root第三方定位软件直接可用适用RK3566 / RK356X 平台 Android 12。整机没有 GPS 模组或者有模组但装在天线收不到星的位置而系统需要稳定上报一个固定坐标。典型场景广告机、工控一体机、自助终端、展厅样机、车载终端等固定安装的设备。0. 先说效果和代价改完之后的表现任意第三方定位 App 直接可用—— 不需要 root、不需要装 mock 框架、不需要开「允许模拟位置」、App 也不需要声明任何额外权限App 无法从 API 层面分辨—— 因为坐标是从真实的 GNSS provider 链路出来的不是 Mock Provider所以Location.isFromMockProvider()返回false开机即有定位不依赖 adb、不依赖任何人工操作这一点对出货设备是决定性的。代价也说清楚要改系统源码、要重新编译烧录坐标是静态的不会移动所以轨迹类应用运动记录、导航行为会不正常坐标存在persist属性里恢复出厂设置会被清掉。和另外三种做法比一下同样是「让设备上报一个假坐标」路子差别很大选错会白干方案改动量需要 adb / root开机即生效所有 App 无感可用适用场景A. 打 GNSS HAL 补丁本文改系统源码要刷机不需要是是出货整机、固定点位设备B.cmd locationTest ProviderAndroid 11 官方接口零改代码需要每次开机配一次否重启失效是但是否被打 mock 标记需按版本实测研发调试、样机演示C. 开发者选项 Mock 定位 App零改代码不需要需人工开开关否会被isFromMockProvider()识别部分 App 直接拒绝个人测试D. 在自家 App 里注入坐标改 App不需要是只对这一个 App 有效只调自己应用B 方案的具体用法不同 Android 版本子命令名可能有细微差异以adb shell cmd location -h为准adb shell cmd location providers add-test-provider gps_test adb shell cmd location providers set-test-provider-location gps_test--location22.5431,114.0579--accuracy8adb shell cmd location providers set-test-provider-enabled gps_testtrue为什么出货设备必须走 AB 依赖 adb且重启失效 —— 设备到了客户手里没人会去跑 adb。A 把坐标固化进系统开机就在。一、原理为什么必须从 HAL 这一层下手Android 的定位链路是分层的第三方 App地图 / 打卡 / 定位测试工具 ↓ LocationManager / LocationManagerService 在这里判断 provider 是否可用、位置是否 mock ↓ GnssLocationProvider ↓ IGnss HAL 服务android.hardware.gnss1.0 ↓ 厂商 HAL 实现 ←── 本文就改在这里 ↓ /dev/ttyXXX真实 GPS 模组本方案没有这一步关键在于mock 标记是在 HAL 之上、由 Mock Provider 注入时才打上的。一旦跳过 HAL 这一层、直接在厂商实现里产出一个 fix上层全链路都会把它当成「真实卫星定位」——LocationManagerService看到的只是一个普通的 GNSS 位置更新没有任何可疑字段。这就是「实测第三方任意定位软件均可使用」的技术原因也是这个方案和装 mock App 的本质区别。下面按顺序做三步让系统认到 GPS 能力 → 把 HAL 跑起来 → 让 HAL 上报一个固定坐标。二、第一步让系统认到 GPS 这个能力2.1 在 compatibility matrix 里放开 gnss 1.0Android 12 引入了 VINTF compatibility matrix系统会校验「设备实际提供的 HAL」是否落在「框架允许的范围」内。矩阵里原本只声明了2.0-1而 TD-GNSS-HAL 提供的是1.0所以要把 1.0 加进允许集合。--- a/hardware/interfaces/compatibility_matrices/compatibility_matrix.6.xml b/hardware/interfaces/compatibility_matrices/compatibility_matrix.6.xml -225,6 225,7 /hal hal formathidl optionaltrue nameandroid.hardware.gnss/name version1.0/version version2.0-1/version interface nameIGnss/name2.2 在 manifest.xml 里注册 HAL 服务matrix 声明的是「允许提供哪些版本」manifest 声明的是「实际提供了哪些版本」。两个文件必须配套。manifestversion1.0typedevicehalformathidlnameandroid.hardware.gnss/nametransporthwbinder/transportversion1.0/versioninterfacenameIGnss/nameinstancedefault/instance/interface/hal/manifest⚠️两处的version必须一致。matrix 里写了1.0而 manifest 里写别的版本或反过来会出现编译期校验失败或服务起不来的问题。这是移植 GNSS 时最常犯的错改完养成顺手对一遍的习惯。2.3 声明 GPS feature并拷贝权限声明文件android.hardware.location.gps.xml这份文件声明了FEATURE_LOCATION_GPS等定位相关能力。App 调PackageManager.hasSystemFeature(FEATURE_LOCATION_GPS)判断设备有没有 GPS 时读的就是它。这一步容易漏不拷这个文件即使 HAL 已经正常跑起来、框架层也能拿到坐标仍然会有一部分 App 认为「这台设备没有 GPS」而直接禁用定位功能。BOARD_HAS_GPS : true ifeq ($(BOARD_HAS_GPS),true) PRODUCT_COPY_FILES \ frameworks/native/data/etc/android.hardware.location.gps.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.location.gps.xml endif用一个BOARD_HAS_GPS开关把这段包起来是为了同一份 device 配置能同时支持「带 GPS 的机型」和「不带 GPS 的机型」不用改代码分支 —— 多机型项目建议保留这个写法。三、第二步移植 TD-GNSS-HAL待补充—— 这里请把你当时实际移植的步骤和补丁贴进来源码/so 放哪个目录、PRODUCT_PACKAGES加了哪个 service、配置文件怎么改。下面是我按通用流程整理的清单可以对照着检查和补全。TD-GNSS-HAL 是泰斗TD1030 系列GNSS 芯片的 HAL 实现对外提供android.hardware.gnss1.0的 HIDL 接口。移植一般涉及四件事放置源码 / 预编译库按项目规范放到hardware/或vendor/下注意Android.mk/Android.bp里的模块名和路径要与后面的PRODUCT_PACKAGES对应。加入编译PRODUCT_PACKAGES android.hardware.gnss1.0-service或厂商自带的 service 名否则烧完机lshal里根本看不到这个服务。产品配置串口节点名、波特率之类的配置项常见于tdgnss.conf之类的文件。本文方案虽然不用真实串口但配置文件里的必要项不能缺—— 如果 HAL 在初始化阶段因为找不到配置/节点而提前return后面你注入的 fix 永远发不出去现象就是「代码都改对了但 App 就是没定位」。SELinuxHAL 进程运行在hal_gnss域它需要能读取persist.gps.*这几个属性、以及访问它自身需要的节点。属性如果没有在property_contexts里登记读属性会被策略拦掉。移植完先验证 HAL 是否真的起来了再往下改代码# 确认 HIDL 服务已注册adb shell lshal|grep-ignss# 看 GNSS 相关日志adb logcat-ball|grep-iEgnss|gps# 看 SELinux 拦截adb shelldmesg|grep-iavclshal里看不到android.hardware.gnss1.0::IGnss/default的话先别动上报代码 —— 问题一定在第 1~4 步里。这一步能省掉大量「改了半天没反应」的排查时间。四、第三步改上报流程从属性里读坐标改动位置tdgnss.c的nmea_reader_parse。思路是把「从串口解析 NMEA 报文」换成「从系统属性读经纬度」其余上报路径完全不动。4.1 读取自定义属性charpropbuf_latitude[PROPERTY_VALUE_MAX];property_get(persist.gps.latitude,propbuf_latitude,0);charpropbuf_longitude[PROPERTY_VALUE_MAX];property_get(persist.gps.longitude,propbuf_longitude,0);charpropbuf_latitudeHemi[PROPERTY_VALUE_MAX];property_get(persist.gps.latitudeHemi,propbuf_latitudeHemi,N);charpropbuf_longitudeHemi[PROPERTY_VALUE_MAX];property_get(persist.gps.longitudeHemi,propbuf_longitudeHemi,E);属性名分开用的是persist.前缀运行时可写setprop后重启生效 —— 这样同一套固件可以装到不同城市的不同设备上现场只改属性、不用重编。4.2 填充 fix 结构体r-sv_status.num_svs6;//卫星数量r-sv_status_changed1;//状态回调r-sv_status.used_in_fix_mask|(1(r-sv_status.num_svs));r-fix.accuracy8.0;//精度r-fix.timestamp1685782786;//时间戳r-fix.latitudestrtod(propbuf_latitude,NULL);r-fix.longitudestrtod(propbuf_longitude,NULL);r-fix.flags|GPS_LOCATION_HAS_LAT_LONG;r-fix.flags|GPS_LOCATION_HAS_ACCURACY;D(lat : %f lon : %f ,r-fix.latitude,r-fix.longitude);4.3 四处必须核对的细节这一节比代码本身重要上面这段是我原始版本的做法能跑通但有四个点建议每台设备都对着核一遍它们对应的是四类不同的「莫名其妙不生效」①used_in_fix_mask的位运算r-sv_status.used_in_fix_mask|(1(r-sv_status.num_svs));used_in_fix_mask是「哪几颗卫星参与了定位」的位图bit i 对应sv_list[i]。当num_svs 6时有效下标是 0~5掩码应该是0x3F而1 6得到的是0x40指向的是第 7 颗 —— 一个并不存在的卫星。后果一部分 App 会遍历0 ~ num_svs逐位检查「这颗星星用没用上」结果全部为 0判定为「没有卫星参与定位」从而丢弃这个 fix。建议改成按位数生成掩码uint32_tnr-sv_status.num_svs;r-sv_status.used_in_fix_mask(n32)?0xFFFFFFFFu:((1un)-1);用而不是|避免多次进入这个函数时掩码被越叠越乱。② 半球标志读出来了但没有参与换算propbuf_latitudeHemi/propbuf_longitudeHemi这两个值在上面只被property_get读出后面没有任何地方用到它们。而r-fix.latitude/longitude是带符号的十进制度—— 南纬、西经必须取负值。也就是说当前代码在南北回归线以外的地区会直接定错位置读进来S却当成北纬坐标会跑到地球另一侧的对称点。要支持全球坐标补上换算doublelatstrtod(propbuf_latitude,NULL);doublelonstrtod(propbuf_longitude,NULL);if(propbuf_latitudeHemi[0]S||propbuf_latitudeHemi[0]s)lat-lat;if(propbuf_longitudeHemi[0]W||propbuf_longitudeHemi[0]w)lon-lon;r-fix.latitudelat;r-fix.longitudelon;如果项目只在国内使用那更简单 —— 直接不用这两个属性省得后人误以为它生效了。③ 时间戳是硬编码的r-fix.timestamp1685782786;//时间戳固定值意味着这个 fix 的时间永远停在那一刻不会前进。地图类应用会显示一个恒定的「最后更新时间」记录轨迹的应用会因为时间不递增而丢弃后续点。建议改成取实时时间并注意单位要和本文件里其他位置对timestamp的处理保持一致不同 SDK 版本里这个字段存在「秒」和「毫秒」两种写法改之前先搜一下同文件里其他地方的赋值方式照抄单位structtimevaltv;gettimeofday(tv,NULL);r-fix.timestamp(int64_t)tv.tv_sec*1000tv.tv_usec/1000;④ 属性默认值0会让设备定位到 (0, 0)纬度0、经度0在几内亚湾是客户端地图上经典的「空岛」位置。没配置过属性的机器会老老实实报到这里。给一个合理的兜底值比如项目所在地或者加个判断属性没设置就不上报GPS_LOCATION_HAS_LAT_LONG让 App 表现为「暂时无定位」而不是「定位到了非洲西海岸」。4.4 属性配置与 SELinux坐标用setprop写进去adb shell setprop persist.gps.latitude22.5431adb shell setprop persist.gps.longitude114.0579adb shell setprop persist.gps.latitudeHemi N adb shell setprop persist.gps.longitudeHemi E# 确认写进去了adb shell getprop persist.gps.latitude两个提醒persist.属性落盘在/data/property/下所以/data必须先挂载好—— 如果 HAL 启动得比/data挂载还早读到的就是默认值现象和「属性没设」一模一样HAL 进程读属性同样受 SELinux 约束。setprop能写不代表 HAL 能读新属性名如果没有在property_contexts里登记读侧可能被拦掉。排查方法还是看dmesg | grep avc。五、验证改完刷机后按顺序看# 1) 写入坐标adb shell setprop persist.gps.latitude22.5431adb shell setprop persist.gps.longitude114.0579adb shellreboot# 2) HAL 是否起来了adb shell lshal|grep-ignss# 3) 代码里那行 D() 打出来的坐标对不对adb logcat-ball|greplat :# 4) 框架层实际吐出来的位置adb shell dumpsys location# 5) 设备有没有宣称自己支持 GPSadb shell pm list features|grep-ilocation最后一步就是装一个第三方定位软件或者地图 App看实际落点。原文的实测截图保留作为证据六、几个必须知道的限制坐标是静态的。速度、方位角恒为 0位置永远不动。导航类 App 会一直提示「重新规划路线」运动记录类 App 会记不到里程。这是方案本身的边界不是 bug。部分 App 会做合理性交叉校验。除了看 GNSS还会拿基站、Wi-Fi、IP 归属地做比对。如果你上报的坐标和设备的实际网络环境差得太远对方平台侧仍有可能判定异常 —— 换的是 HAL换不掉服务端的风控。恢复出厂会丢。persist属性在/data下恢复出厂即被清空坐标掉回默认值。要求「出厂永久固定」的场景需要把兜底值直接写在代码里。合规提示。这个方案的正确用途是自有设备的测试、演示、固定点位上报。不要用来伪造考勤打卡、或者绕过第三方平台的定位限制—— 那是另一回事风险和责任都不在技术层面。七、小结整个方案的核心只有一句话在 GNSS HAL 这一层直接把 fix 造出来绕过真机卫星让框架和所有上层 App 都把它当成真实定位。三步的顺序不能乱先让系统认到 GPS 能力—— matrix manifest 的版本要一致权限声明文件不能漏否则 HAL 起来了 App 也当你不支持再把 HAL 跑起来—— 用lshal确认服务注册成功这一步不过就别改上报代码最后改上报流程—— 从属性读坐标同时核对used_in_fix_mask、半球符号、时间戳、默认值这四处。第 2 步和第 3 步之间那个「HAL 初始化失败导致 fix 永远发不出去」的坑是这类移植里花时间最多的地方 —— 现象和「属性没读到」几乎一样但根因完全不同lshal一查就能分流。深耕 Android 嵌入式与 RK 平台系统级开发多年日常处理显示、定位、存储、开机时序这类系统级问题。如果你在做 RK 平台整机项目时遇到类似的系统级疑难欢迎留言或私信交流。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑