16、传感器与定位:传感器框架、GPS定位与地图SDK集成

说实话,移动设备最迷人的地方,就是它能感知周围的世界。你拿着手机转个圈,屏幕跟着转;你打开地图,它告诉你现在在哪。这些背后,就是传感器和定位技术在干活。

这一章,我带你把这些东西捋清楚。从底层的传感器框架,到GPS和网络定位,再到集成Google Maps和高德、百度地图。嗯,内容不少,但都是实战硬货。

16.1 Android传感器框架

Android的传感器框架,说白了就是一套标准接口。硬件厂商把数据送上来,系统帮你封装好,你只管调用。

核心就三个类:SensorManagerSensorSensorEventListener。流程很简单:先拿到SensorManager,然后通过它获取某个传感器,再注册监听器,数据就源源不断来了。

核心流程:

  1. 获取 SensorManager:getSystemService(SENSOR_SERVICE)
  2. 获取指定传感器:sensorManager.getDefaultSensor(type)
  3. 注册监听器:sensorManager.registerListener(listener, sensor, delay)
  4. 在 onSensorChanged 中处理数据
  5. 记得在 onPause 中取消注册

我刚开始做传感器项目时,就犯过一个低级错误——忘了在Activity暂停时取消注册。结果屏幕关了,传感器还在跑,电量哗哗地掉。后来养成了习惯,registerunregister永远成对出现。

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 传感器知识体系

下面这张图,是我自己整理的传感器知识结构。你看一眼,心里就有谱了。

Android 传感器与定位知识体系 传感器框架 SensorManager SensorEventListener onSensorChanged 三大核心传感器 加速度计 → 线性加速度 陀螺仪 → 角速度 磁力计 → 方向 定位技术 GPS定位 网络定位 融合定位 地图SDK集成 Google Maps | 高德地图 | 百度地图

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