我知道,已经发布了太多答案,但事实是 - 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 已经分析了这里的队列,找到了一个“滥用者”并简单地将其杀死。系统现在是快乐和健康的,而开发者和用户却不是,但既然谷歌将他们的责任限制在系统上,他们为什么要关心后两者呢?显然他们没有。他们能做得更好吗?当然,例如,他们可以提供“应用程序正忙”对话框,要求用户做出等待或终止应用程序的决定,但何苦呢,这不是他们的责任。最主要的是系统现在很健康。
根据我的观察,这种情况发生的相对较少,在我的情况下,对于 1K 用户来说,一个月内大约会发生 1 次崩溃。复制它是不可能的,即使它被复制了,你也无法永久修复它。
在这个线程中有一个很好的建议是使用“bind”而不是“start”,然后当服务准备好时,处理 onServiceConnected,但同样,这意味着根本不使用 startForegroundService 调用。
我认为,谷歌方面正确和诚实的做法是告诉大家 startForegourndServcie 有缺陷,不应该使用。
问题仍然存在:改用什么?对我们来说幸运的是,现在有 JobScheduler 和 JobService,它们是前台服务的更好替代方案。这是一个更好的选择,因为:
在作业运行时,系统会代表您的应用程序持有唤醒锁。因此,您无需采取任何措施来保证设备在作业期间保持唤醒状态。
这意味着您不再需要关心处理唤醒锁,这就是它与前台服务没有区别的原因。从实现的角度来看,JobScheduler 不是您的服务,它是系统的服务,大概它会正确处理队列,Google 永远不会终止自己的孩子:)
三星已在其三星附件协议 (SAP) 中从 startForegroundService 切换到 JobScheduler 和 JobService。当智能手表等设备需要与手机等主机通信时,这非常有用,因为工作确实需要通过应用程序的主线程与用户交互。由于作业由调度程序发布到主线程,因此成为可能。您应该记住该作业在主线程上运行,并将所有繁重的工作卸载到其他线程和异步任务。
此服务在应用程序主线程上运行的处理程序上执行每个传入作业。这意味着您必须将执行逻辑卸载到您选择的另一个线程/处理程序/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)
现在你需要反编译三星的代码,看看里面发生了什么。在 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);
SJobService 只是 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 类,您需要实现该类并处理来自您设备的所有消息。
最重要的是,它并不是那么简单,它需要对内部结构和编码进行一些挖掘,但它可以工作,最重要的是 - 它不会崩溃。