第20章 传感器与定位:让App感知世界
说实话,传感器和定位这块,是我当年从入门到进阶时觉得最「酷」的部分。你想啊,手机能知道你朝哪个方向走、知道你海拔多少、甚至能在地图上画出你的运动轨迹——这背后就是传感器和定位技术在干活。
这一章,我会带你走一遍Android传感器框架的核心用法,再聊聊GPS定位和地图SDK的集成。嗯,都是实战中高频用到的技能。
20.1 Android传感器框架
Android的传感器系统,说白了就是一套「硬件抽象层+事件回调」的架构。你不需要关心底层芯片怎么采样,只需要注册监听器,就能拿到数据。
核心类: SensorManager、Sensor、SensorEventListener
我习惯把传感器分为三类:
- 运动传感器:加速度计、陀螺仪、重力传感器
- 环境传感器:温度、湿度、气压、光照
- 位置传感器:磁力计、方向传感器(已废弃,推荐用旋转矢量)
获取传感器实例的代码很简单:
SensorManager sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
注册监听时,我建议你注意采样频率。别一上来就用SENSOR_DELAY_FASTEST,那会疯狂耗电。我在项目里踩过这个坑——一个计步器App,用最快频率跑了半小时,手机直接烫手。后来改成SENSOR_DELAY_NORMAL,效果完全够用。
我的习惯:游戏用SENSOR_DELAY_GAME,普通UI更新用SENSOR_DELAY_UI,省电场景用SENSOR_DELAY_NORMAL。
20.2 加速度传感器与陀螺仪
加速度传感器测量的是三个轴上的「力」,包括重力。陀螺仪测量的是角速度。两者经常配合使用,比如做姿态检测。
先看加速度计的数据格式:
@Override
public void onSensorChanged(SensorEvent event) {
float x = event.values[0]; // 左右倾斜
float y = event.values[1]; // 前后倾斜
float z = event.values[2]; // 垂直方向
// 单位:m/s²
}
陀螺仪的数据类似,但单位是弧度/秒:
float rotX = event.values[0]; // 绕X轴旋转速度
float rotY = event.values[1];
float rotZ = event.values[2];
我曾经做过一个「手机水平仪」的小工具。一开始直接用加速度计的原始数据,发现手机平放时Z轴读数不是9.8,而是9.7左右。为什么?因为传感器有零偏误差。解决办法是做个校准:让用户把手机平放5秒,取平均值作为偏移量。
注意:陀螺仪有漂移问题。长时间积分会导致角度误差累积。我建议用「互补滤波」或「卡尔曼滤波」把加速度计和陀螺仪的数据融合起来。
20.3 GPS定位(LocationManager)
Android的定位服务,核心是LocationManager。它支持三种Provider:
| Provider | 精度 | 耗电 | 适用场景 |
|---|---|---|---|
| GPS_PROVIDER | 高(3-10米) | 高 | 户外导航 |
| NETWORK_PROVIDER | 中(10-100米) | 低 | 城市粗略定位 |
| PASSIVE_PROVIDER | 取决于其他应用 | 极低 | 被动监听 |
获取位置的基本流程:
LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
// 检查权限(Android 6.0+ 需要运行时权限)
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
Location location = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, locationListener);
}
这里有个坑:getLastKnownLocation可能返回null。我遇到过用户第一次打开App,直接崩溃——因为没做空判断。所以一定要加判空:
if (location != null) {
double lat = location.getLatitude();
double lng = location.getLongitude();
} else {
// 等待第一次定位回调
}
我的建议:优先用NETWORK_PROVIDER获取一个粗略位置,等GPS锁定后再更新。用户体验会好很多——不用干等十几秒。
20.4 百度/高德地图SDK集成
地图SDK的集成,说白了就是三步:申请Key、配置依赖、写代码。但每一步都有细节。
以高德地图为例,申请Key时需要填写SHA1和包名。我当年第一次集成时,SHA1填错了,地图一直显示空白。折腾了两小时才发现是Key不匹配。
Gradle依赖配置:
implementation 'com.amap.api:3dmap:latest.version'
implementation 'com.amap.api:location:latest.version'
在布局中添加地图:
<com.amap.api.maps.MapView
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Activity中初始化:
public class MapActivity extends AppCompatActivity {
private MapView mapView;
private AMap aMap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_map);
mapView = findViewById(R.id.map);
mapView.onCreate(savedInstanceState);
aMap = mapView.getMap();
}
}
百度地图的流程类似,只是类名从AMap换成了BaiduMap。我个人觉得高德的文档更清晰,但百度在室内定位上更强。选哪个?看你项目需求。
注意:地图SDK的so文件很大。如果App包体积敏感,可以考虑只保留arm64-v8a架构,或者用动态下载。
20.5 地理编码与反编码
地理编码:把地址文字转成经纬度。反编码:把经纬度转成地址文字。这两个功能,地图SDK都提供了。
高德的反编码示例:
GeocodeSearch geocodeSearch = new GeocodeSearch(this);
geocodeSearch.setOnGeocodeSearchListener(new GeocodeSearch.OnGeocodeSearchListener() {
@Override
public void onRegeocodeSearched(RegeocodeResult result, int rCode) {
if (rCode == 1000) {
String address = result.getRegeocodeAddress().getFormatAddress();
// 显示地址
}
}
// 忽略编码回调
});
// 发起反编码请求
RegeocodeQuery query = new RegeocodeQuery(latLng, 200, GeocodeSearch.AMAP);
geocodeSearch.getFromLocationAsyn(query);
百度也类似,只是用GeoCoder类。我建议你注意一下「周边POI」的获取——反编码结果里通常包含附近的餐馆、地铁站等信息,做LBS应用时非常有用。
避坑:反编码有调用频率限制。高德免费版每天30万次,百度是每天30万次。如果用户频繁拖动地图,记得做节流处理。我一般用RxJava的throttleLast操作符,每500ms只发一次请求。
知识体系总览
下面这张图,是我梳理的本章核心脉络。你可以把它当作学习地图:
这张图把本章的五个核心模块串起来了。你可以看到,传感器框架是底层基础,加速度计和陀螺仪是具体应用,GPS定位和地图SDK是上层服务,地理编码则是连接位置和文字信息的桥梁。
好了,这一章的内容就到这里。传感器和定位这块,上手不难,但要做好细节处理——权限、空判断、频率控制、Key配置,每一个都是坑。多写几次,自然就熟了。