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

我计划在后台使用服务。

Android文档指出

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

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

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

这有什么问题?


当前回答

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

问题触发

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

解决方案/解决方案

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

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

其他回答

现在在Android O,你可以设置后台限制如下

调用服务类的服务

Intent serviceIntent = new Intent(SettingActivity.this,DetectedService.class);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
    SettingActivity.this.startForegroundService(serviceIntent);
} else {
    startService(serviceIntent);
}

服务类应该是这样的

public class DetectedService extends Service { 
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        int NOTIFICATION_ID = (int) (System.currentTimeMillis()%10000);
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForeground(NOTIFICATION_ID, new Notification.Builder(this).build());
        }


        // Do whatever you want to do here
    }
}

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

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

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

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

我只是检查PendingIntent null或或不是之前调用 context.startForegroundService (service_intent)功能。

这对我很有用

PendingIntent pendingIntent=PendingIntent.getBroadcast(context,0,intent,PendingIntent.FLAG_NO_CREATE);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && pendingIntent==null){
            context.startForegroundService(service_intent);
        }
        else
        {
            context.startService(service_intent);
        }
}

Android O API 26的问题

如果你立即停止服务(所以你的服务实际上并没有真正运行(措辞/理解),并且你在ANR间隔之下,你仍然需要在stopSelf之前调用start前台

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

尝试了这种方法,但它仍然创建一个错误:-

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

我正在使用这个,直到错误被解决

mContext.startService(playIntent);

因为每个来这里的人都遭受着同样的痛苦,我想分享我的解决方法,之前没有人尝试过(在这个问题中)。我可以向您保证,它是工作的,甚至在一个停止的断点上确认这个方法。

问题是调用服务。start前台(id, notification)来自服务本身,对吧?不幸的是,Android框架不保证调用服务。Service.onCreate()中的start前台(id, notification)在5秒内,但无论如何都会抛出异常,所以我想出了这种方法。

Bind the service to a context with a binder from the service before calling Context.startForegroundService() If the bind is successful, call Context.startForegroundService() from the service connection and immediately call Service.startForeground() inside the service connection. IMPORTANT NOTE: Call the Context.bindService() method inside a try-catch because in some occasions the call can throw an exception, in which case you need to rely on calling Context.startForegroundService() directly and hope it will not fail. An example can be a broadcast receiver context, however getting application context does not throw an exception in that case, but using the context directly does.

这甚至可以在我绑定服务后和触发“startForeground”调用之前等待断点时工作。等待3-4秒不会触发异常,而5秒后会抛出异常。(如果设备不能在5秒内执行两行代码,那么就该把它扔进垃圾桶了。)

因此,首先创建一个服务连接。

// Create the service connection.
ServiceConnection connection = new ServiceConnection()
{
    @Override
    public void onServiceConnected(ComponentName name, IBinder service)
    {
        // The binder of the service that returns the instance that is created.
        MyService.LocalBinder binder = (MyService.LocalBinder) service;

        // The getter method to acquire the service.
        MyService myService = binder.getService();

        // getServiceIntent(context) returns the relative service intent 
        context.startForegroundService(getServiceIntent(context));

        // This is the key: Without waiting Android Framework to call this method
        // inside Service.onCreate(), immediately call here to post the notification.
        myService.startForeground(myNotificationId, MyService.getNotification());

        // Release the connection to prevent leaks.
        context.unbindService(this);
    }

    @Override
    public void onBindingDied(ComponentName name)
    {
        Log.w(TAG, "Binding has dead.");
    }

    @Override
    public void onNullBinding(ComponentName name)
    {
        Log.w(TAG, "Bind was null.");
    }

    @Override
    public void onServiceDisconnected(ComponentName name)
    {
        Log.w(TAG, "Service is disconnected..");
    }
};

在服务内部,创建一个返回服务实例的绑定器。

public class MyService extends Service
{
    public class LocalBinder extends Binder
    {
        public MyService getService()
        {
            return MyService.this;
        }
    }

    // Create the instance on the service.
    private final LocalBinder binder = new LocalBinder();

    // Return this instance from onBind method.
    // You may also return new LocalBinder() which is
    // basically the same thing.
    @Nullable
    @Override
    public IBinder onBind(Intent intent)
    {
        return binder;
    }
}

然后,尝试从该上下文中绑定服务。如果成功,它将从您正在使用的服务连接调用serviceconnection . onserviceconnconnected()方法。然后,处理上面所示代码中的逻辑。示例代码如下所示:

// Try to bind the service
try
{
     context.bindService(getServiceIntent(context), connection,
                    Context.BIND_AUTO_CREATE);
}
catch (RuntimeException ignored)
{
     // This is probably a broadcast receiver context even though we are calling getApplicationContext().
     // Just call startForegroundService instead since we cannot bind a service to a
     // broadcast receiver context. The service also have to call startForeground in
     // this case.
     context.startForegroundService(getServiceIntent(context));
}

它似乎在我开发的应用程序上工作,所以当你尝试时它应该也能工作。