Context.startForegroundService()没有调用Service.startForeground()

387

我正在使用Android O OS上的Service类。

我计划在后台中使用Service

Android文档指出:

如果您的应用程序针对API级别26或更高级别,则系统对使用或创建后台服务施加限制,除非应用程序本身处于前台。如果应用程序需要创建前台服务,则应用程序应调用startForegroundService()

如果您使用startForegroundService(),则Service会抛出以下错误。

Context.startForegroundService() did not then call
Service.startForeground() 

这有什么问题吗?


请提供一个最小化且完整的可再现代码示例([mcve]),包括完整的Java堆栈跟踪和导致程序崩溃的代码。 - CommonsWare
6
API 26和27(27.0.3)仍存在此错误。受影响的Android版本为8.0和8.1。您可以通过在onCreate()和onStartCommand()中添加startForeground()来减少崩溃次数,但是对于某些用户仍然会发生崩溃。目前唯一解决它的方法是在您的build.gradle中将targetSdkVersion设置为25。 - Alex
3
FYI https://issuetracker.google.com/issues/76112072 - v1k
2
我有同样的问题。我解决了这个问题。我在这个主题中分享了我的实现 https://dev59.com/MlMI5IYBdhLWcg3wkMUh#56338570 - Beyazid
显示剩余6条评论
35个回答

12

在使用目标SDK为28或更高版本的Android 9设备上,如果不添加以下权限,则将始终发生异常:

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

4
我的应用拥有这个权限,但仍然出现了问题描述中提到的问题。 - JohnnyLambada
@JohnnyLambada 看起来只有活动可以启动前台服务,请尝试在后台启动服务。 - Shrdi

11

有很多答案,但在我的情况下都没有起作用。我已经像这样启动了一个服务。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    startForegroundService(intent);
} else {
    startService(intent);
}

而在我的服務中,在 onStartCommand 中

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
   Notification.Builder builder = new Notification.Builder(this, ANDROID_CHANNEL_ID)
      .setContentTitle(getString(R.string.app_name))
      .setContentText("SmartTracker Running")
      .setAutoCancel(true);
   Notification notification = builder.build();
   startForeground(NOTIFICATION_ID, notification);
} else {
   NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
      .setContentTitle(getString(R.string.app_name))
      .setContentText("SmartTracker is Running...")
      .setPriority(NotificationCompat.PRIORITY_DEFAULT)
      .setAutoCancel(true);
   Notification notification = builder.build();
   startForeground(NOTIFICATION_ID, notification);
}

别忘了将 NOTIFICATION_ID 设置为非零值

private static final String ANDROID_CHANNEL_ID = "com.xxxx.Location.Channel";
private static final int NOTIFICATION_ID = 555;

所以一切都很完美,但在8.1上仍然崩溃,原因如下。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
   stopForeground(true);
} else {
   stopForeground(true);
}

我已经使用“remove notification”来调用stop foreground,但是一旦通知被移除,服务就会成为后台服务,并且在Android O中无法从后台运行。这个服务是在接收推送后启动的。
所以,这个神奇的词是什么呢?
stopSelf();

到目前为止,如果有任何原因导致您的服务崩溃,请按照以上步骤操作并享受。


4
这段代码的作用是:如果当前设备的Android版本号大于或等于Oreo(Android 8.0),则执行第一个代码块中的语句;否则,执行第二个代码块中的语句。而无论哪种情况,都会执行stopForeground(true)这一语句。 - user3135923
我相信你的意思是将stopSelf();放在else块中,而不是stopForeground(true); - Justin Stanley
1
是的,@JustinStanley使用stopSelf(); - sandy
startForeground 应该在 onCreate 中调用吗? - Aba
我正在使用NotificationManager而不是Notification类。我该如何实现这个? - Prajwal Waingankar

10

3
不要为通知ID使用较高的数字,尝试使用一位数字作为ID。 - Marlon

