这篇未完成的文章写于2016年初,两年后翻到了,贴出来。。。
Android系统设计了一个以LocationManagerService为核心的位置管理架构提供相关的位置服务[1],以下基于Android4.4的代码展开。
图1 LMS原理
1. LocationManagerService和其客户端LocationManager。
LocationManagerService和Android Java Framework中其他Service一样由SystemServer创建并运行在system_process进程中。LocationManagerService内部将统一管理Android平台中能提供位置服务的相关模块,而LocationManager为那些需要使用位置服务的应用程序服务。LocationManager和LocationManagerService之间通过Binder进行交互
2. Android平台中能提供位置服务的相关模块统称为Location Provider(位置提供者,LP)。位置提供者必须实现LocationProviderInterface接口。这些接口对应的对象实例由LMS来创建和管理。在所有这些位置提供者中,Android Framework实现了其中的PassiveProvider和GpsLocationProvider。这两个LP由LMS创建并运行在system_process进程中。
3. 除了使用GPS定位外,系统还支持网络定位(NetworkLocation)方法来获取位置信息。这种方法大致的工作原理是,某地区的移动通信基站(Cell Tower)或无线网络AP的位置信息都已事先获取并保存在相关服务提供商的服务器上。当手机使用网络定位时,它首先向服务器查询自己所连接或搜索到的基站位置或AP的位置,然后根据信号的强度推算自己的大致位置。相比GPS定位而言,网络定位速度快,耗电少,适用于室内和室外,但精度较GPS差。Android原生代码并不提供Network Location Provider相关的功能,它一般由第三方应用厂商提供,例如Google的GMS(Google Mobile Service)包中有一个NetworkLocation.apk就提供了该功能,而国内上市的手机则使用百度公司提供的NetworkLocation_Baidu.apk。它们运行在应用程序所在的进程中。
4. FusedLocationProvider是一个比较特殊的LP。它本身不能提供位置信息,其内部将综合GpsLP和NetworkLP的位置信息,然后向使用者提供最符合使用者需求的数据。即它能根据使用者对电源消耗、精度两方面的要求以选择GpsLP或/和NetworkLP
作为真实的LP。同时,FusedLP能选择GpsLP或NetworkLP提供的位置信息中最好的那一个返回给使用者。简单点说,FusedLP出现之前,一个比较完善的LP客户端需要同时操作和管理GpsLP和NetworkLP,而有了FusedLP后,客户端只需要使用它即可,其余事情由FusedLP内部来管理。注意,FusedLP也由应用程序提供,它运行在FusedLocationProvider.apk所在的进程中。
5. GeoCodeProvider: 除了提供位置信息外,系统(借助第三方应用提供)还支持位置信息和地址信息相互转换,即得到某个地址(如国家、市区、街道名等)的位置信息(如经纬度信息),或者根据位置信息得到其对应的地址信息。由于地址和位置信息的映射关系一般也由第三方应用提供,所以LMS 和第三方应用中实现GeocodeProvider的对象交互。
6. libgps.so 包含了对GPS NMEA协议信息的解析, 其源代码主要位于
hardware/libhardware_legacy/gps/gps_qemu.c
NMEA是(National Marine Electronics Association )为海用电子设备制定的标准格式,是GPS接收机最通用的数据输出格式。
1. 设计原则: 低耦合,可替换
从上一节的AndroidLocation原理图上可以看出,GPS接入的处理在硬件抽象层(HAL)的libgps.so 对于普通的应用层App,是无法对此作出修改的。
如果直接修改HAL的代码,固然简单,但是需要拿到整个系统的源码,这种情况只能适用于特殊的机型,不属于本文通用框架的考虑范畴。
本文提出低耦合的,可以替换各种GPS来源的框架,既有利于架构分层明晰,也有利于对不同的GPS设备做对比的评测。
Android 处理之前提到的各种LocationProvider以外,还提供了一种模拟Location的Provider。借助这个特性,可以对LocationManager设置模拟的位置信息,从而达到不改变App层的代码,就能使用各种来源GPS的效果。
该方案的性能虽然略弱于直接修改HAL,但是贵在维护性好,扩展性好,能够满足大部分定位要求。
2. 设计原则:可记录,可回放
记录信号和事后回放是LBSApp的常用调试手段,对于不同的GPS来源,使用LocationManager的位置输出,作为统一的位置记录格式。
各个GPS Agent 可以按实际情况记录原始的位置信息。
3. 通用设计架构图
图2 通用架构图
LBS App 就是我们主要的基于位置的App。用于记录位置信息,使用位置信息。
MockLocationProvider是一个单独的App,用于提供伪装的位置服务,该位置信息则由蓝牙,COM口或者网络连接获取的GPS数据来解析得到。GPS位置信息提取之后,构造MockLocation传给LocationManager。LocationManager将该MockLocation视同真正的位置信息,提供给所有App使用。
以上两个App使我们的主要制品。尤以MockLocationProvider(GPSAgent) 最为重要,因为LBS App可以是其他的 通用App,如Baidu Map,AutoMap之类。
图3 蓝牙GPS Agent 序列图
上图实现基于AndroidSDK 由Java实现。
首先操作从UI层发起,选择已经配对的蓝牙GPS的名称。然后启动新的线程来连接蓝牙,之所以要启动新的线程,是因为UI线程不能被阻塞,Android建议界面的刷新频率在60fps,即每帧处理时间应在1000/ 60 = 16ms 之内,对于连接外设这种无法预期的操作,自然要放在UI线程之外了。
连接蓝牙设备成功之后,就要读取蓝牙GPS发过来的数据了,一般来说是启用新的线程来读取,至于为什么不直接在连接线程就读取呢,我觉得这样也是可行的,而且线程少也容易管理。不过Android SDK给出的示例代码是启用新的线程[2],这样逻辑更清晰些,但是程序复杂度会增加,使用者可以自行斟酌,本项目采用新的读取线程来处理。
从蓝牙GPS读取的数据是NMEA数据,需要进行解析,然后填充MockLocation发送给LocationManager。
NMEA-0183协议定义的语句非常多,但是常用的或者说兼容性最广的语句只有$GPGGA、$GPGSA、$GPGSV、$GPRMC、$GPVTG、$GPGLL等。下面给出这些常用NMEA-0183语句的字段定义解释
图4 NMEA语句结构
NMEA Frame 以$开头 以"\r\n"结尾
字段前两个字符表示GNSS的TalkID
NMEA Talk IDs
配置后的GNSS |
Talker ID |
GPS, SBAS, QZSS |
GP |
GLONASS |
GL |
Galileo |
GA |
Beidou |
GB |
其他兼容GNSS |
GN |
表1 GNSS TalkerID
一般GPS都写成GP,如有其它卫星的数据则写成GN,有些GPS接收机可以被用户配置指定TalkerID,本文都以GP为例。
字段后3个字符表示NMEA语句的类型格式
常用语句如下:
类型 |
全称 |
信息 |
GGA |
Global positioning system fix data |
GPS定位数据时间、经度、纬度、所用到的卫星数和高度 |
RMC |
Recommended Minimum Specific GPS/TRANSIT Data |
推荐的最少GNSS数据:时间、经度、纬度、高度、系统状态、速度、线路、日期 |
GLL |
Geographic Position |
地理位置-经度、纬度、时间和卫星运行状态 |
GSA |
GPS DOP and Active Satellites |
GNSS DOP,活动卫星编号,测量模式2D或3D |
VTG |
Track Made Good and Ground Speed |
地面速度信息 |
表2 常用NMEA语句类型
GGA示例
$GPGGA,092204.999,4250.5589,S,14718.5084,E,1,04,24.4,12.2,M,19.7,M,,0000*1F
字段0:$GPGGA,语句ID,表明该语句为Global Positioning System Fix Data(GGA)GPS定位信息
字段1:UTC 时间,hhmmss.sss,时分秒格式
字段2:纬度ddmm.mmmm,度分格式(前导位数不足则补0)
字段3:纬度N(北纬)或S(南纬)
字段4:经度dddmm.mmmm,度分格式(前导位数不足则补0)
字段5:经度E(东经)或W(西经)
字段6:GPS状态,0=不可用(FIX NOTvalid),1=单点定位(GPS FIX),2=差分定位(DGPS),3=无效PPS,4=实时差分定位(RTK FIX),5=RTK FLOAT,6=正在估算
字段7:正在使用的卫星数量(00 - 12)(前导位数不足则补0)
字段8:HDOP水平精度因子(0.5 - 99.9)
字段9:海拔高度(-9999.9 - 99999.9)
字段10:单位:M(米)
字段11:地球椭球面相对大地水准面的高度 WGS84水准面划分
字段12:WGS84水准面划分单位:M(米)
字段13:差分时间(从接收到差分信号开始的秒数,如果不是差分定位将为空)
字段14:差分站ID号0000 - 1023(前导位数不足则补0,如果不是差分定位将为空)
字段15:校验值($与*之间的数异或后的值)
RMC示例
$GPRMC,024813.640,A,3158.4608,N,11848.3737,E,10.05,324.27,150706,,,A*50
字段0:$GPRMC,语句ID,表明该语句为Recommended Minimum Specific GPS/TRANSIT Data(RMC)推荐最小定位信息
字段1:UTC时间,hhmmss.sss格式
字段2:状态,A=定位,V=未定位
字段3:纬度ddmm.mmmm,度分格式(前导位数不足则补0)
字段4:纬度N(北纬)或S(南纬)
字段5:经度dddmm.mmmm,度分格式(前导位数不足则补0)
字段6:经度E(东经)或W(西经)
字段7:速度,节,Knots
字段8:方位角,度
字段9:UTC日期,DDMMYY格式
字段10:磁偏角,(000 - 180)度(前导位数不足则补0)
字段11:磁偏角方向,E=东W=西
字段12:模式,A=自动,D=差分,E=估测,N=数据无效(3.0协议内容)
字段13:校验值($与*之间的数异或后的值)
MockLocation 的字段来源[3]
android.location.Location |
API含义 |
NMEA 语句 |
float getAccuracy() |
Returns the accuracy of the fix in meters. 定位精度 |
GGA Horizontal dilution of position 水平方向的精度因子 hdop* 10 作为近似的精度 |
double getAltitude() |
Returns the altitude of this fix. 海拔 |
GGA 的海平面 |
float getBearing() |
Returns the direction of travel in degrees East of true North 方向角,正北偏东度数 |
RMC 的 Track angle in degrees True |
Bundle getExtras() |
Returns additional provider-specific information about the location fix as a Bundle 可自定义,暂不处理 |
- |
double getLatitude() |
Returns the latitude of this fix. 定位时的纬度 |
GGA 或者 RMC的纬度 |
double getLongitude() |
Returns the longitude of this fix. 定位时的精度 |
GGA 或者 RMC的精度 |
String getProvider() |
Returns the name of the provider that generated this fix, or null if it is not associated with a provider. |
默认 GPSProvider |
float getSpeed() |
Returns the speed of the device over ground in meters/second. 地面速度 m/s |
RMC Speed over the ground in knots 单位需转成m/s |
long getTime() |
Returns the UTC time of this fix, in milliseconds since January 1, 1970. 自1970-1-1以来的UTC时间,单位ms
|
RMC UTC Date and Time GGA 只有时间没有日期,不能用 |
表3 Android Locaton和NMEA语句对应关系
根据上表的总结
从RMC获取经纬度,UTC时间,速度,方向角
从GGA获取海拔和精度信息
只需要解析2句NMEA即可完成填充MockLocation的任务
DGPS(Differential Global Positioning System,差分GPS),用于提高GPS的定位精度
图5 DGPS原理
为了减少(或者修正)定位计算的误差,人们事先把GPS接收机放在位置已精确测定的点上,这些点叫基站。基站的接收机通过接收GPS卫星信号,测得并计算出它们到卫星的伪距,将伪距和已知的精确距离相比较,求得该点在GPS系统中的伪距测量误差。然后这些基站再将这些误差作为修正值以标准数据格式通过播发台向周围空间播发。
在基站附近的DGPS用户一方面接收GPS卫星信号进行测距,同时它接收来自基站的误差修正信息,并以此来修正定位结果,从而提高定位精度。
DGPS用户离基站多远才算附近呢?下面有两个参考距离。
如果使用伪距差分定位(CodeDifferential Positioning)技术,则DGPS和基站的距离最好在200千米以内。
如果使用载波相位差分定位(Carrier-PhaseDifferential Positioning)技术,则DGPS和基站的距离最好在20千米内。
当修正数据用卫星发送时,这种系统就叫SBAS。
Satellite Based Augmentation System (SBAS)
– 利用地球静止轨道卫星建立的地区性广域差分增强系统
当然,SBAS的功能远不止简单地播发修正数据,它还能监测GPS或其他GNSS卫星的情况以加强信号的可靠性和安全性。展示了目前几个已投入使用或在建的SBAS系统以及它们的覆盖范围。
图6 全球主要SBAS示意图
几个主要的SBAS从左至右分别如下。
·WAAS:美国建造的广域增强系统(Wide Area AugmentationSystem)。
·EGNOS:欧盟建造的欧洲静地导航覆盖服务(EuropeanGeostationary Navigation Overlay Service)。
·GAGAN:印度建造的地球同步轨道增强导航系统(GPS And GEOAugmented Navigation)。
·Beidou:中国建造的北斗导航系统。
·MSAS:日本建造的多功能卫星增强系统(MultifunctionalSatellite Augmentation System)。
虽然每个单独的SBAS只能覆盖一定的范围,但通过RTCA DO-229C协议,SBAS之间的数据能够保证兼容性。
DGPS一般都是通过串口读写,但是Android SDK并未直接提供读写串口的API。需要通过一些特殊的方式实现
方案1: 使用NDK的Linux C函数,在取得设备权限的情况下,读写串口设备文件,方法和Linux类似。
NDK全称是Native Development Kit, 是Google官方的Native开发包,支持 C/C++
串口读写相关API如下
Linux C API |
作用 |
open |
打开或创建文件(串口视同文件描述符) |
cfsetospeed |
设置输出波特率 |
cfsetispeed |
设置输入波特率 |
tcgetattr |
获取与终端相关的参数 |
cfmakeraw |
制作新的终端控制属性 |
tcsetattr |
设置终端参数 |
read |
读文件 |
write |
写文件 |
close |
关闭文件描述符 |
表4 Linux下串口操作API
方案2:如果一定要用Java来读写串口,具体做法是通过C的open函数获取到串口的文件描述符,间接地得到串口的句柄,然后传给Java层处理。
Java没有FileDescriptor 的公开的构造方法,需要在C中通过反射的方式构造。
// JNI public native static FileDescriptor openSerialPort(String path, int baudrate, int flags);
jobject JNICALL Java_com_hehe_netserial_SerialPort_openSerialPort (JNIEnv * env, jclass thiz, jstring jstrPath, jint baudrate, jint flags) { int fd; speed_t speed; jobject mFileDescriptor; // 前面和C一样打开串口设备, 之后用JNI调用了 FileDescriptor 的构造方法
/* Create a corresponding file descriptor */ { // SDK公开方法中找不到这个构造函数... jclass cFileDescriptor = env->FindClass("java/io/FileDescriptor"); jmethodID iFileDescriptor = env->GetMethodID(cFileDescriptor, " jfieldID descriptorID = env->GetFieldID(cFileDescriptor, "descriptor", "I"); mFileDescriptor = env->NewObject(cFileDescriptor, iFileDescriptor); env->SetIntField(mFileDescriptor, descriptorID, (jint)fd); } return mFileDescriptor; } |
Java 代码中可以用Stream来读写串口了
mFd = openSerialPort(device.getAbsolutePath(), baudrate, flags); if (mFd == null) { Log.e(TAG, "native open returns null"); throw new IOException(); } mFileInputStream = new FileInputStream(mFd); mFileOutputStream = new FileOutputStream(mFd); |
WiFi Agent主要依赖于LCM库实现数据的实时分发,虽然有LCM在Android上运行的例子,但是还未有用在Location上实际的DEMO。
解决方法:继续对WiFiAgent 实作DEMO,验证可行性
Android 6.0对App的权限管理作出了调整,需要在【开发者选项】中设置进行MockLocation的App,即使通用架构设计中的GPSAgent App。
作出以上设置后,还需要判断是否启用模拟位置,判断方法也与Android之前的版本不同,
// Android 6.0 以下:是否开启【允许模拟位置】 boolean bMockPosition = Settings.Secure.getInt(getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION, 0) != 0 |
Android 6.0 不再使用Settings.Secure.ALLOW_MOCK_LOCATION[4],导致很多以前的位置模拟工具在Android6.0上无法运行,今后的项目需要为此作出调整。
WiFi Agent 的重要性不如 Bluetooth GPS Agent 和 DGPS Agent, 现阶段也未有实质的应用场景,所以其实现放在了最后,但是这是唯一一个可以向多个用户广播的系统,从万物互联的未来远景来看,和现有的LCM已经可用的情况下,这个方案也是值得研究的。
现有的3种Agent中,Bluetooth GPS Agent 已经实用,使用的蓝牙GPS型号是
Holux RCV-3000 Bluetooth[5] .
图7 Holux RCV-3000 Bluetooth GPS
RCV-3000 的机型采用联发科技 (MTK) GPS 解决方案-MT3329 的低耗能设计
主要功能:
1) 内建 MTK MT3329 低耗能 GPS 芯片组。
2) 66个平行卫星搜寻频道以快速取得并重新取得讯号。
3) 优异敏感度高至 -165 dBm
4) 内建 WAAS/ EGNOS 解调器,无需另外装设硬件。
5) 与蓝牙串行埠规范 (SPP) 兼容。
6) 低耗电量。内建可更换的充电式锂电池,充电后可持续运作最长可达 28 小时。
7) 可选配 USB 端子连接线,可连接无蓝牙装置系统。
8) 支持 NMEA0183 V 3.01 数据协议。
通过这个蓝牙GPS和本项目的Bluetooth GPS Agent配合,已经成功地使Google Map,Baidu Map精确定位,尤其是在和手机自带的GPS定位缓慢时,蓝牙GPS定位快速尤其明显。
DGPS Agent 由于笔者未参与实车走行,没有获取第一手的测试结果。所以只能以看起始点的位置作为测试结果。
本文主要是总结近年来Android和GPS结合的应用方法,现在技术飞速发展,希望这些方法能够对以后的位置信息应用起到参考作用。
[1] 邓凡平. 深入理解Android:WiFi模块 NFC和GPS卷[M].
机械工业出版社, 2014:826-828.
[2] Google. Develop > API Guides > Connectivity-Bluetooth[EB/OL].
https://developer.android.com/guide/topics/connectivity/bluetooth.html, 2016.
[3] Google. android.location.Location [EB/OL].
https://developer.android.com/reference/android/location/Location.html, 2016.
[4] doris_d.Android 使用模拟位置(支持Android6.0)[EB/OL].
http://blog.csdn.net/doris_d/article/details/51384285 , 2016.
[5]Holux. Products > GPS Receiver/DataLogger > Wireless GPS Receiver / Data Logger > RCV-3000[EB/OL].http://www.holux.com/JCore/en/products/products_spec.jsp?pno=440, 2016.