目录
[1. 核心区别概述](#1. 核心区别概述)
[2. 后台服务](#2. 后台服务)
[3. 前台服务](#3. 前台服务)
[3.1 通知 Android 8+](#3.1 通知 Android 8+)
[3.2 前台服务的完整实现](#3.2 前台服务的完整实现)
[3.3 前台服务类型(Android 10+)](#3.3 前台服务类型(Android 10+))
一、前台服务和后台服务
在Android开发中,服务(Service)是实现后台任务的核心组件。随着Android系统的演进,前台服务(Foreground Service)和后台服务(Background Service)的使用场景和限制变得越来越明确。本文将使用Java详细讲解这两种服务的区别、实现方式和最佳实践。
1. 核心区别概述
| 特性 | 前台服务 (Foreground Service) | 后台服务 (Background Service) |
|---|---|---|
| 优先级 | 高 | 低 |
| 通知要求 | 必须显示常驻通知 | 不需要通知 |
| 系统限制 | 限制较少 | Android 8.0+ 严格限制 |
| 生命周期 | 不易被系统杀死 | 容易被系统杀死 |
| 用户感知 | 用户可见 | 用户不可见 |
2. 后台服务
特点:
-
用户无感知,没有持续的通知
-
适用于短暂的后台任务
-
受系统后台限制影响较大
java
// 启动后台服务
Intent serviceIntent = new Intent(context, MyService.class);
context.startService(serviceIntent);// 对于 Android 8.0+ 有限制
注意:Android 8.0+是作用于应用在后台情况,应用处于前台时
- 在Android 8.0之前,后台服务在应用处于后台时,是一直可以运行的。
- 在Android 8.0+,后台服务在应用处于后台时,后台服务在运行一段时间后就会停止。
java
#### 1. 后台服务的限制:
- ❌ 应用进入后台后,后台服务很快会被停止
- ❌ 无法从后台启动新的后台服务
- ✅ 解决方案:使用 WorkManager、JobScheduler 等替代方案
| 应用状态 | Android 8.0-8.1 | Android 9+ | Android 12+ |
|---|---|---|---|
| 前台应用 | 服务正常运行 | 服务正常运行 | 服务正常运行 |
| 进入后台 | 1分钟后开始限制 | 立即开始限制 | 立即开始限制 |
| 服务存活时间 | 约1-3分钟 | 约1-2分钟 | 几十秒到1分钟 |
| 是否可启动新服务 | ❌ 不允许 | ❌ 不允许 | ❌ 不允许 |
| 特性 | Android 7.1 及之前 | Android 8.0+ |
|---|---|---|
| 后台运行时间 | ✅ 无限期运行 | ❌ 几分钟后被停止 |
| 启动限制 | ✅ 随时可启动 | ❌ 后台无法启动 |
| 通知要求 | ❌ 不需要 | ✅ 前台服务需要 |
| 权限要求 | ❌ 不需要特殊权限 | ✅ 需要 FOREGROUND_SERVICE |
| 重启后恢复 | ✅ 自动重启(STICKY) | ⚠️ 受限恢复 |
| CPU限制 | ✅ 无限制 | ❌ 后台受限 |
| 网络访问 | ✅ 正常访问 | ❌ 后台受限 |
| Alarm精确性 | ✅ 精确闹钟 | ❌ 需要特殊权限 |
3. 前台服务
前台服务(Foreground Service)是 Android 中一种特殊的服务,它执行用户能注意到的操作。前台服务必须显示一个状态栏通知,让用户知道服务正在运行。
特点:
-
系统几乎不会终止前台服务
-
必须显示通知
-
相比纯后台服务更受系统保护
java
// 前台服务 - 不容易被系统终止
public class ForegroundService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 显示通知,提升为前台服务
Notification notification = createNotification();
startForeground(NOTIFICATION_ID, notification);
// 系统会尽量保持服务运行
doImportantWork();
return START_STICKY;
}
}
//启动方式
// Android 8.0+ 必须使用 startForegroundService
// 注意:必须在调用 startForegroundService() 后的 5 秒内调用 startForeground()
context.startForegroundService(serviceIntent);
3.1 通知 Android 8+
因为前台服务跟通知挂钩,在正式使用前台服务之前。我们先简单了解下通知在 Android 8 前后的差异。Android 8.0 (API 26) 引入的通知渠道(Notification Channel)功能的一部分。它的作用是创建和管理通知的分类,让用户可以按类别控制应用的通知行为。
在 Android 8.0 之前,用户只能整体控制应用的通知:
要么全部允许:接收所有通知
要么全部禁止:屏蔽所有通知
没有中间选择,这导致了很差的用户体验。
Android 8.0 的改变
Android 8.0 引入了通知渠道的概念:
-
分类管理:应用可以将通知分为不同类别(渠道)
-
精细控制:用户可以单独控制每个渠道的通知行为
-
必须声明:每个通知都必须属于一个渠道
java
// 示例:创建不同类型通知渠道
private void createNotificationChannels() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 1. 重要通知渠道(高重要性,有声音)
NotificationChannel importantChannel = new NotificationChannel(
"important_channel",
"重要通知",
NotificationManager.IMPORTANCE_HIGH
);
importantChannel.setDescription("重要消息通知");
importantChannel.enableLights(true);
importantChannel.setLightColor(Color.RED);
importantChannel.enableVibration(true);
// 2. 普通通知渠道(中等重要性)
NotificationChannel normalChannel = new NotificationChannel(
"normal_channel",
"普通通知",
NotificationManager.IMPORTANCE_DEFAULT
);
normalChannel.setDescription("一般性通知");
// 3. 静默通知渠道(低重要性,无声音)
NotificationChannel silentChannel = new NotificationChannel(
"silent_channel",
"静默通知",
NotificationManager.IMPORTANCE_LOW
);
silentChannel.setDescription("后台任务通知");
silentChannel.setShowBadge(false);
// 注册所有渠道
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(importantChannel);
manager.createNotificationChannel(normalChannel);
manager.createNotificationChannel(silentChannel);
}
}
Android 8.0 之前 vs 之后
| 特性 | Android 8.0 之前 | Android 8.0 之后 |
|---|---|---|
| 通知管理 | 整体控制应用通知 | 按渠道精细控制 |
| 渠道概念 | 不存在 | 必须创建和使用 |
| 优先级设置 | setPriority() |
通过渠道重要性实现 |
| 用户控制 | 只能全部开/关 | 可以控制每个渠道 |
| 兼容性 | 旧方式 | 必须适配新API |
接下来我们回归主题。
3.2 前台服务的完整实现
java
public class BasicForegroundService extends Service {
// 通知相关常量
private static final int NOTIFICATION_ID = 1001;
private static final String CHANNEL_ID = "foreground_service_channel";
private static final String CHANNEL_NAME = "前台服务";
// 线程控制
private volatile boolean isRunning = false;
private Thread serviceThread;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null; // 非绑定式服务
}
@Override
public void onCreate() {
super.onCreate();
Log.d("ForegroundService", "服务创建");
// 创建通知渠道(Android 8.0+必需)
createNotificationChannel();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("ForegroundService", "服务启动");
// 步骤1:创建通知
Notification notification = buildNotification("服务启动中...");
// 步骤2:启动为前台服务
// 必须在调用 startForeground() 后的 5 秒内显示通知
startForeground(NOTIFICATION_ID, notification);
// 步骤3:开始执行任务
if (!isRunning) {
startServiceTask();
}
// 步骤4:根据需要处理重启行为
return START_STICKY; // 服务被终止后会自动重启
}
/**
* 创建通知渠道(Android 8.0+必需)
*/
private void createNotificationChannel() {
// 检查 Android 版本
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 创建通知渠道
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_LOW // 低重要性,不会发出声音
);
// 配置渠道
channel.setDescription("用于前台服务的通知渠道");
channel.setShowBadge(false); // 不显示角标
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
// 注册渠道
NotificationManager manager = getSystemService(NotificationManager.class);
if (manager != null) {
manager.createNotificationChannel(channel);
}
}
}
/**
* 构建通知
*/
private Notification buildNotification(String contentText) {
// 创建点击通知时的Intent
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
pendingIntent = PendingIntent.getActivity(
this,
0,
notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE
);
} else {
pendingIntent = PendingIntent.getActivity(
this,
0,
notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT
);
}
// 使用 NotificationCompat 保证向后兼容
return new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("前台服务运行中")
.setContentText(contentText)
.setSmallIcon(R.drawable.ic_notification) // 必需
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setOngoing(true) // 设置为持续通知,用户无法划掉
.setOnlyAlertOnce(true) // 只在第一次时提醒
.build();
}
/**
* 启动服务任务
*/
private void startServiceTask() {
isRunning = true;
serviceThread = new Thread(new Runnable() {
@Override
public void run() {
int progress = 0;
while (isRunning && progress < 100) {
try {
// 模拟任务执行
Thread.sleep(1000);
progress += 10;
// 更新通知
updateNotification("任务进度: " + progress + "%");
// 执行实际工作
doRealWork(progress);
} catch (InterruptedException e) {
Log.e("ForegroundService", "任务被中断", e);
break;
}
}
// 任务完成
if (progress >= 100) {
updateNotification("任务完成");
// 延迟停止服务
new Handler(Looper.getMainLooper()).postDelayed(
new Runnable() {
@Override
public void run() {
stopSelf();
}
}, 2000);
}
}
});
serviceThread.start();
}
/**
* 执行实际工作
*/
private void doRealWork(int progress) {
// 这里执行实际的前台任务
Log.d("ForegroundService", "执行工作中,进度: " + progress + "%");
// 示例:模拟文件处理
// processFile();
// 示例:上传数据
// uploadData();
}
/**
* 更新通知
*/
private void updateNotification(String contentText) {
Notification notification = buildNotification(contentText);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (manager != null) {
manager.notify(NOTIFICATION_ID, notification);
}
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("ForegroundService", "服务销毁");
// 停止任务
isRunning = false;
if (serviceThread != null && serviceThread.isAlive()) {
serviceThread.interrupt();
try {
serviceThread.join(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 移除通知(startForeground会自动处理)
// stopForeground(true);
}
}
3.3 前台服务类型(Android 10+)
从 Android 10(API 29)开始,Google 引入了 foregroundServiceType 属性,用于声明前台服务的用途。如果你的应用需要在 Android 10 或更高版本中调用 startForeground() 方法,则必须在 AndroidManifest.xml 文件中为对应的 <service> 元素指定 foregroundServiceType 属性。
java
// AndroidManifest.xml 中的声明
<service
android:name=".MyForegroundService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="location|microphone" />
// 可用的前台服务类型常量
// 定义在 android.content.pm.ServiceInfo 中
public static final int FOREGROUND_SERVICE_TYPE_NONE = 0;
public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 1;
public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 2;
public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 4;
public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 8;
public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16;
public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 32;
public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 64;
public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = 128;
foregroundServiceType 的作用:
它用于描述前台服务的具体用途,帮助系统更好地管理资源和通知用户。
常见的类型包括:
- location:用于获取位置信息。
- mediaPlayback:用于播放音频或视频。
- camera:用于访问摄像头。
- microphone:用于录音。
- dataSync:用于后台数据同步。
- phoneCall:用于电话通话。
- connectedDevice:用于与外部设备通信。
java
public class LocationForegroundService extends Service {
private static final int NOTIFICATION_ID = 1003;
private LocationManager locationManager;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 检查位置权限
if (checkLocationPermission()) {
startLocationTracking();
} else {
stopSelf();
}
return START_STICKY;
}
private boolean checkLocationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
// 没有权限,可以发送广播请求权限
requestLocationPermission();
return false;
}
// Android 10+ 需要后台位置权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (checkSelfPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
requestBackgroundLocationPermission();
return false;
}
}
}
return true;
}
private void startLocationTracking() {
// 创建通知
Notification notification = createLocationNotification();
// Android 10+ 需要指定前台服务类型
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(NOTIFICATION_ID, notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
} else {
startForeground(NOTIFICATION_ID, notification);
}
// 开始定位
startLocationUpdates();
}
private void startLocationUpdates() {
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
if (locationManager != null) {
try {
// 请求位置更新
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
5000, // 最小时间间隔(毫秒)
10, // 最小距离间隔(米)
locationListener
);
} catch (SecurityException e) {
Log.e("LocationService", "位置权限被拒绝", e);
stopSelf();
}
}
}
private LocationListener locationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
// 处理位置更新
updateNotification("位置更新: " +
location.getLatitude() + ", " + location.getLongitude());
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {}
@Override
public void onProviderEnabled(String provider) {}
@Override
public void onProviderDisabled(String provider) {}
};
}