Context.startForegroundService()然后未调用Service.startForeground()


257

Service在Android O OS上使用Class。

我计划Service在后台使用。

Android文档指出,

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

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

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

这怎么了


IOW,请提供一个最小的可复制示例。这将包括整个Java堆栈跟踪以及触发崩溃的代码。
CommonsWare

3
该错误仍在API 26和27(27.0.3)中。受影响的android版本是8.0和8.1您可以通过将startForeground()添加到onCreate()和onStartCommand()来减少崩溃次数,但是某些用户仍然会发生崩溃。修复atm的唯一方法是build.gradle中的targetSdkVersion 25。
Alex


我现在正在使用此替代方法。就像魅力theandroiddeveloper.com/blog/...
普拉萨德·帕瓦尔

1
我也有同样的问题。我解决了这个问题。我在这个主题下分享了我的实现stackoverflow.com/questions/55894636/…–
Beyazid,

Answers:


99

根据Google关于Android 8.0的文档,行为更改

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

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

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


19
我在onStartCommand方法中这样做,但仍然出现此错误。我打电话给startForegroundService(intent)MainActivity。也许服务启动太慢。我认为应该保证立即启动服务之前不应该存在5秒的限制。
Kimi Chiu

29
5秒的时间绝对不够,这种异常在调试会话中经常发生。我怀疑它有时也会在发布模式下发生。而作为致命例外,只会使应用程序崩溃!我试图用Thread.setDefaultUncaughtExceptionHandler()捕获它,但是即使到达那里并忽略android也会冻结应用程序。这是因为此异常是从handleMessage()触发的,并且主循环有效地结束了……寻找解决方法。
萨瑟顿

我在广播接收器中调用了Context.startForegroundService()方法。那么如何处理这种情况呢?因为onCreate()在广播接收器中不可用
Anand Savjani

6
@southerton我认为您应该调用它onStartCommand()而不是onCreate,因为如果您关闭该服务并再次启动它,则可能会onStartCommand()不调用而进入onCreate...
android开发人员

1
我们可以在此处查看Google团队的回复issuetracker.google.com/issues/76112072#comment56
Prags

82

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

47
我也是。但我偶尔还是会收到此错误。也许Android无法保证会onCreate在5秒内调用。因此,在迫使我们遵守规则之前,他们应该重新设计它。
Kimi Chiu

5
我在onStartCommand()中调用startForeground,偶尔遇到此错误。我将其移至onCreate,但自从(交叉手指)以来,我再也没有看到它。
泰勒(Tyler)

4
这会在Oreo中创建一条本地通知,显示“ APP_NAME正在运行。点击以关闭或查看信息”。如何停止显示该通知?
Artist404

6
不,Android团队声称这是预期的行为。所以我只是重新设计了我的应用程序。这是荒唐的。由于这些“预期行为”,总是需要重新设计应用程序。这是第三次。
Kimi Chiu

12
导致此问题的主要原因是服务在提升为前台之前已停止。但是断言并没有在服务被销毁后停止。您可以尝试通过StopService在调用后添加来重现此内容startForegroundService
Kimi Chiu

55

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

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

  1. startForegroundonCreate和中都必须有一个通知onStartCommand,因为如果您的服务已经创建,并且您的活动试图重新启动它,onCreate则不会调用该通知。

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

  3. stopSelf绝对不能在之前被调用startForeground

使用上述3种版本,可以稍微减少此问题,但仍然不能解决,真正的解决方法或说解决方法是将目标sdk版本降级到25。

请注意,由于Google甚至不了解发生了什么并且不认为这是他们的错,因此很可能Android P仍然会出现此问题,请阅读#36#56了解更多信息


9
为什么同时使用onCreate和onStartCommand?您可以将其放在onStartCommand中吗?
rcell

不要在startForeground =>之前或之后直接调用stopSelf,因为这样做似乎会取消“ startForeground”。
弗兰克,

1
我执行了一些测试,即使已经创建了服务也没有调用onCreate,您无需再次调用startForeground。因此,您只能在onCreate方法上调用它,而不能在onStartCommand上调用它。大概他们认为服务的单个实例在第一次调用之后一直到结束都处于前台。
Lxu

我将0用作初始呼叫的通知ID startForeground。更改为1可以为我解决问题(希望如此)
mr5

那么解决方案是什么?
IgorGanapolsky

35

我知道,已经发布了太多答案,但是事实是-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有缺陷,不应使用。

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

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

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

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

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

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

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

更新2

对于那些使用SAP并询问SAP V2如何利用JobService的人,下面说明。

在您的自定义代码中,您需要初始化SAP(它是Kotlin):

SAAgentV2.requestAgent(App.app?.applicationContext, 
   MessageJobs::class.java!!.getName(), mAgentCallback)

