Android启动前台服务(startForegroundService)

Android8.0+后台服务适配与限制解决方案
文章讲述了在Android8.0及以上版本中,由于系统对后台Service的限制,如何通过使用前台Service、NotificationChannel、JobScheduler和WorkManager来实现后台任务的执行。特别提到,JobScheduler和WorkManager的使用策略,以及如何解决JobScheduler最小间隔时间限制的问题。

问题:

APP in background in null uid
AndroidRuntime: android.app.RemoteServiceException: Context.startForegroundService()
 did not then call Service.startForeground():

注意事项:

  • 8.0适配:通知需要加上NotificationChannel,开启前台服务的方式startForegroundService()
  • 9.0适配:manifest.xml文件中需要增加权限:FOREGROUND_SERVICE

Android之 Service服务详解

1、前台权限:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

2、Service中开启通知:

class LogUploadService : Service() {

    override fun onBind(arg0: Intent): IBinder? {
        return null
    }

    override fun onCreate() {
        super.onCreate()
        Log.d("caowj", "LogUploadService onCreate")
        initNotification()
    }

    private fun initNotification() {
        val channelName = "埋点上传"
        val channelId = BuildConfig.APPLICATION_ID

		// 发送通知,把service置于前台
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        // 从Android 8.0开始,需要注册通知通道
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH)
            notificationManager.createNotificationChannel(channel)
        }
        val notification = NotificationCompat.Builder(this, channelId)
            .setSmallIcon(R.mipmap.app_icon)
            .setContentTitle("埋点Log上报")
            .setContentText("服务正在运行,请勿关闭")
            .setAutoCancel(false)
            .setOngoing(true)
            .build()
        // 注意第一个参数不能为0
        startForeground(1, notification)
    }

    override fun onDestroy() {
        //停止的时候销毁前台服务。
        stopForeground(true);
    }
}


3、启动Service:

// Android 8.0使用startForegroundService在前台启动新服务
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    startForegroundService(Intent(this, LogUploadService::class.java))
} else {
    startService(Intent(this, LogUploadService::class.java))
}

Android O对后台Service限制怎么解决

4、其他方案:

由于从Android 8.0开始禁止应用在后台运行时创建Service,所以要解决这种这种问题有以下几种方案:

  • 通过Context.startForegroundService()方式启动一个前台Service,前台Service的启动没有受到限制。
  • 集成Google Firebase Messaging。
  • 使用JobService;最小周期时长为 15 分钟
  • WorkManager: 周期性任务;最小周期时长为 15 分钟 (与 JobScheduler 相同)

官方建议使用JobScheduler 替换 后台Service。

从Android 8.0,使用JobScheduler替换后台Service,它会周期性启动一个任务,查询服务器,然后退出。相比于后台Service,它消耗的资源明显较少,间接提升了手机性能。

问题补充:

JobService 最小间隔时间要求大于15分钟;否则报错:

Requested interval +1m0s0ms for job 10 is too small; raising to +15m0s0ms   
Requested flex +1m0s0ms for job 10 is too small; raising to +5m0s0ms        

WorkManager: 周期性任务

5、JobScheduler实现定时间隔处理

Android之任务调度WorkManager和JobSchedule的使用

通过递归的方式,解决最小间隔时间要求大于15分钟的限制;

/**
 * JobScheduler实现定时间隔处理
 * 通过递归的方式,在onStartJob中,利用setMinimumLatency来设置时间间隔,执行完后再重新创建启用任务来实现
 */
class PeriodicJobService : JobService() {

    override fun onStartJob(p0: JobParameters?): Boolean {
        Log.i(TAG, "onStartJob---")
        startScheduler(this)
        return false
    }

    override fun onStopJob(p0: JobParameters?): Boolean = false

