【问题标题】:Context.startForegroundService() did not then call Service.startForeground()Context.startForegroundService() 没有调用 Service.startForeground()
【发布时间】:2017-11-09 13:41:26
【问题描述】:

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

我打算在后台使用Service

Android documentation 声明

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

如果使用startForegroundService()Service 会抛出以下错误。

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

这是怎么回事?

【问题讨论】:

标签: android service operating-system foreground android-8.0-oreo


【解决方案1】:

来自Android 8.0 behavior changes 上的 Google 文档:

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

解决方案: 在onCreate() 中调用startForeground() 以获取您使用的Service Context.startForegroundService()

另请参阅:Background Execution Limits,适用于 Android 8.0 (Oreo)

【讨论】:

  • 我在onStartCommand 方法中执行此操作,但我仍然收到此错误。我在MainActivity 中调用了startForegroundService(intent)。也许服务启动太慢了。我认为在他们承诺立即启动服务之前不应该存在五秒的限制。
  • 5 秒时间肯定不够,这个异常在调试会话中经常发生。我怀疑它也会不时在发布模式下发生。作为致命的例外,它只会使应用程序崩溃!我试图用 Thread.setDefaultUncaughtExceptionHandler() 来捕捉它,但即使在到达那里并忽略 android 也会冻结应用程序。这是因为这个异常是从 handleMessage() 触发的,并且主循环有效地结束了......寻找解决方法。
  • @southerton 我认为您应该在 onStartCommand() 而不是 onCreate 上调用它,因为如果您关闭服务并重新启动它,它可能会转到 onStartCommand() 而无需调用 onCreate 。 ..
  • 我们可以在这里查看谷歌团队的回复issuetracker.google.com/issues/76112072#comment56
  • 我的应用在 Play 商店中有 30k 活跃用户。我也有同样的问题:由于 5 秒的时间限制来启动前台,Android 8+ 上每天有大约 30 次崩溃。崩溃来自各种设备,即使是今年的手机发布(S20Ultra、摩托罗拉......)Android 设计不保证呼叫将在 5 秒内触发并且服务被操作系统杀死。谷歌有一长串关于这个问题的反馈,但他们不承认这是设计缺陷。他们应该在迫使我们放弃后台服务之前更好地重新设计它。
【解决方案2】:

我打电话给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);
        }
}

【讨论】:

  • 我也是。但我仍然偶尔会收到此错误。也许Android不能保证它会在5秒内调用onCreate。所以他们应该在强迫我们遵守规则之前重新设计它。
  • 我在 onStartCommand() 中调用 startForeground,但偶尔会收到此错误。我把它移到 onCreate 上,从那以后我就再也没有看到它(交叉手指)。
  • 这会在 Oreo 中创建一个本地通知,说“APP_NAME 正在运行。点击以关闭或查看信息”。如何停止显示该通知?
  • 不,Android 团队声称这是一种预期行为。所以我只是重新设计了我的应用程序。这是荒唐的。由于这些“预期行为”,总是需要重新设计应用程序。这是第三次了。
  • 这个问题的主要原因是服务在被提升到前台之前被停止了。但是在服务被销毁后,断言并没有停止。您可以尝试通过在调用startForegroundService 后添加StopService 来重现此问题。
【解决方案3】:

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

这绝对是一个框架问题,但并非所有面临此问题的开发人员都在尽力而为:

  1. startForeground 通知必须同时存在于onCreateonStartCommand 中,因为如果您的服务已经创建并且您的活动正在尝试再次启动它,则不会调用onCreate

  2. 通知 ID 不能为 0,否则即使原因不同也会发生相同的崩溃。

  3. stopSelf 不能在 startForeground 之前调用。

通过以上 3 个,这个问题可以稍微减少一点,但仍然不能解决,真正的解决方法或者说解决方法是将目标 sdk 版本降级到 25。