9
请勿在 onCreate() 方法中调用任何 StartForgroundServices,您必须在 onStartCommand() 中创建工作线程后调用 StartForground 服务,否则您将始终收到 ANR(应用程序无响应)的错误提示。因此,请不要在 onStartCommand() 的主线程中编写复杂的逻辑。
public class Services extends Service {

    private static final String ANDROID_CHANNEL_ID = "com.xxxx.Location.Channel";
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Notification.Builder builder = new Notification.Builder(this, ANDROID_CHANNEL_ID)
                    .setContentTitle(getString(R.string.app_name))
                    .setContentText("SmartTracker Running")
                    .setAutoCancel(true);
            Notification notification = builder.build();
            startForeground(1, notification);
            Log.e("home_button","home button");
        } else {
            NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                    .setContentTitle(getString(R.string.app_name))
                    .setContentText("SmartTracker is Running...")
                    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                    .setAutoCancel(true);
            Notification notification = builder.build();
            startForeground(1, notification);
            Log.e("home_button_value","home_button_value");

        }
        return super.onStartCommand(intent, flags, startId);

    }
}

编辑:注意!startForeground函数不能将0作为第一个参数,否则会引发异常!此示例包含错误的函数调用,请将0更改为您自己的常量,该常量不能为0或大于Max(Int32)


8

我们的应用程序中大约有10个用户出现了这个crashlytics错误。

enter image description here

作为回应,Kimi Chiu指出,这个问题的主要原因是服务在推向前台之前就被停止了。但即使服务被销毁后,断言也没有停止。您可以尝试在调用startForegroundService后添加StopService来重现此问题-Kimi Chiu。
所以我进行了测试,并成功复现了该问题。
我采用的一个解决方案是让服务至少停留5秒钟,这样服务将会被推向前台。现在在测试过程中我无法再次复现这个问题。
private fun stopService() {

        lifecycleScope.launch {
            delay(5000L)
            
            try {
                stopForeground(true)
                isForeGroundService = false
                stopSelf()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

让我们看看问题是否可以在下一个版本中重现。

更新:) -> 这次没有与Context.startForegroundService()没有调用Service.startForeground()相关的问题。

enter image description here

Before/After comparission->

Before->输入图像说明 输入图像说明

After-> 输入图像说明


4
好的解决方案。很可怕我们必须这样做,而Google/AOSP却不能为我们做些更好的事情。 - swooby

7

我一直在研究这个问题,目前我发现如果我们有类似于以下代码,就可能会出现崩溃:

MyForegroundService.java

public class MyForegroundService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(...);
    }
}

MainActivity.java

Intent serviceIntent = new Intent(this, MyForegroundService.class);
startForegroundService(serviceIntent);
...
stopService(serviceIntent);

异常在以下代码块中抛出:

ActiveServices.java

private final void bringDownServiceLocked(ServiceRecord r) {
    ...
    if (r.fgRequired) {
        Slog.w(TAG_SERVICE, "Bringing down service while still waiting for start foreground: "
                  + r);
        r.fgRequired = false;
        r.fgWaiting = false;
        mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),
                    AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);
        mAm.mHandler.removeMessages(
                    ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
        if (r.app != null) {
            Message msg = mAm.mHandler.obtainMessage(
                ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
            msg.obj = r.app;
            msg.getData().putCharSequence(
                ActivityManagerService.SERVICE_RECORD_KEY, r.toString());
            mAm.mHandler.sendMessage(msg);
         }
    }
    ...
}

这个方法在执行 MyForegroundServiceonCreate() 之前被调用,因为 Android 将服务的创建安排在主线程处理程序上,但是 bringDownServiceLockedBinderThread 上被调用,这是一种竞争条件。这意味着 MyForegroundService 没有机会调用 startForeground,这将导致崩溃。
要解决这个问题,我们必须确保在调用 MyForegroundServiceonCreate() 之前不会调用 bringDownServiceLocked
public class MyForegroundService extends Service {

    private static final String ACTION_STOP = "com.example.MyForegroundService.ACTION_STOP";