    companion object {
        var TAG: String = "caowj"
        var JOBID: Int = 100
        var InterValTime: Long = 10000
        private var jobScheduler: JobScheduler? = null
        private var jobInfo: JobInfo? = null

        fun startScheduler(context: Context) {
            jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
            cancelScheduler()
            if (jobInfo == null) {
                jobInfo = JobInfo.Builder(JOBID, ComponentName(context, PeriodicJobService::class.java))
                    .setMinimumLatency(InterValTime) // 最小为10秒
                    .build()
            }
            val result = jobScheduler?.schedule(jobInfo!!)
        }

        fun cancelScheduler() {
            jobScheduler?.cancel(JOBID)
        }
    }
}

需要提醒:

  • JobScheduler和WorkManager都只能在APP存活的时候执行,但是定时器是一直工作的。
  • 关闭APP再启动,JobScheduler并不能够直接继续运行,但是WorkManager可以。
  • 如果重启APP的时候,WorkManager任务的计时器应该已经执行了一次或多次,则会立即开始执行。
  • 重启App之后WorkManager如果直接执行了一个任务,则从这个时候开始算新的周期,不会按旧有周期走。
<think>我们被要求回答关于AndroidstartForegroundService的使用方法。根据引用内容,我们可以总结如下: 1. **startForegroundServicestartService的区别**:-在Android8.0(API26)及以上,如果应用在后台启动服务,必须使用`startForegroundService`,并且该服务必须在创建后5秒内调用`startForeground`方法,否则系统会抛出`ForegroundServiceDidNotStartInTimeException`异常,导致应用崩溃(引用[3])。-在Android8.0以下,可以直接使用`startService`。2.**使用步骤**:-首先,在清单文件中声明服务,并申请必要的权限(如`FOREGROUND_SERVICE`权限)。-在代码中,根据设备版本选择启动方式(引用[2]):```javaIntentserviceIntent= newIntent(this, MyForegroundService.class);if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.O){startForegroundService(serviceIntent);}else {startService(serviceIntent);}```-在服务类(如`MyForegroundService`)中,重写`onCreate`和`onStartCommand`方法,并在服务启动后的5秒内调用`startForeground`方法(引用[3])。3.**在服务中调用startForeground**:-在服务中,必须创建一个通知,并调用`startForeground`方法,传入一个唯一的通知ID和通知对象(引用[3]和[5])。-注意:从Android8.0开始,必须使用通知渠道(NotificationChannel)(引用[5])。 4. **异常处理**:-如果忘记调用`startForeground`,系统会抛出异常(引用[3])。因此,我们可以按照以下步骤编写代码:###步骤1:在AndroidManifest.xml中添加权限和服务声明```xml<uses-permissionandroid:name="android.permission.FOREGROUND_SERVICE" /><application...><serviceandroid:name=".MyForegroundService"android:enabled="true"android:exported="false" /></application> ```###步骤2:创建通知渠道(仅Android8.0及以上) 在应用启动时(如主Activity的onCreate中)创建通知渠道:```javaif(Build.VERSION.SDK_INT >=Build.VERSION_CODES.O) {NotificationChannel channel= newNotificationChannel("channel_id","ChannelName",NotificationManager.IMPORTANCE_DEFAULT);NotificationManagermanager =getSystemService(NotificationManager.class);manager.createNotificationChannel(channel); }``` ###步骤3:实现前台服务类```javapublic classMyForegroundServiceextends Service{privatestatic finalint NOTIFICATION_ID=1;privatestatic finalString CHANNEL_ID ="channel_id";@Overridepublicvoid onCreate() {super.onCreate();}@Overridepublicint onStartCommand(Intentintent,int flags, intstartId) {//创建通知Notificationnotification =new NotificationCompat.Builder(this,CHANNEL_ID).setContentTitle("前台服务标题").setContentText("前台服务内容").setSmallIcon(R.drawable.ic_notification).build();//调用startForeground,将服务设置为前台服务startForeground(NOTIFICATION_ID,notification);//在这里执行服务的实际工作returnSTART_STICKY; //根据需要返回合适的启动方式}@Nullable@Overridepublic IBinder onBind(Intent intent) {returnnull;}} ```###步骤4:启动服务在需要启动服务的地方(如Activity中):```javaIntentserviceIntent= newIntent(this, MyForegroundService.class); if(Build.VERSION.SDK_INT >=Build.VERSION_CODES.O) {startForegroundService(serviceIntent); }else {startService(serviceIntent); }``` ###注意事项1.**时间限制**:使用`startForegroundService`启动服务后,必须在5秒内调用`startForeground`,否则会抛出异常(引用[3])。 2. **通知渠道**:Android8.0及以上必须创建通知渠道(引用[5])。3.**通知内容**:前台服务必须有一个持续的通知显示给用户。###相关问题1.在Android12中,前台服务启动有哪些新的限制?2.如何停止前台服务?停止后通知会自动消失吗?3.前台服务与后台服务在资源限制上有何不同?4.如何在前台服务中更新通知内容? 以上步骤和代码示例可以帮助你正确使用`startForegroundService`启动前台服务。</think>### Android `startForegroundService` 使用方法详解 #### 1. **基本使用流程** ```java // 步骤1:创建服务Intent Intent serviceIntent = new Intent(context, MyForegroundService.class); // 步骤2:检查Android版本 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // Android 8.0+ 必须使用startForegroundService context.startForegroundService(serviceIntent); } else { // 旧版本使用startService context.startService(serviceIntent); } ``` [^2] #### 2. **服务类实现关键点** ```java public class MyForegroundService extends Service { private static final int NOTIFICATION_ID = 1; private static final String CHANNEL_ID = "foreground_channel"; @Override public void onCreate() { super.onCreate(); createNotificationChannel(); // 创建通知渠道 } @Override public int onStartCommand(Intent intent, int flags, int startId) { // 必须在5秒内调用startForeground() Notification notification = buildNotification(); startForeground(NOTIFICATION_ID, notification); // 这里执行实际的后台工作 return START_STICKY; } private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( CHANNEL_ID, "前台服务通道", NotificationManager.IMPORTANCE_DEFAULT ); NotificationManager manager = getSystemService(NotificationManager.class); manager.createNotificationChannel(channel); } } private Notification buildNotification() { return new NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("服务运行中") .setContentText("正在执行后台任务") .setSmallIcon(R.drawable.ic_notification) .build(); } } ``` [^3][^5] #### 3. **AndroidManifest.xml配置** ```xml <service android:name=".MyForegroundService" android:enabled="true" android:exported="false" /> <!-- 添加前台服务权限 --> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> ``` #### ⚠️ 关键注意事项 1. **5秒超时规则** 调用`startForegroundService()`后必须在**5秒内**执行`startForeground()`,否则会抛出`ForegroundServiceDidNotStartInTimeException`导致应用崩溃[^3]。 2. **通知渠道要求** Android 8.0+ 必须创建通知渠道(NotificationChannel),否则通知不会显示[^5]。 3. **前台服务通知** 必须提供持续显示的通知,用户可通过此通知感知服务运行状态。 4. **权限声明** 必须声明`FOREGROUND_SERVICE`权限,否则在Android 9+会导致安全异常。 #### 4. 与`startService()`的区别 | 特性 | `startForegroundService()` | `startService()` | |---------------------|------------------------------------|--------------------------| | **适用版本** | Android 8.0+ (API 26+) | 所有版本 | | **后台限制** | 允许在后台启动服务 | Android 8.0+ 后台受限 | | **通知要求** | 必须显示前台通知 | 无需通知 | | **超时要求** | 5秒内必须调用startForeground() | 无超时限制 | | **系统资源优先级** | 较高(避免被系统杀死) | 较低(可能被系统回收) | [^1][^4] #### 5. 最佳实践建议 1. 在服务的`onStartCommand()`中立即调用`startForeground()` 2. 处理可能的工作线程阻塞情况 3. 在通知中提供停止服务的操作按钮 4. 使用`stopSelf()`或`stopForeground(true)`正确停止服务 --- ### 相关问题 1. Android 12 对前台服务有哪些新的限制和要求? 2. 如何在前台服务中更新通知内容? 3. 当应用处于后台时,启动前台服务会有什么限制? 4. 如何处理前台服务的生命周期和资源释放? 5. 为什么有时调用`startForeground()`后通知仍不显示?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

唐诺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值