请注意,Android P 很可能仍会出现此问题,因为 Google 甚至拒绝了解正在发生的事情,也不认为这是他们的错,请阅读 #36#56 了解更多信息

【讨论】:

  • 为什么同时使用 onCreate 和 onStartCommand?你能把它放在 onStartCommand 里吗?
  • stopSelf 不能在 startForeground => 之前或之后直接调用,因为这样做似乎取消了“startForeground”。
  • 我执行了一些测试,如果服务已经创建,即使不调用onCreate,也不需要再次调用startForeground。因此,您只能在 onCreate 方法中调用它,而不能在 onStartCommand 中调用它。可能他们认为服务的单个实例在第一次调用之后一直处于前台。
  • 我使用 0 作为startForeground 的初始呼叫的通知 ID。将其更改为 1 可以解决我的问题(希望如此)
  • because if your service is already created and somehow your activity is trying to start it again, onCreate won't be called. - 如果服务在此之前启动,则该服务已经在前台,因为它在onCreate 中变为前台,所以这句话没有任何意义......
【解决方案4】:

我知道,已经发布了太多答案,但事实是 - startForegroundService 无法在应用程序级别修复,您应该停止使用它。 Google 建议在调用 Context#startForegroundService() 后 5 秒内使用 Service#startForeground() API,这并不是应用程序总能做到的。

Android 同时运行大量进程,并且无法保证 Looper 会在 5 秒内调用您的目标服务,该服务应该调用 startForeground()。如果您的目标服务在 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)

据我了解,Looper 已经分析了这里的队列,找到了一个“滥用者”并简单地将其杀死。系统现在是快乐和健康的,而开发者和用户却不是,但既然谷歌将他们的责任限制在系统上,他们为什么要关心后两者呢?显然他们没有。他们能做得更好吗?当然,例如他们本可以提供“应用程序正忙”对话框,要求用户做出等待或终止应用程序的决定,但何苦呢,这不是他们的责任。主要是系统现在很健康。

根据我的观察,这种情况很少发生,在我的情况下,1000 名用户一个月内大约会发生 1 次崩溃。复制它是不可能的,即使它被复制了,你也无法永久修复它。

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

我认为,谷歌方面的正确和诚实的做法是告诉大家 startForegourndServcie 有缺陷,不应该使用。

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

当作业正在运行时,系统会代表您持有唤醒锁 应用程序。为此,您无需采取任何措施来保证 设备在工作期间保持唤醒状态。

这意味着您不再需要关心处理唤醒锁,这就是为什么它与前台服务没有什么不同。从实现的角度来看,JobScheduler 不是您的服务,它是系统的服务,大概它会正确处理队列,Google 永远不会终止自己的孩子:)

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

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

切换到 JobScheduler/JobService 的唯一缺陷是您需要重构旧代码,而且不好玩。过去两天我一直在这样做以使用新的三星 SAP 实施。我会查看我的崩溃报告,如果再次看到崩溃,我会通知您。理论上它不应该发生,但总有一些我们可能不知道的细节。

更新 Play 商店不再报告崩溃。这意味着 JobScheduler/JobService 不存在这样的问题,切换到此模型是一劳永逸地摆脱 startForegroundService 问题的正确方法。我希望,Google/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 的 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 的 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 类,它处理来自您设备的所有消息。

归根结底,它并不是那么简单,它需要对内部结构和编码进行一些挖掘,但它可以工作,最重要的是 - 它不会崩溃。

【讨论】:

  • 很好的答案,但有一个主要问题,JobIntentService 在 Oreo 下方以 IntentService 的形式立即运行,但在 Oreo 及其上方安排了作业,因此 JobIntentService 不会立即启动。 More info
  • 你说得对,前台服务可能不会在时间限制的 5 秒内启动(谷歌是应该为此受到指责的人),但 JobService 是 15 分钟间隔的东西,我赞成您的帖子,因为该方法很好但没有用。
  • @CopsOnRoad 它非常有用,在我的情况下它会立即启动,正如我所写的,它用于手机和智能手表之间的实时交互。不可能,用户会等待 15 分钟在这两者之间发送数据。运行良好,永不崩溃。
  • 如果时间允许,我可能会这样做:我需要将其与自定义特定代码分离并反编译一些三星专有库
  • 所以称JobService为好替代品是错误的,因为它不能一直运行,只有粘性前台服务才能做到
