我知道,已经有太多答案发布了,然而事实是- 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=
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,它们是前台服务的更好选择。这是一个更好的选择,因为:
当作业正在运行时,系统代表您的应用程序持有wakelock。因此,您不需要采取任何措施来保证设备在作业期间保持唤醒状态。
这意味着您不需要再关心处理wakelocks了,这就是为什么与前台服务没有区别。从实现的角度来看,JobScheduler不是您的服务,而是系统的服务,它应该会正确地处理队列,并且谷歌永远不会终止自己的子进程 :)
三星已经从startForegroundService切换到了JobScheduler和JobService,用于其Samsung Accessory Protocol (SAP)。当像智能手表这样的设备需要与像手机这样的主机交互时,这非常有帮助,其中作业确实需要通过应用程序的主线程与用户交互。由于作业是由调度程序发布到主线程的,所以这变得可能。但是请记住,作业正在主线程上运行,请将所有繁重的工作卸载到其他线程和异步任务中。
该服务在运行每个传入的作业时,都会在您的应用程序的主线程上运行一个Handler。这意味着您必须将执行逻辑卸载到另一个线程/ handler / AsyncTask 中
切换到JobScheduler / JobService的唯一缺陷是您需要重构旧代码,这不是令人愉快的事情。我花了过去两天的时间来做到这一点,以使用新的三星SAP实现。我会监视我的崩溃报告,并告诉您是否再次看到崩溃。理论上不应该发生,但总有我们可能不知道的细节。
更新
Play Store没有报道更多崩溃。这意味着JobScheduler / JobService没有这样的问题,切换到此模
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 的 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 类的实现,它处理来自设备的所有消息。
总之,这并不简单,需要深入了解内部和编码,但它能够正常工作,最重要的是——它不会崩溃。