15、后台采集与前台服务:使用前台Service持续采集光线数据,通知栏提示,避免被系统杀死
好,咱们继续往下走。
前面几章,我们都是在 Activity 里直接采集光线数据。这有个问题——App 一退到后台,系统就可能把你的进程给杀了。尤其是国产手机,那后台管理策略,你懂的。我当年做第一个光线采集项目时,就吃过这个亏。用户反馈说「App 挂后台一会儿,数据就不动了」。查了半天,原来是系统把 Service 给回收了。
那怎么解决?用前台 Service。
为什么普通 Service 不行?
Android 系统有个优先级机制。普通 Service 的优先级其实不高,跟 Activity 不在前台时差不多。系统内存吃紧时,第一个被干掉的就是它。
前台 Service 不一样。它会在通知栏显示一条通知,告诉用户「嘿,我还在跑呢」。系统看到这条通知,就知道这个 Service 对用户来说是可见的,优先级会大幅提升。说白了,就是给系统一个「别杀我」的理由。
核心区别:
- 普通 Service:后台运行,无通知,低优先级,容易被杀
- 前台 Service:后台运行,有通知,高优先级,系统尽量不杀
实现步骤:三步走
我个人习惯把前台 Service 的实现拆成三步。这样思路清晰,不容易漏东西。
- 创建 Service 类——继承 Service,重写生命周期方法
- 构建通知——创建 NotificationChannel(Android 8.0+ 必须)和 Notification
- 调用 startForeground()——把 Service 提升为前台服务
嗯,这里要注意:startForeground() 必须在 Service 的 onCreate() 或 onStartCommand() 里调用,而且要在 5 秒内完成。否则系统会报错。
代码实战:光线采集前台 Service
来,直接看代码。我写了一个完整的 LightSensorService,你拿去就能用。
public class LightSensorService extends Service {
private static final String CHANNEL_ID = "light_sensor_channel";
private static final int NOTIFICATION_ID = 1001;
private SensorManager sensorManager;
private Sensor lightSensor;
private float lastLux = 0f;
@Override
public void onCreate() {
super.onCreate();
// 初始化传感器
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
// 创建通知渠道
createNotificationChannel();
// 启动前台服务
startForeground(NOTIFICATION_ID, buildNotification("光线传感器已启动"));
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 注册传感器监听
if (lightSensor != null) {
sensorManager.registerListener(sensorEventListener, lightSensor,
SensorManager.SENSOR_DELAY_NORMAL);
}
// 如果服务被杀死,自动重启
return START_STICKY;
}
private final SensorEventListener sensorEventListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
lastLux = event.values[0];
// 更新通知栏显示
Notification notification = buildNotification(
"当前光照强度: " + lastLux + " lux");
NotificationManager manager = (NotificationManager)
getSystemService(NOTIFICATION_SERVICE);
manager.notify(NOTIFICATION_ID, notification);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// 精度变化时,可以记录日志
}
};
private Notification buildNotification(String content) {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(
this, 0, notificationIntent,
PendingIntent.FLAG_IMMUTABLE);
return new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("光线采集服务")
.setContentText(content)
.setSmallIcon(R.drawable.ic_light)
.setContentIntent(pendingIntent)
.setOngoing(true) // 不可滑动删除
.build();
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
"光线传感器",
NotificationManager.IMPORTANCE_LOW
);
channel.setDescription("用于显示光线采集状态");
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(channel);
}
}
@Override
public void onDestroy() {
super.onDestroy();
sensorManager.unregisterListener(sensorEventListener);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
小提示:我在 onStartCommand() 里返回了 START_STICKY。这样即使 Service 被系统意外杀死,它也会自动重启。不过别太依赖这个,国产系统上不一定管用。
别忘了清单文件注册
Service 必须在 AndroidManifest.xml 里注册,否则系统找不到它。还要加上 FOREGROUND_SERVICE 权限。
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application ...>
<service
android:name=".LightSensorService"
android:enabled="true"
android:exported="false" />
</application>
注意:Android 9(API 28)开始,前台 Service 必须声明 foregroundServiceType。如果你只做传感器采集,可以声明为 dataSync 或 specialUse。不声明的话,高版本系统会直接抛异常。
启动与停止 Service
在 Activity 里启动和停止这个 Service,代码很简单:
// 启动
Intent intent = new Intent(this, LightSensorService.class);
startForegroundService(intent);
// 停止
Intent intent = new Intent(this, LightSensorService.class);
stopService(intent);
注意,这里用的是 startForegroundService(),不是 startService()。这是 Android 8.0 引入的新方法,专门用于启动前台 Service。如果你用 startService(),系统会认为你忘了调用 startForeground(),过一会儿就会报错。
前台 Service 的生命周期
我画了一张图,帮你理清整个流程:
避坑指南
我曾经在几个项目里踩过前台 Service 的坑,分享给你:
- 通知渠道必须创建:Android 8.0 以上,不创建 NotificationChannel 直接发通知,通知不会显示。而且这个坑不会崩溃,只是静默失败,特别难排查。
- 权限问题:Android 13(API 33)开始,
POST_NOTIFICATIONS权限需要动态申请。如果用户拒绝,前台 Service 的通知栏就不显示了。但 Service 本身还能跑。 - 国产手机适配:小米、华为、OPPO 等厂商有自己的后台管理策略。即使用了前台 Service,也可能被限制。我建议在
onStartCommand()里加个日志,看看 Service 是否真的在跑。 - 不要频繁更新通知:每次传感器数据变化都更新通知,会导致通知栏闪烁。我一般设置一个阈值,比如变化超过 10 lux 才更新,或者每 2 秒更新一次。
我的习惯:在开发阶段,我会在通知栏显示详细数据,方便调试。上线前,我会把通知内容改成「光线采集服务运行中」,减少对用户的打扰。毕竟没人喜欢通知栏一直闪。
总结一下
前台 Service 是 Android 后台任务的「免死金牌」。虽然不能保证 100% 不被杀,但至少能让系统三思而后行。配合光线传感器,你就可以实现一个稳定、可靠的后台采集服务。
嗯,这一章的内容就到这儿。代码不多,但每一行都有它的道理。你动手试试,把 Service 跑起来,看看通知栏的变化。实践出真知。
公众号:蓝海资料掘金营,微信deep3321