【解决方案5】:

如果您在调用Service.startForeground(...) 之前调用Context.startForegroundService(...),然后调用Context.stopService(...),您的应用将会崩溃。

我在这里有一个明确的复制ForegroundServiceAPI26

我在 Google issue tracker 上打开了一个错误

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

希望通过清晰的复制步骤进行挖掘。

谷歌团队提供的信息

Google issue tracker Comment 36

这不是框架错误;这是故意的。如果应用使用startForegroundService() 启动服务实例,它必须将该服务实例转换为前台状态并显示通知。如果服务实例在调用 startForeground() 之前停止,则该承诺未实现:这是应用程序中的错误。

Re #31,发布一个其他应用可以直接启动的Service,从根本上来说是不安全的。您可以通过将该服务的 all 启动操作视为需要 startForeground() 来缓解这种情况,但显然这可能不是您的想法。

Google issue tracker Comment 56

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

直接的语义问题,即使用startForegroundService() 启动某些内容只是一个错误,但忽略通过startForeground() 将其实际转换到前台,这就是语义问题。故意将其视为应用程序错误。在将服务转换到前台之前停止服务是应用程序错误。这就是 OP 的症结所在,这也是为什么这个问题被标记为“按预期工作”的原因。

然而,也有关于虚假检测这个问题的问题。这 is 被视为一个真正的问题,尽管它与这个特定的错误跟踪器问题是分开跟踪的。我们不会对投诉置若罔闻。

【讨论】:

  • 我见过的最好的更新是issuetracker.google.com/issues/76112072#comment56。我重写了我的代码以使用 Context.bindService,它完全避免了对 Context.startForegroundService 的有问题的调用。您可以在github.com/paulpv/ForegroundServiceAPI26/tree/bound/app/src/… 看到我的示例代码
  • 是的,正如我所写,bind 应该可以工作,但我已经切换到 JobServive 并且不会更改任何内容,除非这个也坏了':)
  • @swooby 你的解决方案对我帮助很大。
  • 在我的情况下这是不可能的,因为我不使用context.stopService。我从活动发送本地广播以停止服务,服务注册到这个广播,调用stopSelf,所以它自己停止,当它调用stopSelfstartForeground在100%之前被调用
【解决方案6】:

由于访问这里的每个人都遭受同样的事情,我想分享我以前没有人尝试过的解决方案(无论如何在这个问题中)。我可以向您保证,它正在工作,即使在已停止的断点上也可以确认此方法。

问题是从服务本身调用Service.startForeground(id, notification),对吗?不幸的是,Android Framework 不保证在 5 秒内调用 Service.onCreate() 内的 Service.startForeground(id, notification) 但无论如何都会抛出异常,所以我想出了这个方法。

  1. 在调用 Context.startForegroundService() 之前,使用服务中的绑定器将服务绑定到上下文
  2. 如果绑定成功,从服务连接调用Context.startForegroundService(),并立即在服务连接内调用Service.startForeground()
  3. 重要提示:try-catch中调用Context.bindService()方法,因为在某些情况下调用会抛出异常,在这种情况下你需要依赖调用@987654328直接@,希望它不会失败。一个示例可以是广播接收器上下文,但在这种情况下获取应用程序上下文不会引发异常,但直接使用上下文会引发异常。

当我在绑定服务之后和触发“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.onServiceConnected() 方法。然后,处理上面显示的代码中的逻辑。示例代码如下所示:

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

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

