美文网首页
Android 定位(GPS为主) 笔记

Android 定位(GPS为主) 笔记

作者: silencefun | 来源:发表于2020-09-01 22:14 被阅读0次

    一概览

    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;//测量数据
    

    参见Android 获取GNSS原始数据

    获取办法

       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()来获取卫星数。

    相关文章

      网友评论

          本文标题:Android 定位(GPS为主) 笔记

          本文链接:https://www.haomeiwen.com/subject/cmpcjktx.html