现在,您需要反编译Samsung的代码以查看内部发生了什么。在SAAgentV2中,查看requestAgent实现和以下行:

SAAgentV2.d var3 = new SAAgentV2.d(var0, var1, var2);

where d defined as below

private SAAdapter d;

现在转到SAAdapter类,并找到onServiceConnectionRequested函数,该函数使用以下调用来调度作业:

SAJobService.scheduleSCJob(SAAdapter.this.d, var11, var14, var3, var12); 

SAJobService只是Android的JobService的一种实现,它是执行作业调度的一种:

private static void a(Context var0, String var1, String var2, long var3, String var5, SAPeerAgent var6) {
    ComponentName var7 = new ComponentName(var0, SAJobService.class);
    Builder var10;
    (var10 = new Builder(a++, var7)).setOverrideDeadline(3000L);
    PersistableBundle var8;
    (var8 = new PersistableBundle()).putString("action", var1);
    var8.putString("agentImplclass", var2);
    var8.putLong("transactionId", var3);
    var8.putString("agentId", var5);
    if (var6 == null) {
        var8.putStringArray("peerAgent", (String[])null);
    } else {
        List var9;
        String[] var11 = new String[(var9 = var6.d()).size()];
        var11 = (String[])var9.toArray(var11);
        var8.putStringArray("peerAgent", var11);
    }

    var10.setExtras(var8);
    ((JobScheduler)var0.getSystemService("jobscheduler")).schedule(var10.build());
}

如您所见,这里的最后一行使用Android的JobScheduler获取此系统服务并安排作业。

在requestAgent调用中,我们传递了mAgentCallback,这是一个回调函数,在发生重要事件时将接收控制。这是在我的应用中定义回调的方式:

private val mAgentCallback = object : SAAgentV2.RequestAgentCallback {
    override fun onAgentAvailable(agent: SAAgentV2) {
        mMessageService = agent as? MessageJobs
        App.d(Accounts.TAG, "Agent " + agent)
    }

    override fun onError(errorCode: Int, message: String) {
        App.d(Accounts.TAG, "Agent initialization error: $errorCode. ErrorMsg: $message")
    }
}

MessageJobs是我实现的一个类,用于处理来自三星智能手表的所有请求。这不是完整的代码,只是一个框架:

class MessageJobs (context:Context) : SAAgentV2(SERVICETAG, context, MessageSocket::class.java) {


    public fun release () {

    }


    override fun onServiceConnectionResponse(p0: SAPeerAgent?, p1: SASocket?, p2: Int) {
        super.onServiceConnectionResponse(p0, p1, p2)
        App.d(TAG, "conn resp " + p1?.javaClass?.name + p2)


    }

    override fun onAuthenticationResponse(p0: SAPeerAgent?, p1: SAAuthenticationToken?, p2: Int) {
        super.onAuthenticationResponse(p0, p1, p2)
        App.d(TAG, "Auth " + p1.toString())

    }


    override protected fun onServiceConnectionRequested(agent: SAPeerAgent) {


        }
    }

    override fun onFindPeerAgentsResponse(peerAgents: Array<SAPeerAgent>?, result: Int) {
    }

    override fun onError(peerAgent: SAPeerAgent?, errorMessage: String?, errorCode: Int) {
        super.onError(peerAgent, errorMessage, errorCode)
    }

    override fun onPeerAgentsUpdated(peerAgents: Array<SAPeerAgent>?, result: Int) {

    }

}

如您所见,MessageJobs也需要您需要实现的MessageSocket类,并处理来自设备的所有消息。

最重要的是,它并不是那么简单,它需要深入研究内部结构和编码,但是它是有效的,而且最重要的是,它不会崩溃。


好的答案,但是有一个主要问题,JobIntentServiceIntentService在Oreo下方立即运行,但在Oreo上方和上方计划作业,因此JobIntentService不会立即开始。更多信息
CopsOnRoad

1
@CopsOnRoad这非常有用,在我的案例中它会立即启动,正如我所写的那样,它用于手机和智能手表之间的实时交互。用户不可能等待15分钟在这两者之间发送数据。效果很好,永不崩溃。
奥列格里布

1
听起来很酷,如果您可以共享一些示例代码,这将使您的答案非常有价值,我是Android的新手,并且发现Job...启动它需要花费时间,并且它是的高级版本AlarmManager
CopsOnRoad

2
如果时间允许,我可能会做到:我需要将其与特定于定制的代码分离,并反编译一些专有的Samsung库
Oleg Gryb,

1
@batmaci-JobService由SAP内部使用。有关详细信息,请参见OP中的Update 2。
Oleg Gryb

32

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

我在这里有明确的repro ForegroundServiceAPI26

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

