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

我计划在后台使用服务。

Android文档指出

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

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

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

这有什么问题?


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

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

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

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


我调用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)在它被销毁之前…


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

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

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

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

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

谷歌团队提供的信息

谷歌问题跟踪器评论36

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

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

谷歌问题跟踪器评论56

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

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

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


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

我也面临着同样的问题,花时间找到了一个解决方案,你可以尝试下面的代码。如果你使用服务,然后把这段代码放在onCreate,否则你使用意图服务,然后把这段代码放在onHandleIntent。

if (Build.VERSION.SDK_INT >= 26) {
        String CHANNEL_ID = "my_app";
        NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                "MyApp", NotificationManager.IMPORTANCE_DEFAULT);
        ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("")
                .setContentText("").build();
        startForeground(1, notification);
    }

提醒一下,我在这上面浪费了太多时间。我一直得到这个异常,即使我调用start前台(..)作为onCreate(..)的第一件事。 最后,我发现这个问题是由于使用NOTIFICATION_ID = 0引起的。使用任何其他值似乎都可以解决这个问题。


此错误也发生在Android 8+服务时。start前台(int id, Notification Notification)在id设置为0时被调用。

id int:该通知的标识符,根据NotificationManager。通知(int、通知);一定不是0。


为什么这个问题会发生,因为Android框架不能保证你的服务在5秒内启动,但另一方面,框架确实有严格的限制前台通知必须在5秒内触发,而不检查框架是否试图启动服务。

这绝对是一个框架问题,但并不是所有面临这个问题的开发人员都尽力了:

一个通知必须同时在onCreate和onStartCommand中,因为如果你的服务已经被创建并且你的activity试图再次启动它,onCreate将不会被调用。 通知ID不能为0,否则即使原因不同,也会发生相同的崩溃。 在start前台之前不能调用stopSelf。

有了以上3个,这个问题可以减少一点,但仍然不是一个解决方案,真正的解决方案或让我们说的变通办法是把你的目标sdk版本降低到25。

请注意,Android P很可能仍然会有这个问题,因为谷歌甚至拒绝理解发生了什么,不相信这是他们的错,阅读#36和#56以获得更多信息


很多答案,但没有一个对我有用。

我已经开始这样服务了。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    startForegroundService(intent);
} else {
    startService(intent);
}

在我的服务在onStartCommand

    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(NOTIFICATION_ID, notification);
    } 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(NOTIFICATION_ID, notification);
    }

不要忘记将NOTIFICATION_ID设置为非零

private static final String ANDROID_CHANNEL_ID = "com.xxxx.Location.Channel";
private static final int NOTIFICATION_ID = 555;

所以一切都是完美的,但仍然崩溃在8.1,所以原因如下。

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

我已经调用停止前台删除通知,但一旦通知删除服务成为后台和后台服务不能从后台运行在android O。在收到推送后启动。

神奇的词是

   stopSelf();

到目前为止,任何原因你的服务崩溃遵循以上所有步骤,并享受。


即使在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;
}

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


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

我只是检查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);
        }
}

我有一个解决这个问题的办法。我已经在自己的应用(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.

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


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

问题触发

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

解决方案/解决方案

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

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

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

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


请不要在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)


我已经修复了用startService(intent)而不是context . start前台()启动服务的问题,并在super.OnCreate()之后立即调用start前台()。此外,如果您在引导时启动服务,您可以启动在引导广播时启动服务的Activity。虽然这不是一个永久的解决方案,但它是有效的。


只要在创建Service或IntentService后立即调用start前台方法。是这样的:

import android.app.Notification;
public class AuthenticationService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(1,new Notification());
    }
}

服务

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

更新onStartCommand中的数据(…)

