然后Context.startForegroundService()没有调用Service.startForeground()

Nic*_*Guy 212 service android operating-system foreground android-8.0-oreo

Service在Android O OS上使用Class.

我计划Service在后台使用.

Android建议指出startService()应该使用startForegroundService().

如果您使用startForegroundService(),则Service抛出a Service然后调用Service错误.

这有什么问题?

小智 87

来自Google的Android 8.0上的文档行为更改:

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

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

另请参阅:Android 8.0的背景执行限制(奥利奥)

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

hum*_*zed 69

我打电话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);
        }
}
Run Code Online (Sandbox Code Playgroud)

  • 我也是.但我偶尔会遇到这个错误.也许Android无法保证它会在5秒内调用`onCreate`.所以他们应该在强制我们遵守规则之前重新设计它. (37认同)
  • 导致此问题的主要原因是服务在升级到前台之前已停止.但是在服务被破坏之后,断言没有停止.您可以尝试在调用`startForegroundService`之后添加`StopService`来重现这一点. (7认同)
  • 不,Android团队声称这是预期的行为.所以我只是重新设计了我的应用程序.这是荒唐的.由于这些"预期行为",总是需要重新设计应用程序.这是第三次. (6认同)
  • 我在onStartCommand()中调用了startForeground,偶尔会遇到这个错误.我把它移到了onCreate上,从那以后我就没有看过它(交叉手指). (5认同)
  • 这会在Oreo中创建一个本地通知,说"APP_NAME正在运行.点击关闭或查看信息".如何停止显示该通知? (4认同)
  • 我经常在调试模式下获得此异常,最后决定使用自定义异常处理程序捕获异常并保持应用程序运行.http://choruscode.blogspot.com/2018/05/handling-exceptions-thrown-from.html (2认同)

Zho*_*ouX 45

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

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

  1. startForeground通知必须同时在onCreate和onStartCommand中,因为如果您的服务已经创建,并且您的活动试图以某种方式再次启动它,则不会调用onCreate.

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

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

有了以上所有3这个问题可以减少一点但仍然没有修复,真正的修复或让我们说解决方法是将目标sdk版本降级到25.

请注意,很可能Android P仍会出现此问题,因为Google拒绝了解发生了什么,并且不相信这是他们的错,请在此处阅读#36:https://issuetracker.google.com/issues/76112072.

  • 为什么onCreate和onStartCommand?你能把它放在onStartCommand吗? (8认同)
  • `因为如果您的服务已经创建并且您的活动以某种方式尝试再次启动它,则 onCreate 将不会被调用。` - 如果服务之前启动,则该服务已经在前台,因为它在 `onCreate` 中成为前台,所以这句话没有任何意义... (5认同)
  • 我执行了一些测试,如果服务已经创建,即使没有调用onCreate,您也不需要再次调用startForeground。因此,您只能在 onCreate 方法上调用它,而不能在 onStartCommand 中调用它。可能他们认为从第一次调用到结束,服务的单个实例都在前台。 (4认同)
  • 我使用 0 作为初始调用“startForeground”的通知 ID。将其更改为 1 可以解决我的问题(希望如此) (2认同)

swo*_*oby 23

如果你打电话给你的应用程序会崩溃Context.startForegroundService(...),然后调用Context.stopService(...)之前Service.startForeground(...)被调用.

我有一个明确的副本:https: //github.com/paulpv/ForegroundServiceAPI26/blob/repro/app/src/main/java/com/github/paulpv/foregroundserviceapi26/MainService.kt#L28

我已经在此处打开了一个错误:https: //issuetracker.google.com/issues/76112072

关于此的几个错误已经打开并关闭将不会修复.

希望采用明确的复制步骤进行复制.

  • 我见过的最好的更新是https://issuetracker.google.com/issues/76112072#comment56。我重写了代码以使用Context.bindService,这完全避免了对Context.startForegroundService的有问题的调用。您可以在https://github.com/paulpv/ForegroundServiceAPI26/tree/bound/app/src/main/java/com/github/paulpv/foregroundserviceapi26查看我的示例代码 (2认同)
  • 大家看到我的答案对我有用 /sf/answers/5092793261/ (2认同)

Fur*_*kul 20

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

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

  1. 在调用之前使用来自服务的绑定器将服务绑定到上下文 Context.startForegroundService()
  2. 如果绑定成功,Context.startForegroundService() 则从服务连接调用并立即在服务连接内部调用Service.startForeground()
  3. 重要说明:Context.bindService()try-catch 中调用该方法,因为在某些情况下调用可能会抛出异常,在这种情况下,您需要依靠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..");
    }
};
Run Code Online (Sandbox Code Playgroud)

