我在Android O操作系统上使用服务类。

我计划在后台使用服务。

Android文档指出

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

如果使用startForegroundService(),服务抛出以下错误。

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

这有什么问题?


当前回答

以下是谷歌在Android 12上的行为变化:

To provide a streamlined experience for short-running foreground services on Android 12, the system can delay the display of foreground service notifications by 10 seconds for certain foreground services. This change gives short-lived tasks a chance to complete before their notifications appear.

解决方案:在onCreate()中为你使用的服务调用start前台()

其他回答

在我的例子中,我称之为上下文。在服务有机会在内部调用start前台之前,停止服务(外部服务)。

在我的情况下,通知ID我传递给startForeground方法是'0',因为这个错误来了。

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

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

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

以下是谷歌在Android 8.0上的行为变化:

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

解决方案: 在onCreate()中为你使用的服务调用start前台()

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

我有一个解决这个问题的办法。我已经在自己的应用(DAU超过30万)中验证了这一修复方法,它至少可以减少95%的这种崩溃,但仍然不能100%避免这个问题。

即使您确保在服务启动后调用start前台(),也会发生此问题。这可能是因为在很多情况下,服务创建和初始化过程已经花费了超过5秒的时间,那么无论何时何地调用startForeground()方法,这个崩溃都是不可避免的。

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

Do not use startForegroundService at the first place, use bindService() with auto_create flag. It will wait for the service initialization. Here is the code, my sample service is 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); Then here is MusicBinder implementation: /** * 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(); } } The most important part, MusicService implementation, forceForeground() method will ensure that startForeground() method is called just after service start: 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) { Notification notification = mNotificationHandler.createNotification(this); // call startForeground just after service start. startForeground(Constants.NOTIFICATION_ID, notification); } } } If you want to run the step 1 code snippet in a pending intent, such as if you want to start a foreground service in a widget (a click on widget button) without opening your app, you can wrap the code snippet in a broadcast receiver, and fire a broadcast event instead of start service command.

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

我知道,已经发布了太多的答案,但事实是——startForegroundService不能在应用程序级别上被修复,你应该停止使用它。谷歌建议在调用Context# start前台服务()后5秒内使用service# start前台()API,这并不是应用程序总能做到的。

Android同时运行很多进程,并不能保证Looper会在5秒内调用你的目标服务start前台()。如果您的目标服务在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)

As I understand, Looper has analyzed the queue here, found an "abuser" and simply killed it. The system is happy and healthy now, while developers and users are not, but since Google limits their responsibilities to the system, why should they care about the latter two? Apparently they don't. Could they make it better? Of course, e.g. they could've served "Application is busy" dialog, asking a user to make a decision about waiting or killing the app, but why bother, it's not their responsibility. The main thing is that the system is healthy now.

根据我的观察,这种情况发生得相对较少,在我的案例中,每个月大约有1个用户崩溃。复制它是不可能的,即使它被复制了,你也无法永久性地修复它。

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

我认为,从谷歌方面正确和诚实的行动是告诉每个人startForegourndServcie有缺陷,不应该使用。

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

当作业正在运行时,系统代表您的作业持有一个wakelock 因此,您不需要做任何保证 设备在作业期间保持清醒状态。

这意味着你不再需要关心如何处理wakelocks,这就是为什么它与前台服务没有什么不同。从实现的角度来看,JobScheduler不是你的服务,它是一个系统的服务,假设它将正确地处理队列,谷歌永远不会终止自己的子服务:)

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

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

切换到JobScheduler/JobService的唯一缺陷是需要重构旧代码,这并不有趣。我花了两天时间来使用三星的新SAP实现。我会看我的坠机报告,如果再次看到坠机,我会告诉你。从理论上讲,这是不应该发生的,但总有一些细节我们可能没有意识到。

更新 Play Store不再报告崩溃。这意味着JobScheduler/JobService不存在这样的问题,切换到这个模型是彻底摆脱startForegroundService问题的正确方法。我希望谷歌/Android会读到它,并最终为大家提供评论/建议/官方指导。

更新2

对于那些使用SAP并询问SAP V2如何使用JobService的人,下面将给出解释。

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

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

现在你需要反编译三星的代码,看看里面发生了什么。在SAAgentV2中,看一下requestAgent实现和下面这行代码:

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

where d defined as below

private SAAdapter d;

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

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

SAJobService只是Android'd 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'd 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类,您需要实现这个类并处理来自设备的所有消息。

底线,它不是那么简单,它需要一些挖掘内部和编码,但它工作,最重要的是-它不会崩溃。