上绑定(...

与onCreate(…)相比,onBind(…)是一个更好的生命周期事件来初始化start前台,因为onBind(…)传递了一个Intent,其中可能包含初始化服务所需的Bundle中的重要数据。然而,这是不必要的,因为onStartCommand(…)是在服务第一次创建或后续多次调用时调用的。

onStartCommand(...)

onStartCommand(…)中的start前台是很重要的,以便在服务创建后更新它。

当ContextCompat.startForegroundService(…)在服务创建后被调用时,onBind(…)和onCreate(…)没有被调用。因此,更新后的数据可以通过Intent Bundle传递到onStartCommand(…)来更新服务中的数据。

样本

我使用这个模式在Coinverse加密货币新闻应用程序中实现PlayerNotificationManager。

Activity / Fragment.kt

context?.bindService(
        Intent(context, AudioService::class.java),
        serviceConnection, Context.BIND_AUTO_CREATE)
ContextCompat.startForegroundService(
        context!!,
        Intent(context, AudioService::class.java).apply {
            action = CONTENT_SELECTED_ACTION
            putExtra(CONTENT_SELECTED_KEY, contentToPlay.content.apply {
                audioUrl = uri.toString()
            })
        })

AudioService.kt

private var uri: Uri = Uri.parse("")

override fun onBind(intent: Intent?) =
        AudioServiceBinder().apply {
            player = ExoPlayerFactory.newSimpleInstance(
                    applicationContext,
                    AudioOnlyRenderersFactory(applicationContext),
                    DefaultTrackSelector())
        }

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    intent?.let {
        when (intent.action) {
            CONTENT_SELECTED_ACTION -> it.getParcelableExtra<Content>(CONTENT_SELECTED_KEY).also { content ->
                val intentUri = Uri.parse(content.audioUrl)
                // Checks whether to update Uri passed in Intent Bundle.
                if (!intentUri.equals(uri)) {
                    uri = intentUri
                    player?.prepare(ProgressiveMediaSource.Factory(
                            DefaultDataSourceFactory(
                                    this,
                                    Util.getUserAgent(this, getString(app_name))))
                            .createMediaSource(uri))
                    player?.playWhenReady = true
                    // Calling 'startForeground' in 'buildNotification(...)'.          
                    buildNotification(intent.getParcelableExtra(CONTENT_SELECTED_KEY))
                }
            }
        }
    }
    return super.onStartCommand(intent, flags, startId)
}

// Calling 'startForeground' in 'onNotificationStarted(...)'.
private fun buildNotification(content: Content): Unit? {
    playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
            this,
            content.title,
            app_name,
            if (!content.audioUrl.isNullOrEmpty()) 1 else -1,
            object : PlayerNotificationManager.MediaDescriptionAdapter {
                override fun createCurrentContentIntent(player: Player?) = ...
                override fun getCurrentContentText(player: Player?) = ...
                override fun getCurrentContentTitle(player: Player?) = ...
                override fun getCurrentLargeIcon(player: Player?,
                                                 callback: PlayerNotificationManager.BitmapCallback?) = ...
            },
            object : PlayerNotificationManager.NotificationListener {
                override fun onNotificationStarted(notificationId: Int, notification: Notification) {
                    startForeground(notificationId, notification)
                }
                override fun onNotificationCancelled(notificationId: Int) {
                    stopForeground(true)
                    stopSelf()
                }
            })
    return playerNotificationManager.setPlayer(player)
}

我知道,已经发布了太多的答案,但事实是——startForegroundService不能在应用程序级别上被修复,你应该停止使用它。谷歌建议在调用Context# start前台服务()后5秒内使用service# start前台()API,这并不是应用程序总能做到的。

Android同时运行很多进程,并不能保证Looper会在5秒内调用你的目标服务start前台()。如果您的目标服务在5秒内没有收到调用,那么您就不走运了,您的用户将遇到ANR情况。在你的堆栈跟踪中,你会看到这样的东西:

Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{1946947 u0 ...MessageService}

main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x763e01d8 self=0x7d77814c00
  | sysTid=11171 nice=-10 cgrp=default sched=0/0 handle=0x7dfe411560
  | state=S schedstat=( 1337466614 103021380 2047 ) utm=106 stm=27 core=0 HZ=100
  | stack=0x7fd522f000-0x7fd5231000 stackSize=8MB
  | held mutexes=
  #00  pc 00000000000712e0  /system/lib64/libc.so (__epoll_pwait+8)
  #01  pc 00000000000141c0  /system/lib64/libutils.so (android::Looper::pollInner(int)+144)
  #02  pc 000000000001408c  /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+60)
  #03  pc 000000000012c0d4  /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44)
  at android.os.MessageQueue.nativePollOnce (MessageQueue.java)
  at android.os.MessageQueue.next (MessageQueue.java:326)
  at android.os.Looper.loop (Looper.java:181)
  at android.app.ActivityThread.main (ActivityThread.java:6981)
  at java.lang.reflect.Method.invoke (Method.java)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:493)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1445)

As I understand, Looper has analyzed the queue here, found an "abuser" and simply killed it. The system is happy and healthy now, while developers and users are not, but since Google limits their responsibilities to the system, why should they care about the latter two? Apparently they don't. Could they make it better? Of course, e.g. they could've served "Application is busy" dialog, asking a user to make a decision about waiting or killing the app, but why bother, it's not their responsibility. The main thing is that the system is healthy now.

根据我的观察,这种情况发生得相对较少,在我的案例中,每个月大约有1个用户崩溃。复制它是不可能的,即使它被复制了,你也无法永久性地修复它。

在这个线程中有一个很好的建议,使用“bind”而不是“start”,然后当服务准备好时,处理onserviceconnconnected,但同样,这意味着根本不使用startForegroundService调用。

