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

我计划在后台使用服务。

Android文档指出

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

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

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

这有什么问题?


当前回答

我有一个解决这个问题的办法。我已经在自己的应用(DAU超过30万)中验证了这一修复方法,它至少可以减少95%的这种崩溃,但仍然不能100%避免这个问题。

即使您确保在服务启动后调用start前台(),也会发生此问题。这可能是因为在很多情况下,服务创建和初始化过程已经花费了超过5秒的时间,那么无论何时何地调用startForeground()方法,这个崩溃都是不可避免的。

我的解决方案是确保start前台()将在startForegroundService()方法后的5秒内执行,无论您的服务需要创建和初始化多长时间。下面是详细的解决方案。

Do not use startForegroundService at the first place, use bindService() with auto_create flag. It will wait for the service initialization. Here is the code, my sample service is MusicService: final Context applicationContext = context.getApplicationContext(); Intent intent = new Intent(context, MusicService.class); applicationContext.bindService(intent, new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder binder) { if (binder instanceof MusicBinder) { MusicBinder musicBinder = (MusicBinder) binder; MusicService service = musicBinder.getService(); if (service != null) { // start a command such as music play or pause. service.startCommand(command); // force the service to run in foreground here. // the service is already initialized when bind and auto_create. service.forceForeground(); } } applicationContext.unbindService(this); } @Override public void onServiceDisconnected(ComponentName name) { } }, Context.BIND_AUTO_CREATE); Then here is MusicBinder implementation: /** * Use weak reference to avoid binder service leak. */ public class MusicBinder extends Binder { private WeakReference<MusicService> weakService; /** * Inject service instance to weak reference. */ public void onBind(MusicService service) { this.weakService = new WeakReference<>(service); } public MusicService getService() { return weakService == null ? null : weakService.get(); } } The most important part, MusicService implementation, forceForeground() method will ensure that startForeground() method is called just after service start: public class MusicService extends MediaBrowserServiceCompat { ... private final MusicBinder musicBind = new MusicBinder(); ... @Override public IBinder onBind(Intent intent) { musicBind.onBind(this); return musicBind; } ... public void forceForeground() { // API lower than 26 do not need this work around. if (Build.VERSION.SDK_INT >= 26) { Notification notification = mNotificationHandler.createNotification(this); // call startForeground just after service start. startForeground(Constants.NOTIFICATION_ID, notification); } } } If you want to run the step 1 code snippet in a pending intent, such as if you want to start a foreground service in a widget (a click on widget button) without opening your app, you can wrap the code snippet in a broadcast receiver, and fire a broadcast event instead of start service command.

仅此而已。希望能有所帮助。祝你好运。

其他回答

在我的情况下,通知ID我传递给startForeground方法是'0',因为这个错误来了。

startForeground(0, notification); //This is wrong.

startForeground(1, notification); //This is right.

可以使用0以外的任何整数。

服务

class TestService : Service() {

    override fun onCreate() {
        super.onCreate()
        Log.d(TAG, "onCreate")

        val nBuilder = NotificationCompat.Builder(this, "all")
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentTitle("TestService")
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
        startForeground(1337, nBuilder.build())
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val rtn = super.onStartCommand(intent, flags, startId)

        if (intent?.action == STOP_ACTION) {
            Log.d(TAG, "onStartCommand -> STOP")
            stopForeground(true)
            stopSelf()
        } else {
            Log.d(TAG, "onStartCommand -> START")
        }

        return rtn
    }

    override fun onDestroy() {
        Log.d(TAG, "onDestroy")
        super.onDestroy()
    }

    override fun onBind(intent: Intent?): IBinder? = null

    companion object {

        private val TAG = "TestService"
        private val STOP_ACTION = "ly.zen.test.TestService.ACTION_STOP"

        fun start(context: Context) {
            ContextCompat.startForegroundService(context, Intent(context, TestService::class.java))
        }

        fun stop(context: Context) {
            val intent = Intent(context, TestService::class.java)
            intent.action = STOP_ACTION
            ContextCompat.startForegroundService(context, intent)
        }

    }

}

测试人员

val nChannel = NotificationChannel("all", "All", NotificationManager.IMPORTANCE_NONE)
val nManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nManager.createNotificationChannel(nChannel)

start_test_service.setOnClickListener {
    TestService.start(this@MainActivity)
    TestService.stop(this@MainActivity)
}

结果

D/TestService: onCreate
D/TestService: onStartCommand -> START
D/TestService: onStartCommand -> STOP
D/TestService: onDestroy

我在Pixel 3和Android 11中遇到了一个问题,当我的服务运行得很短时,前台通知没有被取消。

在stopForeground() stopSelf()之前添加100ms的延迟似乎有所帮助。

人们在这里写道,stopprospect()应该在stopSelf()之前被调用。我不能确认,但我猜它不会费心去做。

public class AService extends Service {

@Override
public void onCreate() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        startForeground(
            getForegroundNotificationId(),
            channelManager.buildBackgroundInfoNotification(getNotificationTitle(), getNotificationText()),
            ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC);
    } else {
        startForeground(getForegroundNotificationId(),
            channelManager.buildBackgroundInfoNotification(getNotificationTitle(), getNotificationText())
        );
    }

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

    if (hasQueueMoreItems()) {
        startWorkerThreads();
    } else {
        stopForeground(true);
        stopSelf();
    }
    return START_STICKY;
}

private class WorkerRunnable implements Runnable {

    @Override
    public void run() {

        while (getItem() != null && !isLoopInterrupted) {
            doSomething(getItem())   
        }

        waitALittle();
        stopForeground(true);
        stopSelf();
    }

    private void waitALittle() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
}

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

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

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

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

即使在Service中调用start前台之后,如果我们在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;
}

附注:如果服务没有运行,它将首先启动服务,这是一种开销。