Android 四大组件——Service(服务)【基础篇2】

目录

一、前台服务和后台服务

[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+是作用于应用在后台情况,应用处于前台时

  1. 在Android 8.0之前,后台服务在应用处于后台时,是一直可以运行的
  2. 在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 引入了通知渠道的概念:

  1. 分类管理:应用可以将通知分为不同类别(渠道)

  2. 精细控制:用户可以单独控制每个渠道的通知行为

  3. 必须声明:每个通知都必须属于一个渠道

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) {}
    };
}
相关推荐
@淡 定2 小时前
JVM调优参数配置详解
java·jvm·算法
爱宇阳2 小时前
在 Docker 环境中为 GitLab 实例配置邮件服务器
java·docker·gitlab
大梦谁先觉i2 小时前
Spring 实现 3 种异步流式接口,干掉接口超时烦恼
java·后端·spring
Lenyiin2 小时前
第 97 场周赛:公平的糖果交换、查找和替换模式、根据前序和后序遍历构造二叉树、子序列宽度之和
java·c++·python·leetcode·周赛·lenyiin
明天更新2 小时前
oss存储分片的简单思路
java
是垚不是土2 小时前
MySQL8.0数据库GTID主从同步方案
android·网络·数据库·安全·adb
cnxy1883 小时前
MySQL地理空间数据完整使用指南
android·数据库·mysql
凌冰_3 小时前
IDEA2025 搭建Web并部署到Tomcat运行Servlet+Thymeleaf
java·servlet·tomcat
Seven973 小时前
剑指offer-53、表达数值的字符串
java