339

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

我打算Service在后台使用。

Android文档指出

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

如果使用startForegroundService(),则会Service引发以下错误。

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

这有什么问题?

4

31 回答 31

130

来自谷歌关于Android 8.0 行为变化的文档:

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

解决方案:调用您使用startForeground()onCreate()ServiceContext.startForegroundService()

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

于 2017-07-12T02:51:28.247 回答
114

然后我打电话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);
        }
}
于 2017-09-27T14:04:19.153 回答
75

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

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

  1. startForeground通知必须在两者中onCreateonStartCommand因为如果您的服务已经创建并且您的活动以某种方式尝试再次启动它,onCreate则不会被调用。

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

  3. stopSelf之前一定不能调用startForeground

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

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

于 2018-06-05T03:17:10.427 回答
66

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

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

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

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

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

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

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

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

此服务在应用程序主线程上运行的处理程序上执行每个传入作业。这意味着您必须将执行逻辑卸载到您选择的另一个线程/处理程序/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); 

SJobService 只是 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 类,您需要实现该类并处理来自您设备的所有消息。

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

于 2019-06-10T22:53:39.783 回答
44

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

我在这里有一个明确的重现ForegroundServiceAPI26

我在以下位置打开了一个错误:Google 问题跟踪器

已经打开和关闭了几个关于此的错误不会修复。

希望我的清晰复制步骤能够成功。

谷歌团队提供的信息

谷歌问题跟踪器评论 36

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

关于#31,发布其他应用程序可以直接启动的服务从根本上是不安全的。您可以通过将该服务的所有启动操作视为 requires来缓解这一点startForeground(),但显然这可能不是您的想法。

谷歌问题跟踪器评论 56

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

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

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

于 2018-03-21T23:39:22.713 回答
24

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

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

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

当我在绑定服务之后和触发“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));
}

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

于 2019-08-16T08:33:52.227 回答
23

我已经对此进行了几天的研究并得到了解决方案。现在在 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
    }
}
于 2018-04-05T13:27:24.147 回答
18

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

问题触发器

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

解决方法/解决方案

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

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    stopForeground(true);
} else {
    stopSelf();
}
于 2018-11-20T21:44:10.517 回答
15

提醒一下,因为我在这上面浪费了太多时间。即使我startForeground(..)onCreate(..). 最后我发现问题是由于使用NOTIFICATION_ID = 0. 使用任何其他值似乎可以解决此问题。

于 2018-05-16T20:01:52.273 回答
13

我有一个解决这个问题的方法。我已经在我自己的应用程序(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. 如果您想在待处理的意图中运行第 1 步代码片段,例如,如果您想在不打开应用的情况下在小部件中启动前台服务(单击小部件按钮),您可以将代码片段包装在广播接收器中,并触发广播事件而不是启动服务命令。

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

于 2018-11-13T17:09:01.787 回答
11

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

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
于 2020-03-11T17:28:05.447 回答
9

当id设置为 0时调用Service.startForeground(int id, Notification notification)在 Android 8+ 上也会发生此错误。

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

于 2018-06-01T07:51:12.037 回答
9

这么多答案,但在我的情况下没有一个有效。

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

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

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

于 2018-06-11T18:53:23.887 回答
6

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

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

异常在以下代码块中引发:

活动服务.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);
         }
    }
    ...
}

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

要解决这个问题,我们必须确保在ofbringDownServiceLocked之前没有调用它。onCreate()MyForegroundService

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

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

于 2018-11-22T10:30:25.580 回答
5

请不要在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 不能为 0 或大于 Max(Int32)

于 2018-12-31T11:41:56.547 回答
4

我面临同样的问题,花时间找到解决方案后,您可以尝试下面的代码。如果您使用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);
    }
于 2018-04-09T07:20:28.560 回答
4

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);
于 2018-06-27T09:35:03.097 回答
4

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

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

与普通的 startService(Intent) 不同,该方法可以随时使用,无论托管该服务的应用程序是否处于前台状态。

确保你Service.startForeground(int, android.app.Notification)在 onCreate() 上调用了,所以你确保它会被调用..如果你有任何可能阻止你这样做的情况,那么你最好不要使用正常Context.startService(Intent)Service.startForeground(int, android.app.Notification)自己打电话。

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

于 2018-01-12T10:35:55.197 回答
4

来自谷歌关于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() 中为您使用的 Service 调用 startForeground() Context.startForegroundService()

于 2021-03-14T19:08:24.333 回答
3

即使在调用startForegroundin之后Service,如果我们在调用stopService之前调用它,它也会在某些设备上崩溃onCreate。因此,我通过使用附加标志启动服务来解决此问题:

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

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

于 2018-06-22T07:21:06.273 回答
2

我在 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();
        }
    }
}
}
于 2020-10-08T12:06:14.827 回答
2

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

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

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

于 2019-06-24T13:41:55.347 回答
1

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

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

于 2019-08-04T19:16:08.037 回答
1

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

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

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(1,new Notification());
    }
}
于 2019-02-23T10:39:02.307 回答
1

我只是在调用函数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);
        }
}
于 2018-11-06T04:14:52.387 回答
1

更新数据onStartCommand(...)

绑定(...)

onBind(...)是一个更好的生命周期事件,因为传入一个可能包含初始化所需的重要startForeground数据的. 但是,不需要在第一次创建时调用或在随后调用时调用。onCreate(...)onBind(...)IntentBundleServiceonStartCommand(...)Service

onStartCommand(...)

startForegroundinonStartCommand(...)很重要,以便在Service创建后更新它。

当a被创建并且不被ContextCompat.startForegroundService(...)调用之后被调用。因此,更新后的数据可以通过.ServiceonBind(...)onCreate(...)onStartCommand(...)Intent BundleService

样本

我正在使用这种模式PlayerNotificationManagerCoinverse加密货币新闻应用程序中实现。

活动/片段.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()
            })
        })

音频服务.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)
}
于 2019-06-08T22:54:51.703 回答
0

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

<service
        android:name=".AudioRecorderService"
        android:enabled="true"
        android:exported="false"
        android:foregroundServiceType="microphone" />
于 2020-09-18T12:06:29.600 回答
0

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

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

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

我已经阅读了谷歌开发者网站上的服务、其他一些博客和一些堆栈溢出问题,并且知道当我们调用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: 这仅适用于前台和后台服务。使用绑定服务时不要测试。我分享这个是因为只有这样我才能解决这个问题。

于 2020-08-18T14:22:56.127 回答
0

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

于 2019-01-07T13:06:48.553 回答
-1

服务

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-03-12T10:30:16.853 回答
-2

在我的情况下,我传递给 startForeground 方法的通知 ID 是“0”,因为这个错误即将到来。

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

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

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

于 2021-09-08T11:45:42.060 回答