在您的服务中,创建一个返回服务实例的绑定器。

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,尝试从该上下文绑定服务。如果成功,它将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));
}
Run Code Online (Sandbox Code Playgroud)

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

  • 到目前为止,它对我有用 - 几乎一个月后,没有更多与 startForegroundService() 相关的崩溃或 ANR - 谢谢! (3认同)

Ole*_*ryb 15

我知道,已经发布了太多答案,但是事实是-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)
Run Code Online (Sandbox Code Playgroud)

据我了解,Looper在这里分析了队列,找到了“滥用者”并干脆杀死了它。该系统现在是快乐和健康的,而开发人员和用户却不是,但是由于Google将其责任限制在该系统上,为什么他们要关心后两者?显然他们没有。他们可以做得更好吗?当然,例如,他们本可以为“应用程序忙”对话框提供服务,要求用户做出有关等待或终止该应用程序的决定,但是为什么要打扰,这不是他们的责任。最主要的是,该系统现在很健康。

根据我的观察,这种情况相对很少发生,以我为例,一千个用户在一个月内大约发生了1次崩溃。再现它是不可能的,即使再现它,也无法永久修复它。

在该线程中有一个很好的建议,即使用“ bind”而不是“ start”,然后在服务就绪时处理onServiceConnected,但是同样,这意味着完全不使用startForegroundService调用。

我认为,Google方面的正确和诚实的举动是告诉所有人startForegourndServcie有缺陷,不应使用。

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

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

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

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

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

切换到JobScheduler / JobService的唯一陷阱是您需要重构旧代码,这很不好玩。我已经花了两天的时间来使用新的三星的SAP实施。我将查看崩溃报告,并让您知道是否再次看到崩溃。从理论上讲,这不应该发生,但是总有一些细节我们可能不知道。

更新 Play商店没有更多的崩溃报告。这意味着JobScheduler / JobService不会出现这样的问题,而切换到该模型是永远摆脱startForegroundService问题的正确方法。希望Google / Android能够阅读并最终为所有人提供评论/建议/提供正式指导。

  • 很好的答案,但有一个主要问题,“JobIntentService”在 Oreo 下立即作为“IntentService”运行,但在 Oreo 上及上方安排作业,因此“JobIntentService”不会立即启动。[更多信息](/sf/ask/3727396991/) (3认同)
  • 如果时间允许,我可能会这样做:我需要将其与特定于自定义的代码分离并反编译一些专有的三星库 (2认同)
  • 因此,将“JobService”称为好的替代品是错误的,因为它不能一直运行,只有粘性前台服务可以做到这一点 (2认同)

Ahm*_*lan 14

我已经研究了几天并得到了解决方案.现在在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);
}
Run Code Online (Sandbox Code Playgroud)

而服务类应该是这样的

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
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 您是否在至少拥有数千名用户的某个应用中尝试过?我尝试过,一些用户仍然遇到崩溃问题。 (3认同)

Roy*_*erg 13

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

问题触发器

我甚至在我的Pixel 3 XL上都注意到了这个问题,当时我根本不认为该设备有很多负载.并且覆盖了任何和所有代码路径startForeground().但后来我意识到,在很多情况下,我的服务很快就完成了工作.我相信我的应用程序的触发器是服务在系统实际到达显示通知之前完成.

解决方法/解决方案

我能够摆脱所有崩溃.我做的是删除对的呼叫stopSelf().(我在考虑延迟停止,直到我非常确定通知已显示,但我不希望用户在没有必要的情况下看到通知.)当服务闲置一分钟或系统时通常会破坏它而不会抛出任何异常.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    stopForeground(true);
} else {
    stopSelf();
}
Run Code Online (Sandbox Code Playgroud)


tob*_*alr 10

只是一个抬头,因为我浪费了太多时间.即使我打电话startForeground(..)给第一件事,我也一直得到这个例外onCreate(..).最后我发现问题是由于使用引起的NOTIFICATION_ID = 0.使用任何其他值似乎解决了这个问题.

  • 我正在使用ID 101,但有时仍会收到此错误。 (4认同)

san*_*ndy 9

So many answer but none worked in my case.

我已经开始这样的服务。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    startForegroundService(intent);
} else {
    startService(intent);
}
Run Code Online (Sandbox Code Playgroud)

在我的 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);
    }
Run Code Online (Sandbox Code Playgroud)

