一概覽
0.概念
GPS是英文Global Positioning System(全球定位系統(tǒng))的簡稱。
GNSS全球?qū)Ш叫l(wèi)星系統(tǒng)(Global Navigation Satellite System)邑滨,它是泛指所有的衛(wèi)星導(dǎo)航系統(tǒng)身堡,包括全球的绵脯、區(qū)域的和增強(qiáng)的。
1.定位方式
(1)GPS_PROVIDER:通過 GPS 來獲取地理位置的經(jīng)緯度信息;
優(yōu)點(diǎn):獲取地理位置信息精確度高嫌套;
缺點(diǎn):只能在戶外使用,獲取經(jīng)緯度信息耗時圾旨,耗電踱讨;
(2)NETWORK_PROVIDER:通過移動網(wǎng)絡(luò)的基站或者 Wi-Fi 來獲取地理位置;
優(yōu)點(diǎn):只要有網(wǎng)絡(luò)碳胳,就可以快速定位勇蝙,室內(nèi)室外都可;
缺點(diǎn):精確度不高挨约;
(3)PASSIVE_PROVIDER:被動接收更新地理位置信息味混,而不用自己請求地理位置信息。 PASSIVE_PROVIDER 返回的位置是通過其他 providers 產(chǎn)生的诫惭,可以查詢 getProvider() 方法決定位置更新的由來翁锡,需要 ACCESS_FINE_LOCATION 權(quán)限,但是如果未啟用 GPS夕土,則此 provider 可能只返回粗略位置匹配馆衔;
2.版本差異與權(quán)限
權(quán)限
(1)ACCESS_FINE_LOCATION是精確位置瘟判,如果使用GPS_PROVIDER或者同時使用GPS_PROVIDER和NETWORK_PROVIDER,需聲明該權(quán)限角溃,它對于這兩個provider都是有效的拷获;
(2)ACCESS_COARSE_LOCATION是粗略位置,該權(quán)限只針對NETWORK_PROVIDER减细。
版本差異
Android 6.0 以上動態(tài)申請權(quán)限
Android 7.0 以上 可以獲取GPS原始數(shù)據(jù)匆瓜。
二、代碼相關(guān)
2.0 權(quán)限檢查
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 開啟狀態(tài)
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 首次通信非常耗時未蝌,所以很多時候可能請求不到坐標(biāo)驮吱。可以側(cè)面從有效衛(wèi)星數(shù)量來判斷是室內(nèi)外萧吠。
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;//最低有效衛(wèi)星數(shù)量
private ArrayList<GpsSatellite> numSatelliteList = new ArrayList();//有效搜星數(shù)量
private final GpsStatus.Listener statusListener = new GpsStatus.Listener() {
public void onGpsStatusChanged(int event) {// GPS狀態(tài)變化時的回調(diào)左冬,獲取當(dāng)前狀態(tài)
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);
// 獲取衛(wèi)星狀態(tài)相關(guān)數(shù)據(jù) 判斷 室內(nèi)還是室外
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) {
// 獲取最大的衛(wèi)星數(shù)(這個只是一個預(yù)設(shè)值)
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的時候才算搜到了星 此處統(tǒng)計SNR在30-50間的衛(wèi)星數(shù), 重要: 需要清除星歷 重測 冷啟動
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 此處判斷搜星結(jié)果 為有效信號
}
} else if (event == GpsStatus.GPS_EVENT_STARTED) {
Log.i(TAG, "updateGpsStatus----GPS_EVENT_STARTED=");
//定位啟動
} else if (event == GpsStatus.GPS_EVENT_STOPPED) {
//定位結(jié)束
Log.i(TAG, "updateGpsStatus----GPS_EVENT_STOPPED=");
}
}
});
}
2.2 坐標(biāo)獲取
變量
private LocationManager locationManager;
private String provider = LocationManager.GPS_PROVIDER;//定位提供者
private long minTime = 1000;//最小間隔時間
方法
locationManager.requestLocationUpdates(provider, minTime * 2, 5, locationListener2);
requestLocationUpdates方法四個參數(shù)分別為纸型,位置提供者類型拇砰,最小間隔時間(毫秒),最小間隔距離(米)绊袋,位置變化回調(diào)
/**
* 這個 只用來監(jiān)聽狀態(tài) 定位請求交給 主動 realyRequest
*/
private final LocationListener locationListener2 = new LocationListener() {
public void onLocationChanged(Location location) {
//當(dāng)坐標(biāo)改變時觸發(fā)此函數(shù)毕匀,如果Provider傳進(jìn)相同的坐標(biāo),它就不會被觸發(fā)
Log.i(TAG, "LocationListener onLocationChanged");
updateToNewLocation(location);
}
public void onProviderDisabled(String provider) {
//Provider被disable時觸發(fā)此函數(shù)癌别,比如GPS被關(guān)閉
Log.i(TAG, "LocationListener onProviderDisabled");
}
public void onProviderEnabled(String provider) {
// Provider被enable時觸發(fā)此函數(shù)皂岔,比如GPS被打開
Log.i(TAG, "LocationListener onProviderEnabled");
}
public void onStatusChanged(String provider, int status, Bundle extras) {
// extras provider的一些設(shè)置參數(shù)(如高精度、低功耗等)
// Provider的轉(zhuǎn)態(tài)在可用展姐、暫時不可用和無服務(wù)三個狀態(tài)直接切換時觸發(fā)此函數(shù)
switch (status) {
// Provider的轉(zhuǎn)態(tài)
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 是傳遞狀態(tài)結(jié)果的接口
gpsCallBack.onReceiverStateInfo(state2);
}
break;
}
}
};
至于很多誤導(dǎo)人的操作請求是
Location newLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
這個是上次獲取的已知坐標(biāo)躁垛,可能是很久之前的其實(shí)沒有意義。
可以從收到的坐標(biāo)中獲取精度最高的圾笨,同時做超時控制判斷每隔一定時間獲取精度最高點(diǎn)上報教馆,最后移除listeners
/**
* 移除監(jiān)聽
*/
private void removeListeners() {
if (locationManager != null) {
Log.i(TAG, "removeListeners----removeListeners");
// 關(guān)閉程序時將監(jiān)聽器移除
locationManager.removeUpdates(locationListener2);
locationManager.removeGpsStatusListener(statusListener);
locationManager = null;
}
}
三、一些筆記
1.實(shí)測requestLocationUpdates要配合getLastKnownLocation一起使用擂达,效果會更好土铺。
2.GPS的精度問題
location.hasAccuracy() 判斷然后location.getAccuracy()獲取。
官方API中提到
我理解是 以收到坐標(biāo)信息為原點(diǎn)板鬓,以精度值為半徑悲敷,真實(shí)位置在該圓內(nèi)的概率是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是用于與船用電子設(shè)備通信的標(biāo)準(zhǔn),通過此方法獲取GPS引擎接收NMEA數(shù)據(jù)后德。
2.registerGnssStatusCallback
GNSS 狀態(tài)回調(diào)
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衛(wèi)星消息瓢湃。(一般用不到)
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導(dǎo)航系統(tǒng)的最新狀態(tài)。
super.onStatusChanged(status);
}
};
4.registerGnssMeasurementsCallback
注冊GPS測量回調(diào)绵患。
mLocationManager.registerGnssMeasurementsCallback(gnssMeasurementEventListener);
private final GnssMeasurementsEvent.Callback gnssMeasurementEventListener =
new GnssMeasurementsEvent.Callback() {
@Override
public void onGnssMeasurementsReceived(GnssMeasurementsEvent eventArgs) {
super.onGnssMeasurementsReceived(eventArgs);
//這里我們獲取到了回調(diào)的測量數(shù)據(jù)容器:GnssMeasurementsEvent eventArgs
onGnssMeasurementsReceived(eventArgs);
}
@Override
public void onStatusChanged(int status) {
super.onStatusChanged(status);
}
};
五 GNSS
Android 7.0 以后官方推薦使用GNSS 雾叭。
1.GnssMeasurementsEvent
gnss 的測量數(shù)據(jù)mLocationManager.registerGnssMeasurementsCallback(gnssMeasurementEventListener)回調(diào)接受的數(shù)據(jù)
成員變量
private final GnssClock mClock;//時鐘
private final Collection<GnssMeasurement> mReadOnlyMeasurements;//測量數(shù)據(jù)
獲取辦法
private void onGnssMeasurementsReceived(GnssMeasurementsEvent event) {
StringBuilder builder=new StringBuilder("GNSS測量數(shù)據(jù):\n\n");
//這里的toStringClock和toStringMeasurement將寫在下一步里
builder.append(toStringClock(event.getClock()));//寫入gnss時鐘的數(shù)據(jù)
builder.append("\n");
for (GnssMeasurement measurement : event.getMeasurements()) {
builder.append(toStringMeasurement(measurement));//寫入gnss測量數(shù)據(jù)
builder.append("\n");
}
}
private String toStringClock(GnssClock gnssClock){
//將GPS接收器時鐘的值轉(zhuǎn)換為字符串
final String format = " %-4s = %s\n";//定義數(shù)據(jù)顯示格式,“%-4”表示左對齊藏雏、不足四位補(bǔ)足四位
StringBuilder builder=new StringBuilder("GNSS時鐘:\n");
DecimalFormat numberFormat = new DecimalFormat("#0.000");//定義格式化數(shù)字
if (gnssClock.hasLeapSecond()) {
//如果閏秒存在則顯示閏秒
builder.append(String.format(format, "閏秒(LeapSecond)", gnssClock.getLeapSecond()));
}
builder.append(String.format(format, "硬件時鐘(TimeNanos)", gnssClock.getTimeNanos()));//獲取以毫秒為單位的GNSS接收器內(nèi)部硬件時鐘值
if (gnssClock.hasTimeUncertaintyNanos()) {
//獲取硬件時鐘的誤差估計(不確定度)
builder.append(String.format(format, "時鐘誤差估計(TimeUncertaintyNanos)", gnssClock.getTimeUncertaintyNanos()));
}
if (gnssClock.hasFullBiasNanos()) {
//如果存在接收機(jī)本地時鐘總偏差拷况,則顯示
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())));
}
/**
* 注意:以上五個數(shù)據(jù)用于計算GPS時鐘
* 具體計算方法為:local estimate of GPS time = TimeNanos - (FullBiasNanos + BiasNanos)
* 世界標(biāo)準(zhǔn)時: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())));
}
//獲取硬件時鐘不連續(xù)的計數(shù),即:每當(dāng)gnssclock中斷時,該值+1
builder.append(String.format(format, "中斷計數(shù)(HardwareClockDiscontinuityCount)", gnssClock.getHardwareClockDiscontinuityCount()));
return builder.toString();
}
private String toStringMeasurement(GnssMeasurement measurement){
//將GNSS測量結(jié)果轉(zhuǎn)換為字符串
//定義顯示格式
final String format = " %-4s = %s\n";
StringBuilder builder = new StringBuilder("GNSS測量結(jié)果:\n");
DecimalFormat numberFormat = new DecimalFormat("#0.000");
DecimalFormat numberFormat1 = new DecimalFormat("#0.000E00");
//獲取衛(wèi)星ID
/**
* 取決于衛(wèi)星類型
* 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, "衛(wèi)星ID", measurement.getSvid()));
//獲取衛(wèi)星類型
/**
* 1:CONSTELLATION_GPS 使用GPS定位
* 2:CONSTELLATION_SBAS 使用SBAS定位
* 3:CONSTELLATION_GLONASS 使用格洛納斯定位
* 4:CONSTELLATION_QZSS 使用QZSS定位
* 5:CONSTELLATION_BEIDOU 使用北斗定位 (^-^)!
* 6:CONSTELLATION_GALILEO 使用伽利略定位
* 7:CONSTELLATION_IRNSS 使用印度區(qū)域衛(wèi)星定位
*/
builder.append(String.format(format, "衛(wèi)星類型", measurement.getConstellationType()));
//獲取進(jìn)行測量的時間偏移量(以納秒為單位)
builder.append(String.format(format, "測量時間偏移量", measurement.getTimeOffsetNanos()));
//獲取每個衛(wèi)星的同步狀態(tài)
//具體數(shù)值含義請查表
builder.append(String.format(format, "同步狀態(tài)", 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) {
// 獲取“累積增量范圍”狀態(tài)
// 返回:MULTIPATH_INDICATOR_UNKNOWN(指示器不可用)=0
// notice 即:指示器可用時,收集數(shù)據(jù)
builder.append(
String.format(format, "累積增量范圍狀態(tài)", measurement.getAccumulatedDeltaRangeState()));
//獲取自上次重置通道以來的累積增量范圍悲酷,以米為單位.
//該值僅在上面的state值為“可用”時有效
//notice 累積增量范圍= -k * 載波相位(其中k為常數(shù))
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()) {
//衛(wèi)星和接收器之間的完整載波周期數(shù)
builder.append(String.format(format, "載波周期數(shù)", 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()));
}
//獲取一個值套菜,該值指示事件的“多路徑”狀態(tài),返回0或1或2
//MULTIPATH_INDICATOR_DETECTED = 1 測量顯示有“多路徑效應(yīng)”跡象
// MULTIPATH_INDICATOR_NOT_DETECTED = 2 測量結(jié)果顯示沒有“多路徑效應(yīng)”跡象
builder.append(String.format(format, "多路經(jīng)效應(yīng)指示器", 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衛(wèi)星導(dǎo)航消息设易。
其中event.getData()二進(jìn)制數(shù)據(jù)可以按照ICD協(xié)議文件(GPS接口控制文件)逐子幀解碼出衛(wèi)星運(yùn)行軌道參數(shù)逗柴、衛(wèi)星 鐘改正參數(shù)、電離層延遲改正參數(shù)等數(shù)據(jù)用于導(dǎo)航與定位顿肺。
絕大多數(shù)應(yīng)用層開發(fā)無實(shí)際意義戏溺。
3.GnssStatus
mLocationManager.registerGnssStatusCallback(gnssStatucallback)回調(diào)中接收到的數(shù)據(jù)。
status.getSatelliteCount()來獲取衛(wèi)星數(shù)屠尊。