与此相关的几个错误已被打开和关闭。

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

Google团队提供的信息

Google问题追踪器评论36

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

关于#31,发布其他应用可以直接启动的服务根本上是不安全的。您可以通过将该服务的所有启动操作视作需要来缓解这种情况startForeground(),尽管显然您可能没有想到。

Google问题追踪器评论56

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

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

但是,也存在有关此问题的虚假检测的问题。这被视为一个真正的问题,虽然它被从该特定的bug跟踪问题分别跟踪。我们对这项投诉不屑一顾。


2
我见过的最好的更新是issuetracker.google.com/issues/76112072#comment56。我重写了代码以使用Context.bindService,这完全避免了对Context.startForegroundService的有问题的调用。你可以看到在我的示例代码github.com/paulpv/ForegroundServiceAPI26/tree/bound/app/src/...
swooby

是的,正如我所写的那样,绑定应该可以工作,但是我已经切换到JobServive了,除非这个也被破坏了,否则将不会进行任何更改':)
Oleg Gryb

19

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

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

不,不,我使用的是确切的代码,我看不到客户端崩溃。
艾哈迈德·阿尔斯兰

您有多少用户?我看到每天有10k用户的应用每天崩溃约10-30次以上(有错误)。当然,这仅适用于采用Android 8.0和Android 8.1的用户情况,万一targetSdkVersion是27.所有的问题消失0崩溃之后我设置targetSdkVersion为25
亚历

仅2200用户: - /但没有得到任何崩溃
艾哈迈德·阿尔斯兰

1
@Jeeva,不是真的。Atm,我将targetSdkVersion 25与compileSdkVersion 27一起使用。经过大量实验后,这似乎是最好的方法....希望他们能在2018年8月之前完成developer.android.com/topic/libraries/architecture/…,因为android-developers.googleblog .com / 2017/12 /…
亚历克斯

16

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

问题触发

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

解决方法/解决方案

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

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

11

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


就我而言,我使用的是ID 1,谢谢,问题得到了解决
Amin Pinjari

4
我正在使用ID 101,有时仍然会收到此错误。
CopsOnRoad

9

我已经解决了这个问题。我已经在自己的应用程序(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);
  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步代码段,例如要在不打开应用程序的情况下在窗口小部件中启动前台服务(单击窗口小部件按钮),则可以将代码段包装在广播接收器中,并触发广播事件,而不是启动服务命令。

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



7

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

问题是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..");
    }
};

在服务内部,创建一个绑定程序,该绑定程序返回服务的实例。

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()从您正在使用的服务连接中调用method。然后,处理上面显示的代码中的逻辑。示例代码如下所示:

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

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


5

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

3
应该是Util.SDK_INT> = 26,而不仅仅是更大
Andris

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

4

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

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会在主线程处理程序上安排服务的创建,但是bringDownServiceLocked会在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));
    }
}

通过使用持久广播,我们确保广播没有迷路,并stopReceiver尽快,因为它已经被注册接收到停止的意图onCreate()MyForegroundService。到这个时候我们已经打电话了startForeground(...)。我们还必须删除该粘性广播,以防止下次通知stopReceiver。

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


当您要停止服务时,应调用它而不是context.stopService(serviceIntent)。
makovkastar

4

如此众多的答案,但对我而言却无济于事。

我已经开始这样服务了。

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

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

神奇的话是

   stopSelf();

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


如果(Build.VERSION.SDK_INT> = Build.VERSION_CODES.O){stopForeground(true); } else {stopForeground(true); 如果还有其他用途,这是什么?两种情况您都在做同样的事情,stopForeground(true);
user3135923

我相信你打算放stopSelf(); 在else块内部,而不是stopForeground(true);
贾斯汀·斯坦利

1
是的,@ JustinStanley使用stopSelf();
桑迪

startForeground应该打来电话onCreate吗?
阿坝州

我正在使用NotificationManager而不是Notification类。我该如何实施呢?
Prajwal W

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)不同,此方法可以随时使用,而不管承载该服务的应用程序是否处于前台状态。

确保您Service.startForeground(int, android.app.Notification)在onCreate()上调用了on,以便确保将其调用..如果您有任何可能阻止执行此操作的条件,那么最好使用常规函数Context.startService(Intent)Service.startForeground(int, android.app.Notification)自己进行调用。

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


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,该常量不能为0或大于Max(Int32)


2

即使在调用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如果服务未运行,它将首先启动服务,这是一项开销。


2

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

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

1

PendingIntent在调用context.startForegroundService(service_intent)函数之前,我只是检查null还是不 检查。

这对我有用

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

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

实际上,这只会将服务踢到startService而不是startForegroundService(如果为null)。if语句必须嵌套,否则该应用程序在某些时候尝试在更高版本上使用服务时将崩溃。
a54studio