我认为,从谷歌方面正确和诚实的行动是告诉每个人startForegourndServcie有缺陷,不应该使用。

问题仍然存在:用什么来代替?幸运的是,现在有了JobScheduler和JobService,它们是前台服务的更好选择。这是一个更好的选择,因为:

当作业正在运行时,系统代表您的作业持有一个wakelock 因此,您不需要做任何保证 设备在作业期间保持清醒状态。

这意味着你不再需要关心如何处理wakelocks,这就是为什么它与前台服务没有什么不同。从实现的角度来看,JobScheduler不是你的服务,它是一个系统的服务,假设它将正确地处理队列,谷歌永远不会终止自己的子服务:)

三星在其附件协议(SAP)中从startForegroundService切换到JobScheduler和JobService。当像智能手表这样的设备需要与手机这样的主机进行通信时,这是非常有用的,因为这项工作确实需要通过应用程序的主线程与用户进行交互。由于作业是由调度器发布到主线程的,所以这是可能的。不过你应该记住,作业是在主线程上运行的,并将所有繁重的工作卸载给其他线程和异步任务。

此服务执行运行在您的处理器上的每个传入作业 应用程序主线程。这意味着你必须卸下你的 执行逻辑到你选择的另一个线程/处理器/AsyncTask

切换到JobScheduler/JobService的唯一缺陷是需要重构旧代码,这并不有趣。我花了两天时间来使用三星的新SAP实现。我会看我的坠机报告,如果再次看到坠机,我会告诉你。从理论上讲,这是不应该发生的,但总有一些细节我们可能没有意识到。

更新 Play Store不再报告崩溃。这意味着JobScheduler/JobService不存在这样的问题,切换到这个模型是彻底摆脱startForegroundService问题的正确方法。我希望谷歌/Android会读到它,并最终为大家提供评论/建议/官方指导。

更新2

对于那些使用SAP并询问SAP V2如何使用JobService的人,下面将给出解释。

在你的自定义代码中,你需要初始化SAP(它是Kotlin):

SAAgentV2.requestAgent(App.app?.applicationContext, 
   MessageJobs::class.java!!.getName(), mAgentCallback)

现在你需要反编译三星的代码,看看里面发生了什么。在SAAgentV2中,看一下requestAgent实现和下面这行代码:

SAAgentV2.d var3 = new SAAgentV2.d(var0, var1, var2);

where d defined as below

private SAAdapter d;

现在转到SAAdapter类,并找到onServiceConnectionRequested函数,该函数使用以下调用调度作业:

SAJobService.scheduleSCJob(SAAdapter.this.d, var11, var14, var3, var12); 

SAJobService只是Android'd JobService的一个实现,这是一个作业调度:

private static void a(Context var0, String var1, String var2, long var3, String var5, SAPeerAgent var6) {
    ComponentName var7 = new ComponentName(var0, SAJobService.class);
    Builder var10;
    (var10 = new Builder(a++, var7)).setOverrideDeadline(3000L);
    PersistableBundle var8;
    (var8 = new PersistableBundle()).putString("action", var1);
    var8.putString("agentImplclass", var2);
    var8.putLong("transactionId", var3);
    var8.putString("agentId", var5);
    if (var6 == null) {
        var8.putStringArray("peerAgent", (String[])null);
    } else {
        List var9;
        String[] var11 = new String[(var9 = var6.d()).size()];
        var11 = (String[])var9.toArray(var11);
        var8.putStringArray("peerAgent", var11);
    }

    var10.setExtras(var8);
    ((JobScheduler)var0.getSystemService("jobscheduler")).schedule(var10.build());
}

如你所见,这里的最后一行使用Android'd JobScheduler来获取这个系统服务并调度作业。

在requestAgent调用中,我们传递了mAgentCallback,这是一个回调函数,它将在重要事件发生时接收控制。这是如何定义回调在我的应用程序:

private val mAgentCallback = object : SAAgentV2.RequestAgentCallback {
    override fun onAgentAvailable(agent: SAAgentV2) {
        mMessageService = agent as? MessageJobs
        App.d(Accounts.TAG, "Agent " + agent)
    }

    override fun onError(errorCode: Int, message: String) {
        App.d(Accounts.TAG, "Agent initialization error: $errorCode. ErrorMsg: $message")
    }
}

MessageJobs是我实现的一个类,用于处理来自三星智能手表的所有请求。这不是完整的代码,只是一个骨架:

class MessageJobs (context:Context) : SAAgentV2(SERVICETAG, context, MessageSocket::class.java) {


    public fun release () {

    }


    override fun onServiceConnectionResponse(p0: SAPeerAgent?, p1: SASocket?, p2: Int) {
        super.onServiceConnectionResponse(p0, p1, p2)
        App.d(TAG, "conn resp " + p1?.javaClass?.name + p2)


    }