并且不要忘记设置 NOTIFICATION_ID 非零

private static final String ANDROID_CHANNEL_ID = "com.xxxx.Location.Channel";
private static final int NOTIFICATION_ID = 555;
Run Code Online (Sandbox Code Playgroud)

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

     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            stopForeground(true);
        } else {
            stopForeground(true);
        }
Run Code Online (Sandbox Code Playgroud)

我已经用 remove notificaton 调用了停止前台,但是一旦通知删除的服务成为后台,后台服务就无法从后台在 android O 中运行。收到推送后开始。

这么神奇的词是

   stopSelf();
Run Code Online (Sandbox Code Playgroud)

到目前为止,您的服务崩溃的任何原因请按照上述所有步骤操作并享受。

  • if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { stopForeground(true); } } else { stopForeground(true); if else 是做什么用的?这两种情况你都做同样的事情,stopForeground(true); (4认同)

Shr*_*rdi 8

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

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Run Code Online (Sandbox Code Playgroud)

  • 我的应用程序具有此权限,但仍然遇到问题中描述的问题。 (2认同)

Kun*_*war 8

大约 10 个用户在我们的应用程序的 crashlytics 中遇到此错误。

在此输入图像描述

正如 Kimi Chiu 回复的那样:这个问题的主要原因是服务在提升到前台之前就被停止了。但在服务被破坏后,断言并没有停止。您可以尝试通过在调用 startForegroundService-Kimi Chiu 后添加 StopService 来重现此问题

所以我对此进行了测试并能够重现。

我应用的一种解决方案是,让服务停留至少 5 秒,以便服务提升到前台。现在我在测试时无法重现该问题。

private fun stopService() {

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

让我们看看这个问题是否会在我们的下一个版本中重现。

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

在此输入图像描述

前后对比->

之前->在此输入图像描述 在此输入图像描述

之后-> 在此输入图像描述

  • 很好的解决方案。我们必须这样做,而 Google/AOSP 却无法为我们做更好的事情,这真是太愚蠢了。 (4认同)

alm*_*oft 7

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

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

  • 通知ID也不要使用更高的数字。尝试使用一位数字ID。/sf/answers/855998881/ (2认同)

Hex*_*ise 7

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

即使您确定在Google记录服务启动后立即调用startForeground(),也会发生此问题。可能是因为在许多情况下服务创建和初始化过程已经花费了5秒钟以上的时间,因此无论何时何地调用startForeground()方法,这种崩溃都是不可避免的。

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

  1. 首先不要使用startForegroundService,而应将bindService()与auto_create标志一起使用。它将等待服务初始化。这是代码,我的示例服务是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);
    
    Run Code Online (Sandbox Code Playgroud)
  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();
         }
     }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 最重要的部分,MusicService实现,forceForeground()方法将确保在startForegroundService()之后立即调用startForeground()方法:

    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);
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  4. 如果要以挂起的意图运行第1步代码段,例如,要在不打开应用程序的情况下在窗口小部件中启动前台服务(单击窗口小部件按钮),则可以将代码段包装在广播接收器中,并触发广播事件,而不是启动服务命令。

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


mak*_*tar 6

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

MyForegroundService.java

public class MyForegroundService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(...);
    }
}
Run Code Online (Sandbox Code Playgroud)

主活动.java

Intent serviceIntent = new Intent(this, MyForegroundService.class);
startForegroundService(serviceIntent);
...
stopService(serviceIntent);
Run Code Online (Sandbox Code Playgroud)

在以下代码块中抛出异常:

活动服务.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);
         }
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

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

为了解决这个问题,我们必须确保bringDownServiceLocked没有之前调用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));
    }
}
Run Code Online (Sandbox Code Playgroud)

通过使用粘性广播,我们可以确保广播不会丢失,并且stopReceiver一旦在onCreate()of 中注册就接收到停止意图MyForegroundService。此时我们已经调用了startForeground(...). 我们还必须删除粘性广播,以防止下次通知 stopReceiver。

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


Gau*_*gla 5

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

Intent intent = new Intent(context, YourService.class);
intent.putExtra("request_stop", true);
context.startService(intent);
Run Code Online (Sandbox Code Playgroud)

并在 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;
}
Run Code Online (Sandbox Code Playgroud)

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


Sha*_*pta 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);

    }
}
Run Code Online (Sandbox Code Playgroud)

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


Dun*_*una 5

来自 Google 关于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.
Run Code Online (Sandbox Code Playgroud)

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

  • 这个问题对于 Android 12 来说太旧了 (3认同)