一概览
0.概念
GPS是英文Global Positioning System(全球定位系统)的简称。
GNSS全球导航卫星系统(Global Navigation Satellite System),它是泛指所有的卫星导航系统,包括全球的、区域的和增强的。
1.定位方式
(1)GPS_PROVIDER:通过 GPS 来获取地理位置的经纬度信息;
优点:获取地理位置信息精确度高;
缺点:只能在户外使用,获取经纬度信息耗时,耗电;
(2)NETWORK_PROVIDER:通过移动网络的基站或者 Wi-Fi 来获取地理位置;
优点:只要有网络,就可以快速定位,室内室外都可;
缺点:精确度不高;
(3)PASSIVE_PROVIDER:被动接收更新地理位置信息,而不用自己请求地理位置信息。 PASSIVE_PROVIDER 返回的位置是通过其他 providers 产生的,可以查询 getProvider() 方法决定位置更新的由来,需要 ACCESS_FINE_LOCATION 权限,但是如果未启用 GPS,则此 provider 可能只返回粗略位置匹配;
2.版本差异与权限
权限
(1)ACCESS_FINE_LOCATION是精确位置,如果使用GPS_PROVIDER或者同时使用GPS_PROVIDER和NETWORK_PROVIDER,需声明该权限,它对于这两个provider都是有效的;
(2)ACCESS_COARSE_LOCATION是粗略位置,该权限只针对NETWORK_PROVIDER。
版本差异
Android 6.0 以上动态申请权限
Android 7.0 以上 可以获取GPS原始数据。
二、代码相关
2.0 权限检查
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "startLocationClient checkSelfPermission return");
return;
}
GPS是否开启
boolean gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
if (!gpsEnabled) {
//检测gps 开启状态
Log.i(TAG, "gpsEnabled " + gpsEnabled);
Intent settingsIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
settingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(settingsIntent);
}
2.1 搜星判断
GPS 首次通信非常耗时,所以很多时候可能请求不到坐标。可以侧面从有效卫星数量来判断是室内外。
LocationManager locationManager = (LocationManager) getSystemService(Context.
LOCATION_SERVICE);
locationManager.addGpsStatusListener(statusListener);
private static int STAR_MAX_SNR = 50;//搜星最大有效snr 上限
private static int STAR_MIN_SNR = 30;//最低有效snr 下限
private static int MIN_STAR_NUM = 4;//最低有效卫星数量
private ArrayList<GpsSatellite> numSatelliteList = new ArrayList();//有效搜星数量
private final GpsStatus.Listener statusListener = new GpsStatus.Listener() {
public void onGpsStatusChanged(int event) {// GPS状态变化时的回调,获取当前状态
if (ActivityCompat.checkSelfPermission(LocationServiceGPS.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(LocationServiceGPS.this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
if (locationManager != null) {
GpsStatus status = locationManager.getGpsStatus(null);
// 获取卫星状态相关数据 判断 室内还是室外
GetGPSStatus(event, status);
}
}
};
/**
* @param event
* @param status
*/
private void GetGPSStatus(final int event, final GpsStatus status) {
ThreadPoolProxyFactory.getNormalThreadPoolProxy().execute(new Runnable() {
@Override
public void run() {
if (status == null) {
} else if (event == GpsStatus.GPS_EVENT_SATELLITE_STATUS) {
// 获取最大的卫星数(这个只是一个预设值)
int maxSatellites = status.getMaxSatellites();
Iterator<GpsSatellite> it = status.getSatellites().iterator();
numSatelliteList.clear();
int count = 0;
while (it.hasNext() && count <= maxSatellites) {
GpsSatellite s = it.next();
if (s.getSnr() >= STAR_MIN_SNR && s.getSnr() <= STAR_MAX_SNR) {//
//只有信躁比不为0的时候才算搜到了星 此处统计SNR在30-50间的卫星数, 重要: 需要清除星历 重测 冷启动
numSatelliteList.add(s);
// SCLog.i(TAG, "s.getSnr()" + " snr--- " + s.getSnr());
count++;
}
}
if (numSatelliteList.size() >= MIN_STAR_NUM) {
Log.i(TAG, "updateGpsStatus----numSatelliteList.size() >= 4 numSatelliteList.size()= " + numSatelliteList.size());
//todo 此处判断搜星结果 为有效信号
}
} else if (event == GpsStatus.GPS_EVENT_STARTED) {
Log.i(TAG, "updateGpsStatus----GPS_EVENT_STARTED=");
//定位启动
} else if (event == GpsStatus.GPS_EVENT_STOPPED) {
//定位结束
Log.i(TAG, "updateGpsStatus----GPS_EVENT_STOPPED=");
}
}
});
}
2.2 坐标获取
变量
private LocationManager locationManager;
private String provider = LocationManager.GPS_PROVIDER;//定位提供者
private long minTime = 1000;//最小间隔时间
方法
locationManager.requestLocationUpdates(provider, minTime * 2, 5, locationListener2);
requestLocationUpdates方法四个参数分别为,位置提供者类型,最小间隔时间(毫秒),最小间隔距离(米),位置变化回调
/**
* 这个 只用来监听状态 定位请求交给 主动 realyRequest
*/
private final LocationListener locationListener2 = new LocationListener() {
public void onLocationChanged(Location location) {
//当坐标改变时触发此函数,如果Provider传进相同的坐标,它就不会被触发
Log.i(TAG, "LocationListener onLocationChanged");
updateToNewLocation(location);
}
public void onProviderDisabled(String provider) {
//Provider被disable时触发此函数,比如GPS被关闭
Log.i(TAG, "LocationListener onProviderDisabled");
}
public void onProviderEnabled(String provider) {
// Provider被enable时触发此函数,比如GPS被打开
Log.i(TAG, "LocationListener onProviderEnabled");
}
public void onStatusChanged(String provider, int status, Bundle extras) {
// extras provider的一些设置参数(如高精度、低功耗等)
// Provider的转态在可用、暂时不可用和无服务三个状态直接切换时触发此函数
switch (status) {
// Provider的转态
case LocationProvider.AVAILABLE:
Log.i(TAG, "LocationListener onStatusChanged LocationProvider.AVAILABLE");
break;
case LocationProvider.OUT_OF_SERVICE:
Log.i(TAG, "LocationListener onStatusChanged .LocationProvider.OUT_OF_SERVICE");
Pair state = new Pair(RESULT_STATE_GPS_ERR, "GPS OUT_OF_SERVICE");
if (gpsCallBack != null) {
gpsCallBack.onReceiverStateInfo(state);
}
break;
case LocationProvider.TEMPORARILY_UNAVAILABLE:
Log.i(TAG, "LocationListener onStatusChanged .LocationProvider.TEMPORARILY_UNAVAILABLE");
Pair state2 = new Pair(RESULT_STATE_GPS_ERR, "GPS 暂不可用");
if (gpsCallBack != null) {
// gpsCallBack 是传递状态结果的接口
gpsCallBack.onReceiverStateInfo(state2);
}
break;
}
}
};
至于很多误导人的操作请求是
Location newLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
这个是上次获取的已知坐标,可能是很久之前的其实没有意义。
可以从收到的坐标中获取精度最高的,同时做超时控制判断每隔一定时间获取精度最高点上报,最后移除listeners
/**
* 移除监听
*/
private void removeListeners() {
if (locationManager != null) {
Log.i(TAG, "removeListeners----removeListeners");
// 关闭程序时将监听器移除
locationManager.removeUpdates(locationListener2);
locationManager.removeGpsStatusListener(statusListener);
locationManager = null;
}
}
三、一些笔记
1.实测requestLocationUpdates要配合getLastKnownLocation一起使用,效果会更好。
2.GPS的精度问题
location.hasAccuracy() 判断然后location.getAccuracy()获取。
官方API中提到
image.png
我理解是 以收到坐标信息为原点,以精度值为半径,真实位置在该圆内的概率是68%。
四、LocationManager
LocationManager 分析
除了上文的添加GpsStatusListener
mLocationManager.addGpsStatusListener(statusListener)
mLocationManager.addNmeaListener(nmeaListener);
mLocationManager.registerGnssMeasurementsCallback(gnssMeasurementEventListener);
mLocationManager.registerGnssStatusCallback(gnssStatucallback);
mLocationManager.registerGnssNavigationMessageCallback(gnssNavigationMessageCallback);
1. addNmeaListener
mLocationManager.addNmeaListener(nmeaListener);
private final GpsStatus.NmeaListener nmeaListener =new GpsStatus.NmeaListener() {
@Override
public void onNmeaReceived(long timestamp, String nmea) {
}
};
- NMEA 0183是用于与船用电子设备通信的标准,通过此方法获取GPS引擎接收NMEA数据。
2.registerGnssStatusCallback
GNSS 状态回调
mLocationManager.registerGnssStatusCallback(gnssStatucallback);
private GnssStatus.Callback gnssStatucallback =new GnssStatus.Callback() {
/**
* Called when GNSS system has started.
*/
public void onStarted() {}
/**
* Called when GNSS system has stopped.
*/
public void onStopped() {}
/**
* Called when the GNSS system has received its first fix since starting.
* @param ttffMillis the time from start to first fix in milliseconds.
*/
public void onFirstFix(int ttffMillis) {}
/**
* Called periodically to report GNSS satellite status.
* @param status the current status of all satellites.
*/
public void onSatelliteStatusChanged(GnssStatus status) {}
};
英语也很简单,很直白就不翻译了。
3.registerGnssNavigationMessageCallback
Used for receiving GNSS satellite Navigation Messages from the GNSS engine.
用于从GNSS引擎接收GNSS卫星消息。(一般用不到)
mLocationManager.registerGnssNavigationMessageCallback(gnssNavigationMessageCallback);
private final GnssNavigationMessage.Callback gnssNavigationMessageCallback=new GnssNavigationMessage.Callback() {
@Override
public void onGnssNavigationMessageReceived(GnssNavigationMessage event) {
//返回最新收集的GNSS消息。
super.onGnssNavigationMessageReceived(event);
}
@Override
public void onStatusChanged(int status) {
//返回GNSS导航系统的最新状态。
super.onStatusChanged(status);
}
};
4.registerGnssMeasurementsCallback
注册GPS测量回调。
mLocationManager.registerGnssMeasurementsCallback(gnssMeasurementEventListener);
private final GnssMeasurementsEvent.Callback gnssMeasurementEventListener =
new GnssMeasurementsEvent.Callback() {
@Override
public void onGnssMeasurementsReceived(GnssMeasurementsEvent eventArgs) {
super.onGnssMeasurementsReceived(eventArgs);
//这里我们获取到了回调的测量数据容器:GnssMeasurementsEvent eventArgs
onGnssMeasurementsReceived(eventArgs);
}
@Override
public void onStatusChanged(int status) {
super.onStatusChanged(status);
}
};
五 GNSS
Android 7.0 以后官方推荐使用GNSS 。
1.GnssMeasurementsEvent
gnss 的测量数据mLocationManager.registerGnssMeasurementsCallback(gnssMeasurementEventListener)回调接受的数据
成员变量
private final GnssClock mClock;//时钟
private final Collection<GnssMeasurement> mReadOnlyMeasurements;//测量数据
获取办法
private void onGnssMeasurementsReceived(GnssMeasurementsEvent event) {
StringBuilder builder=new StringBuilder("GNSS测量数据:\n\n");
//这里的toStringClock和toStringMeasurement将写在下一步里
builder.append(toStringClock(event.getClock()));//写入gnss时钟的数据
builder.append("\n");
for (GnssMeasurement measurement : event.getMeasurements()) {
builder.append(toStringMeasurement(measurement));//写入gnss测量数据
builder.append("\n");
}
}
private String toStringClock(GnssClock gnssClock){
//将GPS接收器时钟的值转换为字符串
final String format = " %-4s = %s\n";//定义数据显示格式,“%-4”表示左对齐、不足四位补足四位
StringBuilder builder=new StringBuilder("GNSS时钟:\n");
DecimalFormat numberFormat = new DecimalFormat("#0.000");//定义格式化数字
if (gnssClock.hasLeapSecond()) {
//如果闰秒存在则显示闰秒
builder.append(String.format(format, "闰秒(LeapSecond)", gnssClock.getLeapSecond()));
}
builder.append(String.format(format, "硬件时钟(TimeNanos)", gnssClock.getTimeNanos()));//获取以毫秒为单位的GNSS接收器内部硬件时钟值
if (gnssClock.hasTimeUncertaintyNanos()) {
//获取硬件时钟的误差估计(不确定度)
builder.append(String.format(format, "时钟误差估计(TimeUncertaintyNanos)", gnssClock.getTimeUncertaintyNanos()));
}
if (gnssClock.hasFullBiasNanos()) {
//如果存在接收机本地时钟总偏差,则显示
builder.append(String.format(format, "总时钟偏差(FullBiasNanos)", gnssClock.getFullBiasNanos()));
}
if (gnssClock.hasBiasNanos()) {
//亚纳秒偏差
builder.append(String.format(format, "亚偏差(BiasNanos)", gnssClock.getBiasNanos()));
}
if (gnssClock.hasBiasUncertaintyNanos()) {
//FullBiasNanos和BiasNanos的误差估计
builder.append(String.format(format, "时钟偏差估计(BiasUncertaintyNanos)", numberFormat.format(gnssClock.getBiasUncertaintyNanos())));
}
/**
* 注意:以上五个数据用于计算GPS时钟
* 具体计算方法为:local estimate of GPS time = TimeNanos - (FullBiasNanos + BiasNanos)
* 世界标准时:UtcTimeNanos = TimeNanos - (FullBiasNanos + BiasNanos) - LeapSecond * 1,000,000,000
*/
if (gnssClock.hasDriftNanosPerSecond()) {
//以每秒纳秒为单位获取时钟的漂移
builder.append(String.format(format, "时钟漂移(DriftNanosPerSecond)", numberFormat.format(gnssClock.getDriftNanosPerSecond())));
}
if (gnssClock.hasDriftUncertaintyNanosPerSecond()) {
//时钟偏差的估计
builder.append(String.format(format, "时钟漂移估计(DriftUncertaintyNanosPerSecond)", numberFormat.format(gnssClock.getDriftUncertaintyNanosPerSecond())));
}
//获取硬件时钟不连续的计数,即:每当gnssclock中断时,该值+1
builder.append(String.format(format, "中断计数(HardwareClockDiscontinuityCount)", gnssClock.getHardwareClockDiscontinuityCount()));
return builder.toString();
}
private String toStringMeasurement(GnssMeasurement measurement){
//将GNSS测量结果转换为字符串
//定义显示格式
final String format = " %-4s = %s\n";
StringBuilder builder = new StringBuilder("GNSS测量结果:\n");
DecimalFormat numberFormat = new DecimalFormat("#0.000");
DecimalFormat numberFormat1 = new DecimalFormat("#0.000E00");
//获取卫星ID
/**
* 取决于卫星类型
* GPS:1-32
* SBAS:120-151、183-192
* GLONASS:OSN或FCN + 100之一
* 1-24作为轨道槽号(OSN)(首选,如果知道)
* 93-106作为频道号(FCN)(-7至+6)加100。即将-7的FCN编码为93,0编码为100,+ 6编码为106
* QZSS:193-200
* 伽利略:1-36
* 北斗:1-37
*/
builder.append(String.format(format, "卫星ID", measurement.getSvid()));
//获取卫星类型
/**
* 1:CONSTELLATION_GPS 使用GPS定位
* 2:CONSTELLATION_SBAS 使用SBAS定位
* 3:CONSTELLATION_GLONASS 使用格洛纳斯定位
* 4:CONSTELLATION_QZSS 使用QZSS定位
* 5:CONSTELLATION_BEIDOU 使用北斗定位 (^-^)!
* 6:CONSTELLATION_GALILEO 使用伽利略定位
* 7:CONSTELLATION_IRNSS 使用印度区域卫星定位
*/
builder.append(String.format(format, "卫星类型", measurement.getConstellationType()));
//获取进行测量的时间偏移量(以纳秒为单位)
builder.append(String.format(format, "测量时间偏移量", measurement.getTimeOffsetNanos()));
//获取每个卫星的同步状态
//具体数值含义请查表
builder.append(String.format(format, "同步状态", measurement.getState()));
//获取时间戳的伪距速率,以m/s为单位
builder.append(String.format(format, "伪距速率", numberFormat.format(measurement.getPseudorangeRateMetersPerSecond())));
//获取伪距的速率不确定性(1-Sigma),以m/s为单位
builder.append(String.format(format, "伪距速率不确定度", numberFormat.format(measurement.getPseudorangeRateUncertaintyMetersPerSecond())));
//
if (measurement.getAccumulatedDeltaRangeState() != 0) {
// 获取“累积增量范围”状态
// 返回:MULTIPATH_INDICATOR_UNKNOWN(指示器不可用)=0
// notice 即:指示器可用时,收集数据
builder.append(
String.format(format, "累积增量范围状态", measurement.getAccumulatedDeltaRangeState()));
//获取自上次重置通道以来的累积增量范围,以米为单位.
//该值仅在上面的state值为“可用”时有效
//notice 累积增量范围= -k * 载波相位(其中k为常数)
builder.append(String.format(format, "累积增量范围", numberFormat.format(measurement.getAccumulatedDeltaRangeMeters())));
//获取以米为单位的累积增量范围的不确定性(1-Sigma)
builder.append(String.format(format, "累积增量范围不确定度", numberFormat1.format(measurement.getAccumulatedDeltaRangeUncertaintyMeters())));
}
if (measurement.hasCarrierFrequencyHz()) {
//获取被跟踪信号的载波频率
builder.append(String.format(format, "信号载波频率", measurement.getCarrierFrequencyHz()));
}
if (measurement.hasCarrierCycles()) {
//卫星和接收器之间的完整载波周期数
builder.append(String.format(format, "载波周期数", measurement.getCarrierCycles()));
}
if (measurement.hasCarrierPhase()) {
//获取接收器检测到的RF相位
builder.append(String.format(format, "RF相位", measurement.getCarrierPhase()));
}
if (measurement.hasCarrierPhaseUncertainty()) {
//误差估计
builder.append(String.format(format, "RF相位不确定度", measurement.getCarrierPhaseUncertainty()));
}
//获取一个值,该值指示事件的“多路径”状态,返回0或1或2
//MULTIPATH_INDICATOR_DETECTED = 1 测量显示有“多路径效应”迹象
// MULTIPATH_INDICATOR_NOT_DETECTED = 2 测量结果显示没有“多路径效应”迹象
builder.append(String.format(format, "多路经效应指示器", measurement.getMultipathIndicator()));
//
if (measurement.hasSnrInDb()) {
//获取信噪比(SNR),以dB为单位
builder.append(String.format(format, "信噪比", measurement.getSnrInDb()));
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
if (measurement.hasAutomaticGainControlLevelDb()) {
//获取以dB为单位的自动增益控制级别
builder.append(String.format(format, "自动增益控制级别", measurement.getAutomaticGainControlLevelDb()));
}
if (measurement.hasCarrierFrequencyHz()) {
builder.append(String.format(format, "载波频率", measurement.getCarrierFrequencyHz()));
}
}
return builder.toString();
}
2.GnssNavigationMessage
GNSS satellite Navigation Message.GNSS卫星导航消息。
其中event.getData()二进制数据可以按照ICD协议文件(GPS接口控制文件)逐子帧解码出卫星运行轨道参数、卫星 钟改正参数、电离层延迟改正参数等数据用于导航与定位。
绝大多数应用层开发无实际意义。
3.GnssStatus
mLocationManager.registerGnssStatusCallback(gnssStatucallback)回调中接收到的数据。
status.getSatelliteCount()来获取卫星数。
网友评论