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

时间:2017-06-08 02:15:46

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

我在Android O OS上使用Service类。

我计划在后台使用startService()

Android建议指出startForegroundService()应使用startForegroundService()

如果您使用Service,则Stream会抛出

  

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

错误。

这有什么问题?

35 个答案:

答案 0 :(得分:73)

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

  

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

解决方案: 对startForeground() onCreate()使用Service中的Context.startForegroundService()致电onCreate()

另请参阅:Background Execution Limits for Android 8.0(Oreo)

答案 1 :(得分:57)

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

答案 2 :(得分:30)

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

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

  1. startForeground通知必须同时包含onCreateonStartCommand,因为如果您的服务已经创建,并且您的某项活动试图再次启动,{{1不会被调用。

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

  3. 不得在onCreate之前调用
  4. stopSelf

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

    请注意,很可能Android P仍会出现此问题,因为Google拒绝了解发生了什么并且不相信这是他们的错,请阅读#36#56以获取更多信息< / p>

答案 3 :(得分:18)

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

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

我在Google issue tracker

上打开了一个错误

关于此问题的几个错误已经打开并关闭。不会修复。

希望采用明确的重复步骤进行切割。

Google团队提供的信息

Google issue tracker Comment 36

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

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

Google issue tracker Comment 56

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

彻头彻尾的语义问题,用startForegroundService()启动某些东西只是一个错误,但忽略了通过startForeground()实际将其转换为前景,就是:一个语义问题。故意将其视为app bug。在将服务转换为前台之前停止服务是应用程序错误。这就是OP的关键,也就是为什么这个问题已被标记为&#34;按预期工作。&#34;

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

答案 4 :(得分:11)

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

答案 5 :(得分:8)

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

问题触发因素

我什至没有想到该设备会承受太多负载,甚至在我的Pixel 3 XL上也注意到了这个问题。 startForeground()覆盖了所有代码路径。但是后来我意识到,在很多情况下,我的服务可以很快完成工作。 我相信,我的应用触发的原因是该服务在系统真正开始显示通知之前就已经完成。

解决方法/解决方案

我能够摆脱所有崩溃的情况。我所做的是删除对stopSelf()的呼叫。(我当时正在考虑将停止时间推迟到完全确定显示通知之前,但是我不希望用户看到该通知是否不必要。)当服务闲置一分钟或系统正常销毁该服务而不会引发任何异常时。

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

答案 6 :(得分:7)

因为我浪费了太多时间,所以只是抬头。即使我在startForeground(..)中调用onCreate(..)作为第一件事,我仍然会收到此异常。 最后我发现问题是由使用NOTIFICATION_ID = 0引起的。使用任何其他值似乎可以解决这个问题。

答案 7 :(得分:6)

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

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

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

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

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

答案 8 :(得分:4)

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

  

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

答案 9 :(得分:4)

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

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

答案 10 :(得分:3)

由于访问这里的每个人都在遭受同样的事情,所以我想分享我的解决方案,这是其他人以前从未尝试过的(无论如何,这个问题)。我可以向您保证它可以正常运行,即使在已停止的断点处也可以确认该方法。

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

  1. 在调用Context.startForegroundService()
  2. 之前,使用服务的绑定器将服务绑定到上下文。
  3. 如果绑定成功,请从服务连接呼叫Context.startForegroundService() ,然后立即在服务连接内部呼叫Service.startForeground()
  4. 重要说明: 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));
}

它似乎可以在我开发的应用程序上运行,因此在您尝试时也应该可以使用。

答案 11 :(得分:3)

许多答案,但在我的案例中都没有。

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

              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 =&#34; com.xxxx.Location.Channel&#34 ;;       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();

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

答案 12 :(得分:3)

我面临同样的问题,花了很长时间找到解决方案,你可以尝试下面的代码。如果您使用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);
    }

答案 13 :(得分:3)

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

答案 14 :(得分:3)

请不要在 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(0, 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(0, notification);
            Log.e("home_button_value","home_button_value");

        }
        return super.onStartCommand(intent, flags, startId);

    }
}

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

答案 15 :(得分:3)

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

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

此方法在onCreate()的{​​{1}}之前执行,因为Android在主线程处理程序上安排了服务的创建,但是MyForegroundServicebringDownServiceLocked上被调用,其中比赛条件。这意味着BinderThread没有机会致电MyForegroundService,这会导致崩溃。

要解决此问题,我们必须确保在startForeground的{​​{1}}之前未调用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)); } } 的{​​{1}}中,便会收到停止意图。此时,我们已经调用了stopReceiver。我们还必须删除该粘性广播,以防止下次通知stopReceiver。

请注意:方法onCreate()已过时,我仅将其用作解决此问题的临时解决方法。

答案 16 :(得分:3)

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

  

与startService(Intent)类似,但隐含的承诺是   Service将调用startForeground(int,android.app.Notification)一次   它开始运行。该服务的时间可比较   到ANR间隔执行此操作,否则系统将执行此操作   自动停止服务并声明应用程序ANR。

     

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

确保你在onCreate()上调用Service.startForeground(int, android.app.Notification),以确保它会被调用..如果你有任何条件可能会阻止你这样做,那么你最好使用正常{ {1}}并自己致电Context.startService(Intent)

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

答案 17 :(得分:2)

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

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

答案 18 :(得分:2)

来自 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()

答案 19 :(得分:1)

我只是在呼叫PendingIntent为null或不检查之前   context.startForegroundService(service_intent)功能。

这对我有用

