之前开发过一个App,需要用到GPS定位,其中有个功能是显示GPS信号强度,然而Android并没有直接提供GPS信号强度的API。
当时查询了一下,有介绍GPS的文章。根据文章介绍,GPS信号的强度根据设备搜索到的卫星数量以及卫星的信噪比(SNR)或载波噪声密度(C/N₀)来决定。当卫星的信噪比为-29 dB至-21 dB,载波噪声密度为37至45 dB-Hz时,可以用于定位,当可用卫星数量大于等于4时可以实现GPS定位功能。
GnssStatusCompat获取卫星信息
之前的项目中,我是通过GpsStatus
和GnssStatus
分别实现的,因为GnssStatus
API需要在Android N以上才可用。但是现在有GnssStatusCompat
API,适用于所有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)
}
}
}
效果如图:
根据效果图可以看到,其实载波噪声密度大于30,并且卫星数量大于等于3就可以获取到定位信息。那么可以简单的定义载波噪声密度大于30的卫星数量小于等于3时为信号弱,大于3小于7为信号中等,大于等于7为信号强。