1

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

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

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(1,new Notification());
    }
}

致命异常:main android.app.RemoteServiceException:startForeground的错误通知:java.lang.RuntimeException:服务通知的无效通道:Notification(channel = null pri = 0 contentView = null振动= null声音= null默认值= 0x0标志= 0x40 color = 0x00000000 vis = PRIVATE)在android.app.ActivityThread $ H.handleMessage(ActivityThread.java:1821)在android.os.Handler.dispatchMessage(Handler.java:106)在android.os.Looper.loop(Looper。 java:164)在android.app.ActivityThread.main(ActivityThread.java:6626)
格里

1

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

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

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


您是从后台还是在活动中启动服务?
Mateen Chaudhry,

背景。问题是Android无法保证服务会在指定的时间内启动。有人会认为,如果您愿意的话,他们只会抛出一个错误来忽略它,但是可惜的是,不是Android。它必须是致命的。这对我们的应用程序有所帮​​助,但并未完全解决。
a54studio

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

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


0

我已经解决了用startService(intent)代替Context.startForeground()startForegound()在之后调用的问题super.OnCreate()。此外,如果在启动时启动服务,则可以启动在启动广播中启动服务的活动。尽管这不是一个永久性的解决方案,但它可以工作。


在某些小米设备中,活动无法从后台开始
Mateen Chaudhry

0

更新数据 onStartCommand(...)

onBind(...)

onBind(...)startForeground与相比,onCreate(...)这是更好的生命周期事件,因为onBind(...)传递Intent可能包含Bundle初始化所需的重要数据Service。但是,没有必要在第一次创建onStartCommand(...)时调用,Service也可以在之后的后续时间调用。

onStartCommand(...)

startForegroundonStartCommand(...)是为了更新重要的Service它已经创建一次。

创建ContextCompat.startForegroundService(...)了a之后将调用when ,并且不会调用。因此,可以通过将更新的数据传递到中以更新中的数据。ServiceonBind(...)onCreate(...)onStartCommand(...)Intent BundleService

样品

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

活动/ Fragment.kt

context?.bindService(
        Intent(context, AudioService::class.java),
        serviceConnection, Context.BIND_AUTO_CREATE)
ContextCompat.startForegroundService(
        context!!,
        Intent(context, AudioService::class.java).apply {
            action = CONTENT_SELECTED_ACTION
            putExtra(CONTENT_SELECTED_KEY, contentToPlay.content.apply {
                audioUrl = uri.toString()
            })
        })

AudioService.kt

private var uri: Uri = Uri.parse("")

override fun onBind(intent: Intent?) =
        AudioServiceBinder().apply {
            player = ExoPlayerFactory.newSimpleInstance(
                    applicationContext,
                    AudioOnlyRenderersFactory(applicationContext),
                    DefaultTrackSelector())
        }

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    intent?.let {
        when (intent.action) {
            CONTENT_SELECTED_ACTION -> it.getParcelableExtra<Content>(CONTENT_SELECTED_KEY).also { content ->
                val intentUri = Uri.parse(content.audioUrl)
                // Checks whether to update Uri passed in Intent Bundle.
                if (!intentUri.equals(uri)) {
                    uri = intentUri
                    player?.prepare(ProgressiveMediaSource.Factory(
                            DefaultDataSourceFactory(
                                    this,
                                    Util.getUserAgent(this, getString(app_name))))
                            .createMediaSource(uri))
                    player?.playWhenReady = true
                    // Calling 'startForeground' in 'buildNotification(...)'.          
                    buildNotification(intent.getParcelableExtra(CONTENT_SELECTED_KEY))
                }
            }
        }
    }
    return super.onStartCommand(intent, flags, startId)
}

// Calling 'startForeground' in 'onNotificationStarted(...)'.
private fun buildNotification(content: Content): Unit? {
    playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
            this,
            content.title,
            app_name,
            if (!content.audioUrl.isNullOrEmpty()) 1 else -1,
            object : PlayerNotificationManager.MediaDescriptionAdapter {
                override fun createCurrentContentIntent(player: Player?) = ...
                override fun getCurrentContentText(player: Player?) = ...
                override fun getCurrentContentTitle(player: Player?) = ...
                override fun getCurrentLargeIcon(player: Player?,
                                                 callback: PlayerNotificationManager.BitmapCallback?) = ...
            },
            object : PlayerNotificationManager.NotificationListener {
                override fun onNotificationStarted(notificationId: Int, notification: Notification) {
                    startForeground(notificationId, notification)
                }
                override fun onNotificationCancelled(notificationId: Int) {
                    stopForeground(true)
                    stopSelf()
                }
            })
    return playerNotificationManager.setPlayer(player)
}

-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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.