    private final BroadcastReceiver stopReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            context.removeStickyBroadcast(intent);
            stopForeground(true);
            stopSelf();
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(...);
        registerReceiver(
            stopReceiver, new IntentFilter(ACTION_STOP));
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unregisterReceiver(stopReceiver);
    }

    public static void stop(Context context) {
        context.sendStickyBroadcast(new Intent(ACTION_STOP));
    }
}

通过使用粘性广播,我们确保广播不会丢失,并且stopReceiverMyForegroundServiceonCreate()中注册后尽快接收停止意图。此时,我们已经调用了startForeground(...)。我们还必须删除该粘性广播,以防止下次通知stopReceiver。
请注意,方法sendStickyBroadcast已被弃用,我仅将其用作暂时的解决方法来解决此问题。

当您想要停止服务时,应该调用它而不是context.stopService(serviceIntent)。 - makovkastar

5
在调用Service中的startForeground之后,如果在调用onCreate之前立即调用stopService,则此问题会在某些设备上崩溃。因此,我通过使用附加标志启动服务来解决此问题。
Intent intent = new Intent(context, YourService.class);
intent.putExtra("request_stop", true);
context.startService(intent);

在onStartCommand中添加了一个检查,以查看它是否已启动以停止:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    //call startForeground first
    if (intent != null) {
        boolean stopService = intent.getBooleanExtra("request_stop", false);
        if (stopService) {
            stopSelf();
        }
    }

    //Continue with the background task
    return START_STICKY;
}

顺便提一句,如果服务没有运行,它会首先启动服务,这会增加额外的负担。


你不需要创建一个服务来停止它,只需使用本地广播管理器)) - user924
不是另一个服务。我想停止的是同一个服务! - Gaurav Singla
亲爱的用户924,如果我必须停止服务,就没有必要再进行广播了,我可以直接调用context.stopService()。以上答案是解决在三星设备上停止服务时出现的问题的技巧,即在它的onStartCommand实际被调用之前停止该服务。 - Gaurav Singla
onCreate 在 onStartCommand 之前被调用,我认为你这里不是反过来了吗? - Emil
@Emil 你是正确的。让onCreate方法完成并从onStartCommand方法中停止服务的想法是正确的。这样操作系统认为已经调用了onCreate方法,通知也已经展示并最终被停止,不会抛出异常。 - Gaurav Singla
显示剩余2条评论

5

根据谷歌关于Android 12行为变更的文档:

To provide a streamlined experience for short-running foreground services on Android 12, the system can delay the display of foreground service notifications by 10 seconds for certain foreground services. This change gives short-lived tasks a chance to complete before their notifications appear.

解决方案:在使用Context.startForegroundService()启动服务的情况下,在onCreate()中调用startForeground()函数。

3
这个问题对于Android 12来说太过陈旧。 - user924

4

我也遇到了同样的问题,花了些时间找到了解决方案,你可以尝试下面的代码。如果你在使用Service,那么把这段代码放在onCreate中;如果你在使用Intent Service,那么把这段代码放在onHandleIntent中。

if (Build.VERSION.SDK_INT >= 26) {
        String CHANNEL_ID = "my_app";
        NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                "MyApp", NotificationManager.IMPORTANCE_DEFAULT);
        ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("")
                .setContentText("").build();
        startForeground(1, notification);
    }

4
问题与Android O API 26相关
如果您立即停止服务(因此您的服务实际上并没有真正运行(措辞/理解),并且您远低于ANR间隔),您仍然需要在stopSelf之前调用startForeground。

https://plus.google.com/116630648530850689477/posts/L2rn4T6SAJ5

尝试了这种方法,但仍然出现错误:

if (Util.SDK_INT > 26) {
    mContext.startForegroundService(playIntent);
} else {
    mContext.startService(playIntent);
}

我使用这个直到错误解决。
mContext.startService(playIntent);

4
应该是 Util.SDK_INT >= 26,而不仅仅是更大。 - Andris
你的意思是startService在所有SDK版本中仍然有效吗? - Emil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接