    override fun onAuthenticationResponse(p0: SAPeerAgent?, p1: SAAuthenticationToken?, p2: Int) {
        super.onAuthenticationResponse(p0, p1, p2)
        App.d(TAG, "Auth " + p1.toString())

    }


    override protected fun onServiceConnectionRequested(agent: SAPeerAgent) {


        }
    }

    override fun onFindPeerAgentsResponse(peerAgents: Array<SAPeerAgent>?, result: Int) {
    }

    override fun onError(peerAgent: SAPeerAgent?, errorMessage: String?, errorCode: Int) {
        super.onError(peerAgent, errorMessage, errorCode)
    }

    override fun onPeerAgentsUpdated(peerAgents: Array<SAPeerAgent>?, result: Int) {

    }

}

如您所见,MessageJobs也需要MessageSocket类,您需要实现这个类并处理来自设备的所有消息。

底线,它不是那么简单,它需要一些挖掘内部和编码,但它工作,最重要的是-它不会崩溃。


好吧,我注意到的一些东西可能也会对其他人有所帮助。这是严格的测试,看看我是否能找出如何解决我所看到的事件。为了简单起见,假设我有一个从演示者调用这个的方法。

context.startForegroundService(new Intent(context, TaskQueueExecutorService.class));

try {
    Thread.sleep(10000);
} catch (InterruptedException e) {
    e.printStackTrace();
}       

这将导致同样的错误。在方法完成之前,服务不会启动,因此服务中没有onCreate()。

So even if you update the UI off the main thread, IF you have anything that might hold up that method after it, it won't start on time and give you the dreaded Foreground Error. In my case we were loading some things onto a queue and each called startForegroundService, but some logic was involved with each in the background. So if the logic took too long to finish that method since they were called back to back, crash time. The old startService just ignored it and went on it's way and since we called it each time, the next round would finish up.

这让我想知道,如果我从后台线程调用服务,它是否可以在启动时完全绑定并立即运行,因此我开始试验。即使这样不会立即启动,它也不会崩溃。

new Handler(Looper.getMainLooper()).post(new Runnable() {
        public void run() {
               context.startForegroundService(new Intent(context, 
           TaskQueueExecutorService.class));
               try {
                   Thread.sleep(10000);
               } catch (InterruptedException e) {
                  e.printStackTrace();
              }       
        }
});

我不会假装知道为什么它没有崩溃,尽管我怀疑这迫使它等待,直到主线程能够及时处理它。我知道将它绑定到主线程并不理想,但由于我的使用是在后台调用它,所以我并不真正关心它是否等待完成而不是崩溃。


我在@humazed答案中添加了一些代码。所以没有初始通知。这可能是个变通办法,但对我来说很管用。

@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("")
                    .setColor(ContextCompat.getColor(this, R.color.transparentColor))
                    .setSmallIcon(ContextCompat.getColor(this, R.color.transparentColor)).build();

            startForeground(1, notification);
        }
}

我在小图标和通知上添加了transparentColor。 它会起作用的。


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

问题是调用服务。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));
}

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


当使用目标sdk 28或更高版本时,你必须为android 9设备添加如下权限,否则异常总是会发生:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

我只是分享我对这个的评论。我不确定(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服务时没有测试。 我之所以分享这些,是因为这是我解决这个问题的唯一方法。


一个问题可能是服务类没有在AndroidManifest文件中启用。 也请检查一下。

<service
        android:name=".AudioRecorderService"
        android:enabled="true"
        android:exported="false"
        android:foregroundServiceType="microphone" />

我在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 12上的行为变化:

To provide a streamlined experience for short-running foreground services on Android 12, the system can delay the display of foreground service notifications by 10 seconds for certain foreground services. This change gives short-lived tasks a chance to complete before their notifications appear.

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


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

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

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

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


大约有10个用户在我们的应用程序的崩溃分析中得到这个错误。

正如Kimi Chiu回答的那样:这个问题的主要原因是服务在提升到前台之前就停止了。但是断言在服务被销毁后并没有停止。你可以尝试在调用startforegroundservice后添加StopService来重现这个过程

所以我测试了这个,并得以复制。

我应用的一个解决方案是,我让服务至少停留5秒钟,这样服务就会提升到前台。现在我在测试时无法重现这个问题。

private fun stopService() {

        lifecycleScope.launch {
            delay(5000L)
            
            try {
                stopForeground(true)
                isForeGroundService = false
                stopSelf()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

让我们看看这个问题是否在我们的下一个构建中重现。

更新:)->这次没有与Context.startForegroundService()相关的问题,然后没有调用service . start前台()

之前/之后comparission - >

在- - - >

后- - - - - - >


在我的例子中,我称之为上下文。在服务有机会在内部调用start前台之前,停止服务(外部服务)。