我在Android O操作系统上使用服务类。

我计划在后台使用服务。

Android文档指出

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

如果使用startForegroundService(),服务抛出以下错误。

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

这有什么问题?


当前回答

如果你在调用service . start前台(…)之前调用Context.startForegroundService(…),然后再调用Context.stopService(…),你的应用程序会崩溃。

我这里有清晰的复制品前景服务api26

我在谷歌问题跟踪器上打开了一个bug

这方面的几个bug已经被打开和关闭不会修复。

希望我的与明确的复制步骤将使削减。

谷歌团队提供的信息

谷歌问题跟踪器评论36

这不是一个框架错误;这是故意的。如果应用程序使用startForegroundService()启动一个服务实例,它必须将该服务实例转换到前台状态并显示通知。如果服务实例在start前台()被调用之前就停止了,那么这个承诺就没有实现:这是应用程序中的一个bug。

re# 31,发布一个其他应用程序可以直接启动的服务从根本上来说是不安全的。您可以通过将该服务的所有启动操作都视为需要startForeground()来减轻这一点,尽管这显然不是您想要的。

谷歌问题跟踪器评论56

这里有几种不同的情况会导致相同的结果。

完全的语义问题是,用startForegroundService()启动一些东西,但忽略了通过startForeground()实际将其转换到前台,这只是一个语义问题。这被故意视为应用程序漏洞。在将服务转移到前台之前停止服务是应用程序错误。这是OP的关键,也是为什么这个问题被标记为“按预期工作”。

然而,也有关于这个问题的虚假检测的问题。这是一个真正的问题,尽管它是分开跟踪这个特殊的bug跟踪器问题。我们对抱怨不是充耳不闻。

其他回答

以下是谷歌在Android 8.0上的行为变化:

系统允许应用程序调用Context.startForegroundService(),即使应用程序处于后台。但是,应用程序必须在服务创建后的5秒内调用该服务的startForeground()方法。

解决方案: 在onCreate()中为你使用的服务调用start前台()

请参见:Android 8.0 (Oreo)的后台执行限制

我有一个小部件,当设备处于清醒状态时,它会相对频繁地进行更新,我在短短几天内就看到了数千次崩溃。

问题触发

我甚至在我的Pixel 3 XL上也注意到了这个问题,而我本以为这款设备负载并不大。所有代码路径都被startForeground()覆盖。但后来我意识到,在很多情况下,我的服务可以很快地完成工作。我认为触发我的应用程序的原因是,在系统真正抽出时间显示通知之前,服务已经结束。

解决方案/解决方案

我摆脱了所有的崩溃。我所做的就是删除对stopSelf()的调用。(我正在考虑延迟停止,直到我非常确定通知被显示,但我不希望用户看到通知,如果它不是必要的。)当服务空闲一分钟或系统正常销毁它而不抛出任何异常时。

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

我调用ContextCompat。startForegroundService(this, intent)然后启动服务

在服务onCreate中

 @Override
 public void onCreate() {
        super.onCreate();

        if (Build.VERSION.SDK_INT >= 26) {
            String CHANNEL_ID = "my_channel_01";
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);

            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                    .setContentTitle("")
                    .setContentText("").build();

            startForeground(1, notification);
        }
}

https://developer.android.com/reference/android/content/Context.html startForegroundService (android.content.Intent)

类似于startService(Intent),但是有一个隐含的承诺 Service将调用start前台(int, android.app.Notification)一次 它开始运行。该服务被赋予了相当的时间 到ANR区间做这个,否则系统会 自动停止服务并声明应用程序ANR。 与普通的startService(Intent)不同,这个方法可以在 任何时候,不管托管服务的应用程序是否在 前景的国家。

一定要打电话给服务处。在onCreate()上的start前台(int, android.app.Notification),所以你确保它将被调用..如果你有任何条件,可能会阻止你这样做,那么你最好使用正常的Context.startService(意图)并调用服务。start前台(int, android.app.Notification)你自己。

似乎Context.startForegroundService()添加了一个看门狗来确保你调用了服务。start前台(int, android.app.Notification)在它被销毁之前…

我一直在研究这个问题,这是我目前为止的发现。如果我们有类似这样的代码,就会发生崩溃:

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);
         }
    }
    ...
}

这个方法在MyForegroundService的onCreate()之前执行,因为Android在主线程处理程序上安排了服务的创建,但在BinderThread上调用了bringdownservicelock,这是一个竞争条件。这意味着MyForegroundService没有机会调用start前台,这将导致崩溃。

为了解决这个问题,我们必须确保在MyForegroundService的onCreate()之前不调用bringdownservicellocked。

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));
    }
}

通过使用粘性广播,我们确保广播不会丢失,stopReceiver在MyForegroundService的onCreate()中注册后立即接收到停止意图。此时我们已经调用了startForeground(…)。我们还必须删除这个粘滞的广播,以防止下一次stopReceiver被通知。

请注意,sendStickyBroadcast方法已弃用,我仅将其用作解决此问题的临时方法。