首页 文章

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

提问于
浏览
95

我在Android O OS上使用 Service Class .

我计划在后台使用 Service .

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

如果你使用startForegroundService, Service 会抛出一个 Context.startForegroundService() 然后没有调用 Service.startForeground() 错误 .

这有什么问题?

19 回答

  • -2

    我知道这是一个迟到的答案但是,我认为它将来会有所帮助,我只是使用 JobIntentService 而不是 IntentService ,包括它的JobScheduler并为我处理一切 . 看看这个example

  • -3

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

    即使您确保在Google记录的服务启动后立即调用startForeground(),也会出现此问题 . 可能是因为在许多情况下服务创建和初始化过程已花费超过5秒,然后无论何时何地调用startForeground()方法,此崩溃都是不可避免的 .

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

    • 首先不要使用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);
    
    • 然后这是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();
         }
     }
    
    • 最重要的部分,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);
            }
        }
    }
    
    • 如果要在挂起的意图中运行第1步代码段,例如,如果要在窗口小部件中启动前台服务(单击窗口小部件按钮)而不打开应用程序,则可以将代码段包装在广播中接收器,并触发广播事件而不是启动服务命令 .

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

  • 0

    来自Google的文档Android 8.0 behavior changes

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

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

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

  • 8

    因为我浪费了太多时间,所以只是抬头 . 即使我在 onCreate(..) 中首先调用 startForeground(..) ,我也一直得到这个例外 . 最后我发现问题是由使用 NOTIFICATION_ID = 0 引起的 . 使用任何其他值似乎解决了这个问题 .

  • 6

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

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

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

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

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

  • 0

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

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

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

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

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

    不得在startForeground之前调用

    • stopSelf .

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

    请注意,很可能Android P仍会出现这个问题,因为谷歌甚至不知道发生了什么,也不相信这是他们的错,请在这里阅读#36:https://issuetracker.google.com/issues/76112072 .

  • 2

    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) ,这样你就可以确保它被调用..如果你有任何条件可能会阻止你这样做,那么你最好使用正常的 Context.startService(Intent) 并自己调用 Service.startForeground(int, android.app.Notification) .

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

  • 13

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

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

    我只是在调用 context.startForegroundService(service_intent) 函数之前检查 PendingIntent 是否为空 .

    这对我有用

    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);
            }
    }
    
  • 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() MyForegroundService 之前执行,因为Android在主线程处理程序上调度服务的创建,但在 BinderThread 上调用 bringDownServiceLocked ,这是竞争条件 . 这意味着 MyForegroundService 没有机会调用 startForeground ,这将导致崩溃 .

    要解决这个问题,我们必须确保在 MyForegroundService MyForegroundService 之前没有调用 bringDownServiceLocked .

    public class MyForegroundService extends Service {
    
        private static final String ACTION_STOP = "com.example.MyForegroundService.ACTION_STOP";
    
        private final BroadcastReceiver stopReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                context.removeStickyBroadcast(intent);
                stopForeground(true);
                stopSelf();
            }
        };
    
        @Override
        public void onCreate() {
            super.onCreate();
            startForeground(...);
            registerReceiver(
                stopReceiver, new IntentFilter(ACTION_STOP));
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            unregisterReceiver(stopReceiver);
        }
    
        public static void stop(Context context) {
            context.sendStickyBroadcast(new Intent(ACTION_STOP));
        }
    }
    

    通过使用粘性广播,我们确保广播不会丢失,并且 stopReceiveronCreate() MyForegroundService 中注册后立即收到停止意图 . 到这时我们已经打电话给 startForeground(...) . 我们还必须删除该粘性广播,以防止下次通知stopReceiver .

    Please note 方法 sendStickyBroadcast 已弃用,我仅将其用作临时解决方法来解决此问题 .

  • 53

    即使在 Service 中调用 startForeground 之后,如果在调用 onCreate 之前调用 stopService ,它会在某些设备上崩溃 . 所以,我通过使用另一个标志启动服务来修复此问题:

    Intent intent = new Intent(context,
                    YourService.class);
    intent.putExtra("request_stop", true);
    context.startService(intent);
    

    并在onStartCommand中添加了一个检查,以查看它是否实际上已停止:

    @Override
    public int onStartCommand(Intent intent,
        int flags, int startId) {
    
        //call startForeground first
        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;
    }
    

    附:如果服务实际上没有运行,它将首先启动服务,这是一个开销 .

  • 3

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

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

    我在这上面打开了一个错误:https://issuetracker.google.com/issues/76112072

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

    希望采用清晰的复制步骤进行复制 .

  • 3

    我有一个widget,它在设备清醒时进行相对频繁的更新,我在短短几天内就看到了成千上万的崩溃事件 .

    问题触发器

    我甚至在我的Pixel 3 XL上都注意到了这个问题,当时我根本不认为该设备有很多负载 . startForeground() 涵盖了所有代码路径 . 但后来我意识到,在很多情况下,我的服务很快就完成了工作 . I believe the trigger for my app was that the service was finishing before the system actually got around to showing a notification.

    变通方法/解决方案

    I was able to get rid of all crashes. What I did was to remove the call to stopSelf(). (我正在考虑延迟停止,直到我非常确定通知已显示,但我没有必要 . )当服务闲置一分钟或系统正常销毁它而不会抛出任何异常 .

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

    如果您重写要启动的服务w / bindService,这一切都会消失 .

    有关如何执行此操作的示例,请参见:https://github.com/paulpv/ForegroundServiceAPI26/tree/bound

    我的"repro"分支的差异可见于:https://github.com/paulpv/ForegroundServiceAPI26/compare/repro...bound?expand=1

  • 3

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

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

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

    在我的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(真);
    通知通知= builder.build();
    startForeground(NOTIFICATION_ID,通知);
    } else {
    NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
    .setContentTitle(的getString(R.string.app_name))
    .setContentText(“SmartTracker正在运行...”)
    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
    .setAutoCancel(真);
    通知通知=builder.build();
    startForeground(NOTIFICATION_ID,通知);
    }

    并且不要忘记将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(真);
    } else {
    stopForeground(真);
    }

    我已通过删除通知调用停止前景,但一旦通知删除服务成为后台,后台服务无法从后台运行在android O中 . 推送后开始 .

    太神奇了

    stopSelf();

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

相关问题