Android:停止前台服务导致应用程序崩溃

3

前提条件: 作为我的应用程序的要求之一,我需要确保该应用程序在后台时不会被Android系统关闭(终止)。为此,我实现了前台服务(Foreground service),即使我不在后台执行任何实际进程,只是维护应用程序的状态。除一个问题外,一切都运行得很好,这个问题我不太清楚如何解决。

问题: 有时候(目前只出现一次),我收到这个异常:

android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground(): 

当我试图停止前台服务,但实际上它并没有启动时,会抛出此异常。

因此,我的问题是 - 有没有一种正确的方式来停止前台服务,在实际停止之前确保它没有在运行? 目前我发现可以为我的服务创建静态实例,并在停止服务之前将其与 null 进行比较,或获取当前运行的所有服务列表。但所有这些都像是一些“hack”解决方法。

以下是一些代码: MyForegroundService:

public class ForegroundService extends Service {

public static final int NOTIFICATION_ID = 1;
public static final String CHANNEL_ID = "SessionForegroundServiceChannel";
public static final String ACTION_FOREGROUND_START = "ACTION_FOREGROUND_START";
public static final String ACTION_FOREGROUND_STOP = "ACTION_FOREGROUND_STOP";

public static void startForegroundService(Context context) {
    Intent intent = new Intent(context, ForegroundService.class);
    intent.setAction(ForegroundService.ACTION_FOREGROUND_START);
    ContextCompat.startForegroundService(context, intent);
}

public static void stopForegroundService(Context context) {
    Intent intent = new Intent(context, ForegroundService.class);
    intent.setAction(ForegroundService.ACTION_FOREGROUND_STOP);
    ContextCompat.startForegroundService(context, intent);
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if (ACTION_FOREGROUND_START.equals(intent.getAction())) {
        createNotificationChannel();
        Intent stopForegroundIntent = new Intent(this, ForegroundServiceBroadcastReceiver.class);
        PendingIntent pendingLogoutIntent = PendingIntent.getBroadcast(this,
                0, stopForegroundIntent, PendingIntent.FLAG_UPDATE_CURRENT);

        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
                        ? null
                        : getString(R.string.app_short_name))
                .setContentText(getString(R.string.foreground_description))
                .setColor(getResources().getColor(R.color.color))
                .setSmallIcon(R.drawable.ic_notification)
                .addAction(R.drawable.ic_logout, getString(R.string.logout), pendingLogoutIntent)
                .build();
        startForeground(NOTIFICATION_ID, notification);
    } else if (ACTION_FOREGROUND_STOP.equals(intent.getAction())) {
        stopForeground(true);
        stopSelf();
    }
    return START_STICKY;
}

private void createNotificationChannel() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationChannel serviceChannel = new NotificationChannel(
                CHANNEL_ID,
                getString(R.string.app_name),
                NotificationManager.IMPORTANCE_LOW
        );
        NotificationManager manager = getSystemService(NotificationManager.class);
        manager.createNotificationChannel(serviceChannel);
    }
}


@Nullable
@Override
public IBinder onBind(Intent intent) {
    return null;
}

}

<service
        android:name=".ui.ForegroundService"
        android:exported="false"
        android:stopWithTask="true"/>

我还有 BroadcastReceiver 和 EventBus 来监听一些事件并根据这些事件停止前台服务。

你们能帮忙吗?


如果您调用 ContextCompat.startForegroundService(Context, Intent),则您的服务必须在3秒内响应 startForeground(Int, Notification),即使您的意图是停止服务处于前台。 - Pawel
是的,我已经在官方文档中阅读过了。但目前,如果服务正在运行并且我停止了它,我实际上不会再次调用startForeground。就像您可以从代码中看到的那样:if (ACTION_FOREGROUND_START.equals(intent.getAction())) { startForeground(NOTIFICATION_ID, notification); } else if (ACTION_FOREGROUND_STOP.equals(intent.getAction())) { stopForeground(true); stopSelf(); }如果按照您建议的做法,我将会陷入无限循环之中。我不需要另一个通知来停止前台。我还有什么遗漏吗? - idk.the.name
如果您在调用Context.startForegroundService后的3秒内没有调用Service.startForeground,则会出现此异常。就是这样。 - Pawel
如果我停止前台,我就不需要调用Service.startForeground。为此,有stopForeground(boolean)方法。只有在服务未启动时才需要调用Service.startForeground。但问题是,如何确保服务已经启动。 - idk.the.name
@eXXXc1ted 执行 stopForegroundstopSelf() 函数的原因是什么?您可以尝试删除 stopSelf - keshav kowshik
1
@Keshav1234,以下是文档中的内容:stopForeground 将此服务从前台状态中移除,允许在需要更多内存时将其终止。这不会停止服务运行(要停止服务运行,请使用stopSelf()或相关方法),只是将其从前台状态中移出。 - idk.the.name
1个回答

4

让我补充一下@Pawel的评论:

如果您在调用Context.startForegroundService后3秒内没有调用Service.startForeground,则会出现此异常,就是这样。

下面是完整解决方案的代码示例:

当需要停止前台服务时,需要执行以下操作(伪代码):

if (action == START_FOREGROUND) {
    ...
    startForeground(NOTIFICATION_ID, notification);
} else if (action == STOP_FOREGROUND) {
    startForeground(NOTIFICATION_ID, closeNotification); //in case it wasn't started before
    stopForeground(true);
    stopSelf();
}

即使文档中没有明确说明,也不太显而易见,当你需要停止前台时,你需要在停止前启动前台(如果它没有被启动)。感谢@Pawel的提示。

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