Я знаю, занадто багато відповідей уже опубліковано, однак правда - startForegroundService неможливо виправити на рівні програми, і вам слід припинити його використовувати. Ця рекомендація Google використовувати API Service # startForeground () протягом 5 секунд після виклику Context # startForegroundService () - це не те, що програма завжди може зробити.
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 аварія на місяць для користувачів 1К. Відтворити це неможливо, і навіть якщо воно відтворене, ви нічого не можете зробити, щоб остаточно його виправити.
У цій темі було добре запропонувати використовувати "bind" замість "start", і тоді, коли служба готова, обробіть onServiceConnected, але знову ж таки, це означає взагалі не використовувати виклики startForegroundService.
Я думаю, що правильним та чесним дій з боку Google було б сказати всім, що startForegourndServcie має дефіцит і його не слід використовувати.
Все ще залишається питання: що використовувати замість цього? На щастя для нас, зараз є JobScheduler та JobService, які є кращою альтернативою для послуг переднього плану. Це кращий варіант, тому що:
Поки робота працює, система від імені вашого додатка проводить будильник. З цієї причини вам не потрібно вживати жодних дій, щоб гарантувати, що пристрій залишається неспаним протягом тривалості завдання.
Це означає, що вам більше не потрібно дбати про обробку годинників, і тому це не відрізняється від служб переднього плану. З точки зору впровадження, JobScheduler - це не ваша послуга, це система, імовірно, вона буде працювати з чергою правильно, і Google ніколи не закриє власну дитину :)
Samsung переключився з startForegroundService на JobScheduler та JobService у своєму протоколі аксесуарів Samsung (SAP). Це дуже корисно, коли пристрої, такі як смарт-годинники, повинні спілкуватися з такими хостами, як телефони, де завдання потрібно взаємодіяти з користувачем через основну нитку програми. Оскільки завдання розміщуються планувальником в основний потік, це стає можливим. Ви повинні пам’ятати, що ця робота виконується в основному потоці та завантажуйте всі важкі речі на інші потоки та завдання асинхронізації.
Ця служба виконує кожне вхідне завдання на оброблювачем, який працює на головному потоці програми. Це означає, що ви повинні вивантажити свою логіку виконання на інший потік / обробник / AsyncTask на ваш вибір
Єдина помилка переходу на JobScheduler / JobService полягає в тому, що вам потрібно буде перефактурувати старий код, і це не весело. Останні два дні я проводив саме так, щоб використовувати нову реалізацію Samsung SAP. Я перегляну свої звіти про аварійне завершення роботи, і повідомляю вас, чи побачите збої ще раз. Теоретично цього не повинно відбуватися, але завжди є деталі, про які ми можемо не знати.
ОНОВЛЕННЯ
Немає більше повідомлень про збої в Play Store. Це означає, що у JobScheduler / JobService не виникає такої проблеми, і перехід на цю модель - це правильний підхід для позбавлення від проблеми startForegroundService раз і назавжди. Сподіваюся, Google / Android читає це, і згодом коментує / радить / надасть офіційне керівництво для всіх.
ОНОВЛЕННЯ 2
Для тих, хто використовує SAP і запитує, як SAP V2 використовує пояснення JobService нижче.
У своєму власному коді вам потрібно ініціалізувати SAP (це Котлін):
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'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 - це клас, який я реалізував для обробки всіх запитів, що надходять від смарт-годинника 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, який вам потрібно було б реалізувати, і обробляє всі повідомлення, що надходять з вашого пристрою.
Підсумок, це не так просто і вимагає певного копання до внутрішніх даних та кодування, але це працює, і найголовніше - він не виходить з ладу.