如何在Flutter安卓应用中获取GPS/GLNSS等卫星数据?
Flutter安卓获取GNSS卫星数据(SNR、信号强度)及实现星座图方案
获取卫星原始数据(SNR、信号强度)
Flutter官方没有直接提供GNSS卫星底层数据的API,可通过以下两种方式实现:
1. 使用第三方GNSS插件
推荐使用flutter_gnss插件,它封装了安卓原生的GNSS状态监听能力,可直接获取卫星的SNR、信号强度、星座类型、方位角、仰角等数据。
步骤:
- 添加依赖到
pubspec.yaml:dependencies: flutter_gnss: ^latest_version - 安卓Manifest添加权限:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- Android 10+ 后台监听需添加 --> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> - 监听卫星数据:
import 'package:flutter_gnss/flutter_gnss.dart'; void initGnssListener() { FlutterGnss().onGnssStatusChanged.listen((status) { for (var sat in status.satellites) { // 提取所需数据 final svid = sat.svid; final constellationType = sat.constellationType; final snr = sat.snr; // 信噪比,单位dBHz final signalStrength = sat.signalStrength; final azimuth = sat.azimuth; final elevation = sat.elevation; // 收集数据用于绘制星座图 // ... } }); }
2. 自定义平台通道调用安卓原生API
如果第三方插件无法满足定制需求,可直接通过平台通道调用安卓的GnssStatus.Callback:
安卓原生端(Kotlin):
class MainActivity : FlutterActivity() { private val CHANNEL = "your.package.name/gnss" private var gnssCallback: GnssStatus.Callback? = null private var eventSink: EventChannel.EventSink? = null override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) EventChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setStreamHandler( object : EventChannel.StreamHandler { override fun onListen(arguments: Any?, sink: EventChannel.EventSink?) { eventSink = sink startGnssMonitoring() } override fun onCancel(arguments: Any?) { stopGnssMonitoring() eventSink = null } } ) } private fun startGnssMonitoring() { val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager gnssCallback = object : GnssStatus.Callback() { override fun onSatelliteStatusChanged(status: GnssStatus) { val satellites = mutableListOf<Map<String, Any>>() for (i in 0 until status.satelliteCount) { satellites.add( mapOf( "svid" to status.getSvid(i), "constellation" to status.getConstellationType(i), "snr" to status.getCn0DbHz(i), "azimuth" to status.getAzimuthDegrees(i), "elevation" to status.getElevationDegrees(i) ) ) } eventSink?.success(satellites) } } locationManager.registerGnssStatusCallback(gnssCallback) } private fun stopGnssMonitoring() { val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager gnssCallback?.let { locationManager.unregisterGnssStatusCallback(it) } } }
Flutter端监听:
import 'package:flutter/services.dart'; Stream<List<dynamic>> getSatelliteStream() { const channel = EventChannel('your.package.name/gnss'); return channel.receiveBroadcastStream().map((event) => event as List<dynamic>); } // 使用示例 void initStream() { getSatelliteStream().listen((satellites) { for (var sat in satellites) { // 解析卫星数据 final snr = sat['snr'] as double; // ... } }); }
实现GPS Test风格的星座图
星座图核心是将卫星的方位角和仰角映射到圆形画布上:
- 仰角90°对应画布中心(天顶),0°对应画布边缘(地平线)
- 方位角0°为正北,顺时针递增,需转换为Flutter画布的坐标系统(右为0,逆时针)
自定义绘制实现
使用CustomPainter绘制星座图:
import 'dart:math'; import 'package:flutter/material.dart'; class ConstellationPainter extends CustomPainter { final List<Satellite> satellites; final double radius; ConstellationPainter({required this.satellites, required this.radius}); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); // 绘制地平线和辅助圆 final outlinePaint = Paint() ..color = Colors.grey.shade400 ..style = PaintingStyle.stroke ..strokeWidth = 2; canvas.drawCircle(center, radius, outlinePaint); canvas.drawCircle(center, radius * sin(45 * pi / 180), outlinePaint..color = Colors.grey.shade200); // 绘制卫星点 for (var sat in satellites) { // 转换方位角为画布坐标角度 final azimuthRad = (90 - sat.azimuth) * pi / 180; // 计算卫星到中心的距离(仰角越大越靠近中心) final distance = radius * cos(sat.elevation * pi / 180); final satOffset = Offset( center.dx + distance * cos(azimuthRad), center.dy + distance * sin(azimuthRad), ); // 按星座设置颜色 final color = _getConstellationColor(sat.constellation); // 按SNR设置点大小和透明度 final pointSize = 4 + (sat.snr / 60) * 8; final alpha = (sat.snr / 60) * 255; final satPaint = Paint() ..color = color.withAlpha(alpha.toInt()) ..style = PaintingStyle.fill; canvas.drawCircle(satOffset, pointSize, satPaint); // 绘制卫星ID final textPainter = TextPainter( text: TextSpan(text: sat.svid.toString(), style: TextStyle(fontSize: 10, color: Colors.black87)), textDirection: TextDirection.ltr, ); textPainter.layout(); textPainter.paint(canvas, Offset(satOffset.dx - textPainter.width/2, satOffset.dy - textPainter.height/2)); } } Color _getConstellationColor(int constellationType) { // 对应安卓ConstellationType枚举值 switch (constellationType) { case 1: return Colors.blue; // GPS case 3: return Colors.red; // GLONASS case 4: return Colors.green; // Galileo case 5: return Colors.orange; // Beidou default: return Colors.grey; } } @override bool shouldRepaint(ConstellationPainter oldDelegate) { return oldDelegate.satellites != satellites; } } // 卫星数据模型 class Satellite { final int svid; final int constellation; final double snr; final double azimuth; final double elevation; Satellite({ required this.svid, required this.constellation, required this.snr, required this.azimuth, required this.elevation, }); }
集成到Widget
class ConstellationMap extends StatefulWidget { const ConstellationMap({super.key}); @override State<ConstellationMap> createState() => _ConstellationMapState(); } class _ConstellationMapState extends State<ConstellationMap> { List<Satellite> _satellites = []; @override void initState() { super.initState(); // 监听卫星数据并更新状态 getSatelliteStream().listen((satDataList) { setState(() { _satellites = satDataList.map((data) => Satellite( svid: data['svid'] as int, constellation: data['constellation'] as int, snr: data['snr'] as double, azimuth: data['azimuth'] as double, elevation: data['elevation'] as double, )).toList(); }); }); } @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; final radius = min(size.width, size.height) / 2 - 20; return CustomPaint( painter: ConstellationPainter(satellites: _satellites, radius: radius), size: Size(size.width, size.height), ); } }
关键注意事项
- 权限:必须在运行时申请
ACCESS_FINE_LOCATION权限,Android 10+后台监听需额外申请ACCESS_BACKGROUND_LOCATION。 - 数据更新:GNSS数据更新频率约1Hz,避免过于频繁的UI重绘。
- 星座映射:安卓
ConstellationType枚举值对应不同卫星系统,需根据实际值调整颜色映射。
内容的提问来源于stack exchange,提问作者user18107302
相关产品推荐
相关产品推荐