【讨论】:

  • 谢谢,刚刚在一个安装量超过 500 万的应用中实施了您的解决方案,让我们看看。希望一切顺利。
  • 到目前为止它对我有用 - 差不多一个月后,没有更多与 startForegroundService() 相关的崩溃或 ANR - 谢谢!
  • 一个棘手的解决方案,我会尝试,在我的应用中有 80k 活跃用户
  • 但是你必须在活动周期的onStop中解绑服务
  • ` // 这是关键:无需等待 Android 框架调用此方法 // 在 Service.onCreate() 中,立即调用此处发布通知。` - 我不确定,如何在onCreate of Service 被调用之前开始通知。而当onServiceConnected被调用的时候,那么就说明onCreate已经被调用了,所以你这句话里有很多废话
【解决方案7】:

我已经对此进行了几天的研究并得到了解决方案。 现在在 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
    }
}

【讨论】:

  • 您是否在一些至少拥有数千名用户的应用中尝试过?我试过了,有些用户还是有崩溃的问题。
  • 不不,我使用的是确切的代码,我没有看到客户端崩溃。
  • 你有多少用户?我在每天有 10k 用户的应用程序中每天看到大约 10-30 次崩溃(有错误)。当然,它只发生在 android 8.0 和 android 8.1 的用户身上,以防 targetSdkVersion 为 27。在我将 targetSdkVersion 设置为 25 后,所有问题都消失为 0 崩溃。
  • 只有 2200 个用户:-/ 但没有崩溃
  • @Jeeva,不是真的。 Atm,我使用 targetSdkVersion 25 和 compileSdkVersion 27。经过大量实验后,它看起来是最好的方法......希望他们能在 2018 年 8 月之前完成developer.android.com/topic/libraries/architecture/…,因为android-developers.googleblog.com/2017/12/…
【解决方案8】:

我有一个widget,它在设备处于唤醒状态时会进行相对频繁的更新,而我在短短几天内就看到了数千次崩溃。

问题触发

我什至在我的 Pixel 3 XL 上也注意到了这个问题,当时我根本没想到该设备有太多负载。并且所有代码路径都被startForeground() 覆盖。但后来我意识到,在许多情况下,我的服务很快就能完成工作。 我认为我的应用的触发因素是服务在系统真正开始显示通知之前完成。

解决方法/解决方案

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

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

【讨论】:

  • 但是,根据文档,stopForeground ...does not stop the service from running (for that you use stopSelf() or related methods)
  • @DimaRostopira 是的,在实践中,我看到系统会在一段时间(可能一分钟)不活动后停止服务运行。这并不理想,但它可以阻止系统抱怨。
  • 当我的服务运行时间很短时,我还遇到了前台通知未删除的问题。在调用 stopForeground(true) 之前添加 100ms 延迟;停止自我();有帮助。您的解决方案没有帮助。 (像素 3,Android 11)
【解决方案9】:

提醒一下,因为我在这方面浪费了太多时间。即使我在onCreate(..) 中将startForeground(..) 作为第一件事,我仍然不断收到此异常。 最后我发现问题是使用NOTIFICATION_ID = 0引起的。使用任何其他值似乎可以解决此问题。

【讨论】:

  • 就我而言,我使用的是 ID 1,谢谢,问题已解决
  • 我使用的是 ID 101,但有时仍会收到此错误。
  • @RavindraKushwaha 不是真的。
【解决方案10】:

我有一个解决这个问题的方法。我已经在我自己的应用程序中验证了这个修复(300K+ DAU),它可以减少至少 95% 的此类崩溃,但仍然不能 100% 避免这个问题。