PendingIntent pendingIntent=PendingIntent.getBroadcast(context,0,intent,PendingIntent.FLAG_NO_CREATE);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && pendingIntent==null){
            context.startForegroundService(service_intent);
        }
        else
        {
            context.startService(service_intent);
        }
}

答案 20 :(得分:1)

即使在startForeground中调用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
    boolean stopService = false;
    if (intent != null) {
        stopService = intent.getBooleanExtra("request_stop", false);
    }
    if (stopService) {
        stopSelf();
        return START_STICKY;
    }
    //Continue with the background task
    return START_STICKY;
}

P.S。如果该服务未真正运行,它将首先启动该服务,这是一项开销。

答案 21 :(得分:1)

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

initial-letter

答案 22 :(得分:1)

我知道,已经发布了太多答案,但是事实是-无法在应用程序级别上修复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在这里分析了队列,找到了“滥用者”并干脆杀死了它。该系统现在是幸福和健康的,而开发人员和用户却不是,但由于Google将他们的责任限制在该系统上,为什么他们要关心后两者?显然他们没有。他们可以做得更好吗?当然,例如他们本可以为“应用程序忙”对话框提供服务,要求用户做出有关等待或终止该应用程序的决定,但是为什么要打扰,这不是他们的责任。最主要的是该系统现在很健康。

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

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

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

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

  

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

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

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

  

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

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

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

答案 23 :(得分:0)

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

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

答案 24 :(得分:0)

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

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

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

我已经在Google开发者网站上阅读了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服务。使用绑定服务时不进行测试。 我之所以这样分享,是因为只有这样,我才能解决此问题。

答案 25 :(得分:0)

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

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

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

Worlds map zccwti 3r: enabledl
They can be Logged on or oil in Options a Vidw
Type map [or a [m cl (ommands

[00: [ s]  (Draven): gt:
[00: [ 7] W (Draven): 3g
[00: [ 7] (Dram): agg
[00: [ s] (Draven): 33g
[00: [ 9] (Draven): ga
[00:20] (Draven): galgaBg
[00:2I] (Draven): gagBa
[00:23] (Dymnpv Flash 0 Ready
[00:23] (0mm): Te[epon : Rudy
[00:24] (Draven): arfar
[00:25] (Draven): m3
[00:27] (omen): 2m Heal
[00:27] (Dymnpv 2m Exhauxt
[00:20] (Draven): mmlr
[00:29] (Draven): ms

答案 26 :(得分:0)

我已解决了以下问题:使用startService(intent)而不是Context.startForeground()启动服务,并在startForegound()之后立即调用super.OnCreate()。另外,如果在启动时启动服务,则可以启动在启动广播中启动服务的活动。尽管它不是永久性的解决方案,但它可以工作。

答案 27 :(得分:0)

调用StartForgroundService后,您应该在5秒钟内在服务中调用StartForground以显示通知。

如果您的应用定位到API级别26或更高级别,则应使用Foreground服务,并在您希望服务继续运行(即使用户未与应用进行交互)时显示通知。因为当应用本身不在前台时,系统对运行后台服务施加了限制。在大多数情况下,您的应用应改为使用计划作业。

答案 28 :(得分:0)

好的,我注意到了这一点,可能也对其他一些人有所帮助。严格来说,这是通过测试来确定是否可以解决所看到的情况。为了简单起见,假设我有一个从演示者调用此方法的方法。

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

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

答案 29 :(得分:0)

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

onBind(...)

onBind(...)是启动startForegroundonCreate(...)的更好的生命周期事件,因为onBind(...)传递了一个Intent,它可能包含{{ 1}}需要初始化Bundle。但是,由于Service是在第一次创建onStartCommand(...)时创建的,或者在之后的后续次数中被调用的,因此没有必要。

onStartCommand(...)

Service中的

startForeground对于在创建onStartCommand(...)之后进行更新非常重要。

在创建Service之后调用ContextCompat.startForegroundService(...)时,不会调用ServiceonBind(...)。因此,可以通过onCreate(...) onStartCommand(...)将更新后的数据传递到Intent中,以更新Bundle中的数据。

样本

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

活动/Fragment.kt

PlayerNotificationManager

AudioService.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()
            })
        })

答案 30 :(得分:0)

我知道这是一个迟来的答案,但是,我认为它可能会在将来有所帮助,我只使用了JobIntentService而不是IntentService来包含JobScheduler并为我处理所有事情。看看这个example

答案 31 :(得分:-1)

服务

function keepItLocal(input) {
  arguments = Array(...arguments.toString());
  console.log(arguments);
  console.log(input);
  return input;
}
keepItLocal('test', (function IIFE() {
  hammerhead = 'on the global scope';
  console.log(hammerhead);
  return 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)
}

答案 32 :(得分:-1)

对我来说,我在清单中添加了前台服务

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

如果下面的评论有效,请告诉我

答案 33 :(得分:-2)

如果您使用bindService重写服务,这一切都会消失。

如何执行此操作的示例可以在以下位置看到: https://github.com/paulpv/ForegroundServiceAPI26/tree/bound

我&#34; repro&#34;的差异。分支机构可以在: https://github.com/paulpv/ForegroundServiceAPI26/compare/repro...bound?expand=1

答案 34 :(得分:-6)

确保所有代码路径都调用startForeground方法,例如代码可能会在服务的onCreate方法上生成一个异常,阻止调用startForeground

   @Override
    public void onCreate() {
try{
}
catch(Exception e)
{
}
finally{
    startForeground(1, notificationbuilder.build());

}
    }