Я знаю, слишком много ответов уже опубликовано, однако правда в том, что startForegroundService нельзя исправить на уровне приложения, и вы должны прекратить его использовать. Рекомендация Google использовать API Service # startForeground () в течение 5 секунд после вызова Context # startForegroundService () - это не то, что приложение всегда может сделать.
Android запускает множество процессов одновременно, и нет никакой гарантии, что Looper вызовет вашу целевую службу, которая должна вызывать startForeground () в течение 5 секунд. Если ваша целевая служба не получила вызов в течение 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)
Как я понимаю, Лупер проанализировал здесь очередь, нашел «обидчика» и просто убил ее. Теперь система счастлива и здорова, а разработчики и пользователи - нет, но, поскольку Google ограничивает свои обязанности системой, почему они должны заботиться о последних двух? Видимо, нет. Могут ли они сделать это лучше? Конечно, например, они могли бы обслуживать диалог «Приложение занято», предлагая пользователю принять решение о том, ждать или убить приложение, но зачем это беспокоить, это не их обязанность. Главное, что система сейчас здорова.
По моим наблюдениям, это случается относительно редко, в моем случае примерно 1 сбой в месяц для пользователей 1К. Воспроизвести его невозможно, и даже если он воспроизведен, вы ничего не сможете сделать, чтобы исправить это навсегда.
В этом потоке было хорошее предложение использовать «bind» вместо «start», а затем, когда служба готова, обрабатывать onServiceConnected, но, опять же, это означает, что вообще не следует использовать вызовы startForegroundService.
Я думаю, что правильным и честным действием со стороны Google было бы сказать всем, что startForegourndServcie имеет недостаток и не должен использоваться.
Остается вопрос: что использовать вместо этого? К счастью для нас, есть JobScheduler и JobService, которые являются лучшей альтернативой для приоритетных сервисов. Это лучший вариант, потому что:
Пока выполняется задание, система удерживает блокировку от имени вашего приложения. По этой причине вам не нужно предпринимать никаких действий, чтобы гарантировать, что устройство остается активным в течение всей работы.
Это означает, что вам больше не нужно заботиться об обработке wakelocks, и поэтому он ничем не отличается от приоритетных сервисов. С точки зрения реализации, JobScheduler - это не ваша служба, а системная, предположительно, она будет правильно обрабатывать очередь, и Google никогда не прекратит свой собственный дочерний элемент :)
Samsung перешла от startForegroundService к JobScheduler и JobService в своем дополнительном протоколе Samsung (SAP). Это очень полезно, когда такие устройства, как умные часы, должны общаться с такими хостами, как телефоны, где работа должна взаимодействовать с пользователем через главный поток приложения. Поскольку задания публикуются планировщиком в главном потоке, это становится возможным. Тем не менее, вы должны помнить, что задание выполняется в главном потоке, и перенести весь тяжелый материал в другие потоки и асинхронные задачи.
Этот сервис выполняет каждое входящее задание в обработчике, запущенном в главном потоке вашего приложения. Это означает, что вы должны перенести логику выполнения в другой поток / обработчик / AsyncTask по вашему выбору
Единственная ловушка перехода на JobScheduler / JobService заключается в том, что вам нужно будет реорганизовать старый код, и это не весело. Последние два дня я потратил именно на это, чтобы использовать новую реализацию SAP от Samsung. Я посмотрю свои отчеты о сбоях и сообщу, увидит ли снова сбой. Теоретически этого не должно происходить, но всегда есть детали, о которых мы могли бы не знать.
ОБНОВЛЕНИЕ
Больше не сообщает о сбоях в Play Store. Это означает, что 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'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 - это класс, который я реализовал для обработки всех запросов, поступающих от умных часов Samsung. Это не полный код, только скелет:
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, который вам необходимо реализовать, и который обрабатывает все сообщения, приходящие с вашего устройства.
Суть в том, что это не так просто и требует некоторого изучения внутренних элементов и кодирования, но это работает, и самое главное - это не дает сбой.