即使您确保按照 Google 记录的那样在服务启动后立即调用 startForeground(),也会发生此问题。可能是因为很多场景下服务创建和初始化过程已经耗时超过5秒,那么无论何时何地调用startForeground()方法,都无法避免这种crash。

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

  1. 首先不要使用 startForegroundService,使用带有 auto_create 标志的 bindService()。它将等待服务初始化。这是代码,我的示例服务是 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);
    
  2. 那么这里是 MusicBinder 的实现:

    /**
     * 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();
         }
     }
    
  3. 最重要的部分,MusicService 的实现,forceForeground() 方法会确保 startForeground() 方法在 startForegroundService() 之后被调用:

    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) {
                Intent intent = new Intent(this, MusicService.class);
                // service has already been initialized.
                // startForeground method should be called within 5 seconds.
                ContextCompat.startForegroundService(this, intent);
                Notification notification = mNotificationHandler.createNotification(this);
                // call startForeground just after startForegroundService.
                startForeground(Constants.NOTIFICATION_ID, notification);
            }
        }
    }
    
  4. 如果您想在待处理的 Intent 中运行步骤 1 代码 sn-p,例如如果您想在不打开应用的情况下在小部件中启动前台服务(单击小部件按钮),您可以将代码 sn-p 包装在广播接收器中,并触发广播事件而不是启动服务命令。

就是这样。希望能帮助到你。祝你好运。

【讨论】:

  • 这仍然没有帮助,我有 80K 活跃用户(200K 下载)
  • @user924 正如我所提到的,它可以减少至少 95% 的这种崩溃,但仍然不能 100% 避免这个问题。我没有找到 100% 解决此异常的方法。对不起。
  • 如果需要 Context.startForeground() 来从应用程序的后台状态启动服务,这是如何解决的?
  • 虽然@CommonsWare 在他的评论stackoverflow.com/questions/55894636/… 中推荐了这个解决方案,但它并不完美。调用 startForeground() unbindService() 后,服务有时会被系统销毁,并立即重新启动。当服务在启动期间初始化工作线程时,我们得到一个错误 java.lang.InternalError: Thread started during runtime shutdown :-(
【解决方案11】:

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

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

【讨论】:

  • 我的应用拥有此权限,但仍然遇到问题中描述的问题。
  • @JohnnyLambada 似乎只有一个活动可以启动ForgroundService,而是尝试在后台启动服务。
【解决方案12】:

id 设置为 0 时调用 Service.startForeground(int id, Notification notification) 时,Android 8+ 也会出现此错误。

id int:根据 NotificationManager.notify(int, Notification) 的通知标识符; 不得为 0

【讨论】:

【解决方案13】:

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

我已经开始这样的服务了。

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

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

【讨论】:

  • if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { stopForeground(true); } else { stopForeground(true); } 这又是为了什么?两种情况你都在做同样的事情,stopForeground(true);
  • 我相信你的意思是把 stopSelf();在 else 块内部而不是 stopForeground(true);
  • 是的,@JustinStanley 使用 stopSelf();
  • 不应该在onCreate 中调用startForeground 吗?
  • 我使用的是 NotificationManager 而不是 Notification 类。我该如何实现?
【解决方案14】:

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

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

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

要解决此问题,我们必须确保在 onCreate()MyForegroundService 之前未调用 bringDownServiceLocked

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 在注册到MyForegroundServiceonCreate() 后立即收到停止意图。此时我们已经调用了startForeground(...)。我们还必须删除那个粘性广播,以防止下次通知 stopReceiver。

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

【讨论】:

  • 当你想停止服务时,你应该调用它而不是 context.stopService(serviceIntent)。
【解决方案15】:

请不要在 onCreate() 方法中调用任何 StartForgroundServices,您必须在创建工作线程后调用 onStartCommand() 中的 StartForground 服务,否则您将得到 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);

    }
}

编辑:小心! startForeground 函数不能以 0 作为第一个参数,它会引发异常! 此示例包含错误的函数调用,请将 0 更改为您自己的 const,该 const 不能为 0 或大于 Max(Int32)

【讨论】:

    【解决方案16】:

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

    类似于 startService(Intent),但有一个隐含的承诺,即 服务将调用一次 startForeground(int, android.app.Notification) 它开始运行。该服务的时间相当 到 ANR 间隔来执行此操作,否则系统将 自动停止服务并声明应用 ANR。

    不同于普通的startService(Intent),这个方法可以用在 任何时候,无论托管服务的应用程序是否在 前景状态。

    确保您在 onCreate() 上调用 Service.startForeground(int, android.app.Notification),以便确保它会被调用..如果您有任何可能阻止您这样做的情况,那么您最好使用普通的 Context.startService(Intent)并自己拨打Service.startForeground(int, android.app.Notification)

    Context.startForegroundService() 似乎添加了一个看门狗,以确保您在 Service.startForeground(int, android.app.Notification) 被销毁之前调用了它...

    【讨论】:

      【解决方案17】:

      我也面临同样的问题,花时间找到解决方案后,您可以尝试以下代码。如果您使用Service,则将此代码放入onCreate,否则您使用Intent Service,然后将此代码放入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);
          }
      

      【讨论】:

        【解决方案18】:

        Android O API 26 的问题

        如果您立即停止服务(因此您的服务实际上并没有真正运行(措辞/理解)并且您处于 ANR 间隔之下,您仍然需要在 stopSelf 之前调用 startForeground

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

        尝试了这种方法,但它仍然会产生错误:-

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

        在错误解决之前我一直在使用它

        mContext.startService(playIntent);
        

        【讨论】:

        • 应该是 Util.SDK_INT >= 26,而不仅仅是更大
        【解决方案19】:

        来自Android 12 behavior changes 上的 Google 文档:

        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() 中为你使用的 Service 调用 startForeground() Context.startForegroundService()

        【讨论】:

        • 这个问题对于 Android 12 来说太旧了
        【解决方案20】:

        好的,我注意到这可能对其他人也有帮助。这严格来自测试,看看我是否能弄清楚如何解决我所看到的问题。为简单起见,假设我有一个从演示者那里调用它的方法。

        context.startForegroundService(new Intent(context, TaskQueueExecutorService.class));
        
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }       
        

        这将崩溃并出现相同的错误。在方法完成之前,服务不会启动,因此服务中没有onCreate()

        因此,即使您从主线程更新 UI,如果您有任何可能阻止该方法之后的内容,它也不会按时启动并给您带来可怕的前台错误。在我的例子中,我们将一些东西加载到一个队列中,每个都称为startForegroundService,但每个都在后台涉及一些逻辑。因此,如果由于它们被背靠背调用,逻辑花费了太长时间来完成该方法,那么崩溃时间。旧的startService 只是忽略了它并继续前进,因为我们每次都调用它,所以下一轮将结束。

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

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

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

        【讨论】:

        • 您是从后台还是在活动中启动服务?
        • 背景。问题是 Android 不能保证服务会在规定的时间内启动。有人会认为如果你愿意,他们只会抛出一个错误来忽略它,但可惜的是,不是 Android。它必须是致命的。这对我们的应用有所帮助,但并没有完全解决问题。
        【解决方案21】:

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

        附:如果服务没有运行,它会先启动服务,这是一个开销。

        【讨论】:

        • 你不需要创建服务来停止它,只需使用本地广播管理器))
        • 它不是一个不同的服务。它是我想要停止的相同服务!
        • 我没有说什么不同的服务,我的意思是你为什么要在它启动的时候停止?
        • 使用广播语言环境消息
        • 尊敬的用户924,如果我必须停止服务,也不需要广播,我可以直接调用context.stopService()。上面的答案是解决三星设备上发生的问题的技巧,以防您在实际调用 onStartCommand 之前停止服务。
        【解决方案22】:

        我在 Pixel 3、Android 11 中遇到了一个问题,即当我的服务运行时间很短时,前台通知没有被解除。

        在 stopForeground() stopSelf() 之前添加 100 毫秒的延迟似乎有帮助

        人们在这里写道,应该在 stopSelf() 之前调用 stopForeground()。我无法确认,但我想它不会那么麻烦。

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

        【讨论】:

          【解决方案23】:

          我只是在调用之前检查PendingIntent是否为空 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);
                  }
          }
          

          【讨论】:

          • 这实际上只是将服务踢到 startService 而不是 startForegroundService 如果为空。 if 语句必须嵌套,否则应用程序在尝试使用更高版本的服务时会崩溃。
          【解决方案24】:

          只需在 Service 或 IntentService 创建后立即调用 startForeground 方法。像这样:

          import android.app.Notification;
          public class AuthenticationService extends Service {
          
              @Override
              public void onCreate() {
                  super.onCreate();
                  startForeground(1,new Notification());
              }
          }
          

          【讨论】:

          • 致命异常:主要 android.app.RemoteServiceException:startForeground 的错误通知:java.lang.RuntimeException:服务通知的无效通道:Notification(channel=null pri=0 contentView=null vibrate=null sound =null defaults=0x0 flags=0x40 color=0x00000000 vis=PRIVATE) 在 android.os.Handler.dispatchMessage(Handler.java:106) 在 android.app.ActivityThread$H.handleMessage(ActivityThread.java:1821) 在 android。 os.Looper.loop(Looper.java:164) 在 android.app.ActivityThread.main(ActivityThread.java:6626)
          【解决方案25】:

          更新onStartCommand(...)中的数据

          onBind(...)

          onBind(...) 是启动startForegroundonCreate(...) 相比更好的生命周期事件,因为onBind(...) 传入Intent,其中可能包含初始化Bundle 所需的重要数据Service。但是,这不是必需的,因为 onStartCommand(...) 在首次创建 Service 时会被调用,或者在随后的时间被调用。

          onStartCommand(...)

          onStartCommand(...) 中的startForeground 很重要,以便在创建 Service 后对其进行更新。

          在创建 Service 后调用 ContextCompat.startForegroundService(...) 时,不会调用 onBind(...)onCreate(...)。因此,更新后的数据可以通过IntentBundle传入onStartCommand(...),以更新Service中的数据。

          示例

          我正在使用这种模式在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)
          }
          

          【讨论】:

            【解决方案26】:

            我在@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);
                    }
            }
            

            我在小图标和通知颜色中添加了透明颜色。 它会起作用的。

            【讨论】:

              【解决方案27】:

              我已经解决了使用startService(intent) 而不是Context.startForeground() 启动服务并在super.OnCreate() 之后立即调用startForegound() 的问题。此外,如果您在启动时启动服务,您可以启动在启动广播时启动服务的 Activity。虽然它不是一个永久的解决方案,但它确实有效。

              【讨论】:

              • 部分小米设备后台无法启动活动
              【解决方案28】:

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

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

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

              我已经阅读了谷歌开发者网站上的service、其他一些博客和一些堆栈溢出问题,并且知道当我们调用startForgroundSerivce() 方法但当时服务没有启动时会发生这个问题。

              就我而言,我已停止服务并立即启动服务。以下是提示。

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

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

              Work for me:

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

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

              Note: 这仅适用于Foreground and Background 服务。使用绑定服务时不要测试。 我分享这个是因为只有这样我才能解决这个问题。

              【讨论】:

              • 在我的应用程序 (200k MAU) 上尝试过,但仍然出现此错误。主要是三星似乎出于某种原因(也许是统计数据,但 idk)。
              【解决方案29】:

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

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

              【讨论】:

                【解决方案30】:

                服务

                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
                

                【讨论】:

                  猜你喜欢
                  • 2019-01-09
                  • 2018-03-29
                  • 2018-03-04
                  • 1970-01-01
                  • 2019-09-17
                  • 1970-01-01
                  • 1970-01-01
                  • 2021-05-31
                  相关资源
                  最近更新 更多