我知道,已经发布了太多答案,但是事实是-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类,并处理来自设备的所有消息。
最重要的是,它并不是那么简单,它需要深入研究内部结构和编码,但是它是有效的,而且最重要的是,它不会崩溃。