16、传感器与定位:传感器框架、GPS定位与地图SDK集成
说实话,移动设备最迷人的地方,就是它能感知周围的世界。你拿着手机转个圈,屏幕跟着转;你打开地图,它告诉你现在在哪。这些背后,就是传感器和定位技术在干活。
这一章,我带你把这些东西捋清楚。从底层的传感器框架,到GPS和网络定位,再到集成Google Maps和高德、百度地图。嗯,内容不少,但都是实战硬货。
16.1 Android传感器框架
Android的传感器框架,说白了就是一套标准接口。硬件厂商把数据送上来,系统帮你封装好,你只管调用。
核心就三个类:SensorManager、Sensor、SensorEventListener。流程很简单:先拿到SensorManager,然后通过它获取某个传感器,再注册监听器,数据就源源不断来了。
核心流程:
- 获取 SensorManager:
getSystemService(SENSOR_SERVICE) - 获取指定传感器:
sensorManager.getDefaultSensor(type) - 注册监听器:
sensorManager.registerListener(listener, sensor, delay) - 在 onSensorChanged 中处理数据
- 记得在 onPause 中取消注册
我刚开始做传感器项目时,就犯过一个低级错误——忘了在Activity暂停时取消注册。结果屏幕关了,传感器还在跑,电量哗哗地掉。后来养成了习惯,register和unregister永远成对出现。
16.1.1 加速度传感器
加速度传感器测量的是设备在三个轴上的加速度,单位是m/s²。注意,它包含重力分量。所以手机平放桌上时,Z轴读数大约是9.8。
实际项目中,我常用它来做计步器。但有个坑:原始数据噪声很大。你走路时手一晃,数据就乱跳。我的做法是先做低通滤波,把高频噪声滤掉,再检测波峰波谷。
// 低通滤波示例
private static final float ALPHA = 0.8f;
private float[] gravity = new float[3];
public void onSensorChanged(SensorEvent event) {
// 提取重力分量
gravity[0] = ALPHA * gravity[0] + (1 - ALPHA) * event.values[0];
gravity[1] = ALPHA * gravity[1] + (1 - ALPHA) * event.values[1];
gravity[2] = ALPHA * gravity[2] + (1 - ALPHA) * event.values[2];
// 线性加速度 = 原始值 - 重力分量
float x = event.values[0] - gravity[0];
float y = event.values[1] - gravity[1];
float z = event.values[2] - gravity[2];
}
16.1.2 陀螺仪
陀螺仪测量的是角速度,单位是rad/s。它告诉你设备在转,但转了多少度,需要你自己积分。
这里有个大坑:积分会累积误差。你想想看,每次测量都有微小偏差,积分一次偏差变大一点,时间长了就漂到天边去了。我曾经做过一个VR头显的Demo,用纯陀螺仪算角度,三分钟后就偏了30度。
避坑指南:不要单独用陀螺仪做长时间的角度追踪。一定要结合加速度计和磁力计做融合。Android提供了Sensor.TYPE_ROTATION_VECTOR,它就是融合后的结果,直接用这个更靠谱。
16.1.3 磁力计
磁力计测量磁场强度,说白了就是电子罗盘。它能告诉你手机朝向哪个方向。
但磁力计特别娇气。你旁边放个音箱、笔记本电脑,甚至金属桌腿,读数就乱飘。我在一个商场项目里遇到过,用户一靠近电梯,方向就乱转。后来加了卡尔曼滤波,才勉强稳住。
我的经验:磁力计使用前最好做校准。让用户拿着手机画“8”字,系统会自动校准。很多地图App启动时都有这个提示。
16.2 传感器知识体系
下面这张图,是我自己整理的传感器知识结构。你看一眼,心里就有谱了。
16.3 GPS定位与网络定位
定位这块,Android提供了两种主要方式:GPS和网络定位。GPS精度高,但慢,室内基本废掉。网络定位快,但精度差,能到几十米就不错了。
实际项目中,我从来不用单一方式。都是先用网络定位快速拿到一个位置,然后等GPS慢慢收敛,再切换到GPS数据。这叫“冷启动优化”。
// 请求位置更新
LocationRequest request = new LocationRequest.Builder(
Priority.PRIORITY_HIGH_ACCURACY, // 高精度模式
5000L) // 更新间隔5秒
.setMinUpdateDistanceMeters(10f) // 最小移动10米才更新
.build();
// 检查权限
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
locationClient.requestLocationUpdates(request, locationCallback, Looper.getMainLooper());
}
注意:Android 10以后,后台定位权限需要单独申请。如果你的App需要在后台持续定位,记得在Manifest里加上ACCESS_BACKGROUND_LOCATION权限,并且要在运行时弹窗申请。这个权限审核很严,Google Play上架时会被重点审查。
16.4 Google Maps集成
Google Maps集成,核心就三步:获取API Key、添加MapFragment、操作地图。
API Key要去Google Cloud Console申请,绑定你的应用签名指纹。我记得第一次弄的时候,忘了配SHA-1指纹,地图加载出来一片灰,折腾了半天才发现是这个问题。
// 在布局中添加
<fragment
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
// 在代码中获取并操作
SupportMapFragment mapFragment = (SupportMapFragment)
getSupportFragmentManager().findFragmentById(R.id.map);
mapFragment.getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(GoogleMap googleMap) {
// 设置地图类型
googleMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
// 添加标记
LatLng sydney = new LatLng(-34, 151);
googleMap.addMarker(new MarkerOptions()
.position(sydney)
.title("Sydney"));
// 移动相机
googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney, 15));
}
});
16.5 高德/百度地图SDK接入
国内用Google Maps?说实话,体验不太好。因为众所周知的原因,加载慢,有时还连不上。所以国内项目,我基本都用高德或百度地图。
这两家的SDK接入流程差不多,都是:注册开发者、创建应用、获取Key、下载SDK、配置权限、初始化。
| 对比项 | 高德地图 | 百度地图 |
|---|---|---|
| 坐标系 | GCJ-02(国测局) | BD-09(百度自研) |
| 定位SDK | 单独集成 | 内置定位 |
| 离线地图 | 支持 | 支持 |
| 文档质量 | 较好 | 一般 |
| 社区活跃度 | 高 | 中 |
这里有个关键点:坐标系转换。GPS拿到的是WGS-84坐标,高德用的是GCJ-02,百度用的是BD-09。如果你把GPS坐标直接扔给百度地图,位置会偏几百米。我踩过这个坑,后来写了个工具类专门做转换。
我的建议:如果项目只做国内,优先选高德。文档清晰,API设计更符合Android开发习惯。如果要做海外,那就只能用Google Maps了。
// 高德地图初始化示例
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 必须在setContentView之前初始化
SDKInitializer.initialize(getApplicationContext());
setContentView(R.layout.activity_main);
// 获取地图控件
MapView mapView = findViewById(R.id.map);
mapView.onCreate(savedInstanceState);
AMap aMap = mapView.getMap();
aMap.setMapType(AMap.MAP_TYPE_NORMAL);
// 添加标记
LatLng point = new LatLng(39.908860, 116.397390);
aMap.addMarker(new MarkerOptions()
.position(point)
.title("天安门")
.snippet("北京"));
}
}
嗯,传感器和定位这块,内容确实不少。但说白了,核心就两件事:感知和定位。传感器让你知道设备的状态,定位让你知道设备的位置。把这两块吃透了,很多有意思的功能就能做出来了。
我个人的习惯是,先跑通一个最简单的Demo,再慢慢加功能。别一上来就想做完美,先让传感器出数据,让地图显示出来,后面的事都好说。
公众号:蓝海资料掘金营,微信deep3321