28、后台监听:如何在Service中持续获取加速度数据?

前面几章我们都在Activity里玩传感器,一退出页面数据就断了。但很多场景下——比如计步、跌倒检测、车辆碰撞预警——你需要App在后台甚至锁屏时也能持续监听加速度。这时候就得请出Service了。

说白了,Service就是Android里专门干「后台活」的组件。它没有界面,但能一直跑。我最早做运动监测App时,就踩过Service被系统杀掉的坑,后来才慢慢摸清门道。

为什么不能在Activity里一直监听?

你想想看,用户按了Home键,Activity就进入onStop状态。系统随时可能回收它。就算不回收,传感器回调也会跟着暂停。所以我们需要一个「打不死的小强」——Service。

但注意,Service也不是绝对安全的。Android 8.0以后对后台限制越来越严。嗯,这里要分情况讨论。

方案一:前台Service(推荐)

这是目前最稳妥的方式。前台Service会显示一个持续通知,告诉用户「我在后台干活」。系统不会轻易杀它。

我个人习惯用前台Service做传感器监听,因为:

  • 优先级高,系统几乎不会回收
  • 用户能感知到App在运行,合规
  • 适合长时间监听(比如计步器)

核心思路:在Service的onCreate里注册传感器,在onDestroy里注销。前台Service通过startForeground()启动。

public class SensorService extends Service implements SensorEventListener {

    private SensorManager sensorManager;
    private Sensor accelerometer;
    private static final int NOTIFICATION_ID = 1001;

    @Override
    public void onCreate() {
        super.onCreate();
        // 初始化传感器
        sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 启动前台Service
        startForeground(NOTIFICATION_ID, buildNotification());

        // 注册监听器
        if (accelerometer != null) {
            sensorManager.registerListener(this, accelerometer,
                    SensorManager.SENSOR_DELAY_NORMAL);
        }

        // 如果Service被杀死,自动重启
        return START_STICKY;
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        float x = event.values[0];
        float y = event.values[1];
        float z = event.values[2];

        // 这里处理数据,比如写入文件或发送广播
        Log.d("SensorService", "X: " + x + ", Y: " + y + ", Z: " + z);
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // 精度变化时处理
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 一定要注销监听,否则传感器一直开着耗电
        sensorManager.unregisterListener(this);
    }

    private Notification buildNotification() {
        // Android 8.0+ 需要通知渠道
        NotificationChannel channel = new NotificationChannel(
                "sensor_channel",
                "传感器服务",
                NotificationManager.IMPORTANCE_LOW
        );
        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        manager.createNotificationChannel(channel);

        return new Notification.Builder(this, "sensor_channel")
                .setContentTitle("加速度传感器")
                .setContentText("正在后台监听中...")
                .setSmallIcon(android.R.drawable.ic_dialog_info)
                .build();
    }
}

小技巧:START_STICKY能让Service被系统杀掉后自动重启。但别太依赖它,Android 12以后这个行为也变了。

方案二:普通后台Service(不推荐)

如果你不需要长时间运行,只是短暂采集几秒钟数据,普通Service也行。但要注意:

  • Android 8.0以上,后台Service在几分钟内就会被系统停止
  • 锁屏后可能直接被杀
  • 适合「一次性采集」场景

我曾经在做一个跌倒检测Demo时,用了普通Service。结果手机放口袋里锁屏后,Service就挂了。后来改成前台Service才稳定。

生命周期管理:别让传感器一直开着

这是很多新手容易忽略的点。传感器一旦注册,就会持续消耗电量。哪怕你Service在跑,如果不需要数据了,一定要注销监听。

我建议在以下时机注销:

  1. Service的onDestroy()里——必须做
  2. 用户主动停止监听时——比如点击「停止」按钮
  3. 长时间没有数据变化时——可以加个超时逻辑

避坑指南:我曾经有个项目,Service里注册了传感器但忘记注销。结果用户反馈手机发烫、掉电快。查了半天发现是Service被销毁时没走onDestroy()——因为系统直接杀了进程。后来我在onTaskRemoved()和onLowMemory()里也加了注销逻辑。

数据传递:Service怎么把数据给Activity?

Service采集到数据,总得让UI层知道吧?常用的方式有:

  • 广播(Broadcast):简单直接,适合低频数据
  • 回调接口:通过Binder绑定Service,但要注意内存泄漏
  • LiveData/Flow:配合ViewModel,生命周期安全
  • 文件/数据库:适合大量数据,比如记录一整天的加速度

我个人偏爱LiveData方案。在Service里持有一个LiveData对象,Activity观察它。这样即使Activity重建,数据也不会丢。

// 在Service中
public class SensorService extends Service {
    private MutableLiveData<float[]> sensorData = new MutableLiveData<>();

    public LiveData<float[]> getSensorData() {
        return sensorData;
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        float[] values = event.values.clone();
        sensorData.postValue(values); // 注意用postValue,因为是在子线程
    }
}

// 在Activity中
SensorService service = ...;
service.getSensorData().observe(this, values -> {
    // 更新UI
    textView.setText("X: " + values[0]);
});

SVG流程图:Service中传感器监听的生命周期

Service中传感器监听生命周期 启动Service onCreate() 初始化传感器 onStartCommand() 注册监听器 onSensorChanged() 持续回调 onDestroy() 注销监听器 关键点 • 前台Service保活 • START_STICKY重启 • 必须注销监听器 • 数据通过LiveData传递 注意:Android 8.0+ 建议使用前台Service

电量优化:别让传感器成为电老虎

加速度传感器本身功耗不高,但如果一直以最高频率采样,电量还是会哗哗地掉。我建议:

  • 降低采样频率:SENSOR_DELAY_NORMAL就够了,别用FASTEST
  • 批量处理:攒一批数据再写入,别来一次写一次
  • 条件触发:比如只有加速度超过阈值时才高频采样
  • 结合Doze模式:Android 6.0以后的省电模式会限制传感器

我的经验:做计步器时,我用了SENSOR_DELAY_UI(约60ms一次),然后每10次回调才计算一次步数。这样既省电,精度也够。

兼容性:不同厂商的坑

嗯,这里要特别提一下。华为、小米、OPPO等厂商对后台限制比原生Android更严格。有些手机甚至不允许前台Service长时间运行传感器。

我曾经在小米手机上测试,前台Service跑了10分钟就被系统弹窗提示「该应用正在耗电」。用户点一下「停止」就全完了。

解决方案?没有完美的。只能:

  • 引导用户加入电池优化白名单
  • 使用JobScheduler定期唤醒采集
  • 或者干脆用厂商提供的运动健康API

总结

在Service中持续获取加速度数据,核心就三件事:

  1. 用前台Service保活
  2. 生命周期里正确注册/注销传感器
  3. 用合适的方式把数据传给UI层

记住,传感器监听是一把双刃剑。用好了能做出很酷的功能,用不好就是电老虎。多测试不同机型,尤其是国产ROM,你会发现很多意想不到的「惊喜」。


公众号:蓝海资料掘金营,微信deep3321