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

我计划在后台使用服务。

Android文档指出

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

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

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

这有什么问题?


当前回答

现在在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
    }
}

其他回答

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

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方法已弃用,我仅将其用作解决此问题的临时方法。

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

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

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

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

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

我只是分享我对这个的评论。我不确定(100%告诉)上面的代码对我和其他人也不起作用,但有时我会遇到这个问题。假设我运行应用程序10次,那么可能会得到这个问题2到3次。

我尝试了所有的答案,但仍然没有解决这个问题。我已经实现了以上所有代码,并在不同的api级别(api级别26,28,29)和不同的移动设备(三星,小米,MIUI, Vivo, Moto, One Plus,华为等)上进行了测试,并得到了相同的以下问题。

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

我在谷歌开发人员网站上阅读了服务,一些其他博客和一些堆栈溢出问题,并得到了这个问题将发生在我们调用startforgroundservice()方法时,但当时服务还没有启动的想法。

在我的情况下,我已经停止服务后立即启动服务。下面是提示。

....//some other code
...// API level and other device auto star service condition is already set
stopService();
startService();
.....//some other code

在这种情况下,由于处理速度和内存不足,服务不会启动,但会调用startForegroundService()方法并触发异常。

为我工作:

new Handler().postDelayed(()->ContextCompat.startForegroundService(activity, new Intent(activity, ChatService.class)), 500);

我改变了代码,并设置了500毫秒的延迟来调用startService()方法,问题解决了。这不是完美的解决方案,因为这样应用程序的性能会下降。

注意: 这只适用于前台和后台服务。使用Bind服务时没有测试。 我之所以分享这些,是因为这是我解决这个问题的唯一方法。

请不要在onCreate()方法中调用任何StartForgroundServices,你必须在onStartCommand()中调用StartForground services,否则你将总是得到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);

    }
}

编辑:小心!start前台函数不能以0作为第一个参数,它将引发异常!这个例子包含错误的函数调用,将0更改为你自己的const,它不能为0或大于Max(Int32)