Android 通过GnssStatus实现GPS信号强度显示

3,407 阅读3分钟

之前开发过一个App,需要用到GPS定位,其中有个功能是显示GPS信号强度,然而Android并没有直接提供GPS信号强度的API。

当时查询了一下,有介绍GPS的文章。根据文章介绍,GPS信号的强度根据设备搜索到的卫星数量以及卫星的信噪比(SNR)或载波噪声密度(C/N₀)来决定。当卫星的信噪比为-29 dB至-21 dB,载波噪声密度为37至45 dB-Hz时,可以用于定位,当可用卫星数量大于等于4时可以实现GPS定位功能。

GnssStatusCompat获取卫星信息

之前的项目中,我是通过GpsStatusGnssStatus分别实现的,因为GnssStatusAPI需要在Android N以上才可用。但是现在有GnssStatusCompatAPI,适用于所有Android版本,下面介绍一下。

使用定位相关的API需要申请定位权限,在AndroidManifest中添加权限,如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

    <application>
        ...
    </application>
</manifest>

定位权限需要主动申请获得用户的同意,代码如下:

private val requestMultiplePermissionsLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions: Map<String, Boolean> ->
    var locationPermissionAllGranted = true
    permissions.entries.forEach {
        val permissionName = it.key
        if (!it.value) {
            //未同意授权
            locationPermissionAllGranted = false
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                if (!shouldShowRequestPermissionRationale(permissionName)) {
                    //用户拒绝权限并且系统不再弹出请求权限的弹窗
                    //这时需要我们自己处理,比如自定义弹窗告知用户为何必须要申请这个权限
                    try {
                        val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                        val uri: Uri = Uri.fromParts("package", packageName, null)
                        intent.data = uri
                        startActivity(intent)
                    } catch (e: Exception) {
                        e.printStackTrace()
                    }
                }
            }
        }
    }
    if (locationPermissionAllGranted) {
        // 定位权限都通过,可以继续相关的操作
    }
}

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
    requestMultiplePermissionsLauncher.launch(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION))
}

通过GnssStatusCompat.Callback获取卫星信息,代码如下:

val gnssStatusListener = object : GnssStatusCompat.Callback() {
    override fun onSatelliteStatusChanged(status: GnssStatusCompat) {
        super.onSatelliteStatusChanged(status)
        // 可以搜索到的卫星总数
        val satelliteCount = status.satelliteCount
        for (index in 0 until satelliteCount) {
            // 每个卫星的载波噪声密度
            val cn0DbHz = status.getCn0DbHz(index)
        }
    }
}
val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
//注册监听
LocationManagerCompat.registerGnssStatusCallback(locationManager, gnssStatusListener, Handler(Looper.myLooper()

示例

整合之后写了个示例demo,代码如下:

class GpsSignalActivity : AppCompatActivity() {

    private lateinit var binding: LayoutGpsSignalActivityBinding
    private lateinit var locationManager: LocationManager

    private val requestMultiplePermissionsLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions: Map<String, Boolean> ->
        var locationPermissionAllGranted = true
        permissions.entries.forEach {
            val permissionName = it.key
            if (!it.value) {
                locationPermissionAllGranted = false
                //未同意授权
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    if (!shouldShowRequestPermissionRationale(permissionName)) {
                        //用户拒绝权限并且系统不再弹出请求权限的弹窗
                        //这时需要我们自己处理,比如自定义弹窗告知用户为何必须要申请这个权限
                        try {
                            val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                            val uri: Uri = Uri.fromParts("package", packageName, null)
                            intent.data = uri
                            startActivity(intent)
                        } catch (e: Exception) {
                            e.printStackTrace()
                        }
                    }
                }
            }
        }
        if (locationPermissionAllGranted) {
            registerGnssStatusListener()
        }
    }

    private var gnssStatusListenerAdded: Boolean = false
    @SuppressLint("SetTextI18n")
    private val locationListener = LocationListener {
        binding.tvLocation.run { post { text = "time :${SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance().time)}\nlongitude :${it.longitude},latitude :${it.latitude}" } }
    }
    private val gnssStatusListener = object : GnssStatusCompat.Callback() {
        @SuppressLint("SetTextI18n")
        override fun onSatelliteStatusChanged(status: GnssStatusCompat) {
            super.onSatelliteStatusChanged(status)
            val satelliteCount = status.satelliteCount
            var cn0DbHz30SatelliteCount = 0
            var cn0DbHz37SatelliteCount = 0
            var satelliteInfo = ""
            for (index in 0 until satelliteCount) {
                val cn0DbHz = status.getCn0DbHz(index)
                satelliteInfo += "svid:${status.getSvid(index)},cn0DbHz:$cn0DbHz\n"
                if (cn0DbHz >= 30) {
                    cn0DbHz30SatelliteCount++
                }
                if (cn0DbHz >= 37) {
                    cn0DbHz37SatelliteCount++
                }
            }
            binding.tvTotalSatelliteCount.run { post { text = "total satellite count :$satelliteCount" } }
            binding.tvAvailableSatelliteCount.run { post { text = "cno >37 count :$cn0DbHz37SatelliteCount\ncno >30 count :$cn0DbHz30SatelliteCount" } }
            binding.tvSatelliteInfo.run { post { text = "satellite info :\n$satelliteInfo" } }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.layout_gps_signal_activity)
        locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager

        binding.btnDetect.setOnClickListener {
            if (checkLocationPermission()) {
                if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
                    val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
                    startActivity(intent)
                } else {
                    locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 2000, 5.toFloat(), locationListener)
                }
            }
        }
        binding.btnStopDetect.setOnClickListener {
            locationManager.removeUpdates(locationListener)
        }
    }

    private fun checkLocationPermission(): Boolean {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
            ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
        ) {
            registerGnssStatusListener()
            return true
        } else {
            requestMultiplePermissionsLauncher.launch(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION))
            return false
        }
    }

    @SuppressLint("MissingPermission")
    private fun registerGnssStatusListener() {
        if (!gnssStatusListenerAdded) {
            gnssStatusListenerAdded = LocationManagerCompat.registerGnssStatusCallback(locationManager, gnssStatusListener, Handler(Looper.myLooper()
                ?: Looper.getMainLooper()))
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        if (gnssStatusListenerAdded) {
            LocationManagerCompat.unregisterGnssStatusCallback(locationManager, gnssStatusListener)
        }
    }
}

效果如图:

device-2022-09-17-193539.gif

根据效果图可以看到,其实载波噪声密度大于30,并且卫星数量大于等于3就可以获取到定位信息。那么可以简单的定义载波噪声密度大于30的卫星数量小于等于3时为信号弱,大于3小于7为信号中等,大于等于7为信号强。