Окі докі. Я пережив пекло і повернувся до цієї проблеми. Ось як далі. Є помилки. У цій публікації описується, як аналізувати помилки в реалізації та обходити проблеми.
Щоб підсумувати, ось як слід працювати. Служби, що працюють, регулярно збиратимуться та припинятимуться приблизно кожні 30 хвилин. Служби, які хочуть залишатися в живих довше цього, повинні зателефонувати до служби Service.startForeground, яка розміщує сповіщення на панелі сповіщень, щоб користувачі знали, що ваша служба постійно працює і потенційно висмоктує заряд акумулятора. Тільки 3 сервісні процеси можуть призначити себе послугами переднього плану в будь-який момент часу. Якщо існує більше трьох служб переднього плану, Android призначить найстарішу службу кандидатом на видалення та припинення роботи.
На жаль, в Android є помилки щодо пріоритетності служб переднього плану, які викликаються різними комбінаціями прапорів прив'язки служб. Незважаючи на те, що ви правильно призначили свою послугу як службу на перший план, Android може припинити її в будь-якому випадку, якщо будь-які з’єднання зі службами у вашому процесі коли-небудь були здійснені з певними комбінаціями прапорів прив’язки. Деталі наведені нижче.
Зауважте, що дуже мало послуг повинні бути послугами на перший план. Як правило, вам потрібно бути службою на передньому плані, лише якщо у вас є постійно активне або тривале якесь з’єднання з Інтернетом, яке користувачі можуть увімкнути та вимкнути або скасувати. Приклади служб, яким потрібен статус переднього плану: сервери UPNP, тривале завантаження дуже великих файлів, синхронізація файлових систем за допомогою Wi-Fi та відтворення музики.
Якщо ви просто опитуєтеся іноді або чекаєте на системних приймачах мовлення або системних подіях, вам було б краще пробудити службу за таймером або у відповідь на приймачі широкомовних повідомлень, а потім дозволити своїй службі вмерти після завершення. Ось така розроблена поведінка послуг. Якщо ви просто мусите залишитися в живих, читайте далі.
Поставивши прапорці з добре відомими вимогами (наприклад, викликаючи Service.startForeground), наступне місце, на яке слід звернути увагу, - це прапори, які ви використовуєте у викликах Context.bindService. Прапори, що використовуються для прив'язки, впливають на пріоритет цільового процесу обслуговування різними несподіваними способами. Зокрема, використання певних прапорів прив’язки може призвести до того, що Android неправильно поверне вашу службу переднього плану до звичайної служби. Код, що використовується для присвоєння пріоритету процесу, був досить сильно розроблений. Варто відзначити, що в API 14+ є виправлення, які можуть спричинити помилки при використанні старих прапорів прив’язки; і є певні помилки в 4.2.1.
Вашим другом у всьому цьому є утиліта sysdump, за допомогою якої можна з’ясувати, який пріоритет менеджер активності призначив вашому процесу обслуговування, та визначити випадки, коли він призначив неправильний пріоритет. Запустіть і запустіть службу, а потім виконайте таку команду з командного рядка на вашому хост-комп’ютері:
adb shell shell dumpsys процеси діяльності> tmp.txt
Використовуйте блокнот (не wordpad / write) для вивчення вмісту.
Спочатку переконайтеся, що вам вдалося запустити свою службу в передньому плані. Перший розділ файлу dumpsys містить опис властивостей ActivityManager для кожного процесу. У першому розділі файлу dumpsys знайдіть рядок, наведений нижче, який відповідає вашій програмі:
APP UID 10068 ProcessRecord {41937d40 2205: tunein.service / u0a10068}
Переконайтеся, що foregroundServices = true у наступному розділі. Не турбуйтеся про приховані та порожні налаштування; вони описують стан діяльності у процесі, і, здається, не є особливо актуальним для процесів із послугами в них. Якщо foregroundService не відповідає дійсності, вам потрібно зателефонувати до Service.startForeground, щоб зробити це істинним.
Наступне, на що вам потрібно звернути увагу, - це розділ біля кінця файлу під назвою "Обробити список LRU (відсортований за oom_adj):". Записи в цьому списку дозволяють визначити, чи Android насправді класифікував вашу програму як послугу на перший план. Якщо ваш процес знаходиться внизу у цьому списку, це найкращий кандидат для знищення. Якщо ваш процес знаходиться у верхній частині списку, він практично незнищуваний.
Давайте розглянемо рядок у цій таблиці:
Proc
Це приклад служби переднього плану, яка зробила все правильно. Ключовим полем тут є поле "adj =". Це вказує на пріоритет, якому ваш процес був призначений ActivityManagerService після того, як все було сказано. Ви хочете, щоб це було "adj = prcp" (видима послуга переднього плану); або "adj = vis" (видимий процес із діяльністю) або "fore" (процес із передньою планією). Якщо це "adj = svc" (процес обслуговування), або "adj = svcb" (застаріла служба?), Або "adj = bak" (порожній фоновий процес), тоді ваш процес, ймовірно, є кандидатом на припинення і буде припинений не рідше, ніж кожні 30 хвилин, навіть якщо немає ніякого тиску на повернення пам'яті. Решта прапорів на рядку - це переважно інформація про діагностичну налагодження для інженерів Google. Рішення про припинення приймаються на основі полів коригування. Коротко, / FS вказує послугу на перший план; / FA вказує на процес на передньому плані з деякою діяльністю. / B позначає фонову службу. Мітка в кінці вказує загальне правило, згідно з яким процесу було призначено пріоритет. Зазвичай воно повинно відповідати полю adj =; але значення adj = можна коригувати вгору або вниз у деяких випадках за рахунок прив'язки прапорів на активних прив'язках з іншими службами чи діями.
Якщо ви спіткнулися про помилку з прив'язуючими прапорами, рядок dumpsys буде виглядати так:
Proc
Зверніть увагу, як значення поля adj неправильно встановлено на "adj = bak" (порожній фоновий процес), що приблизно перекладається як "будь ласка, припиніть мене зараз, щоб я міг припинити це безглузде існування" для цілей очищення процесу. Також зверніть увагу на прапорець (fg-service) у кінці рядка, який вказує, що "правила наземної служби були використані для визначення параметра" adj ". Незважаючи на те, що були використані правила fg-service, цьому процесу було призначено параметр adj "bak", і він не проживе довго. Очевидно, це помилка.
Тож мета полягає в тому, щоб ваш процес завжди отримував "adj = prcp" (або краще). А методом досягнення цієї мети є налаштування прапорів прив’язки, доки не вдасться уникнути помилок при призначенні пріоритету.
Ось помилки, про які я знаю. (1) Якщо БУДЬ-ЯКА послуга чи діяльність коли-небудь прив'язувалась до служби за допомогою Context.BIND_ABOVE_CLIENT, ви ризикуєте, що параметр adj = буде знижено до "bak", навіть якщо це прив'язка більше не активна. Це особливо вірно, якщо у вас також є прив'язка між службами. Явна помилка у джерелах 4.2.1. (2) Безумовно, ніколи не використовуйте BIND_ABOVE_CLIENT для прив'язки послуги до послуги. Не використовуйте його також для з'єднань активності з послугою. Прапор, який використовується для реалізації поведінки BIND_ABOVE_CLIENT, здається, встановлюється для кожного процесу, а не для кожного підключення, тому він запускає помилки з прив'язками послуги до послуги, навіть якщо немає активної діяльності до служби прив'язка із встановленим прапором. Здається також, що виникають проблеми з встановленням пріоритету, коли в процесі є кілька служб із прив’язками між послугами. Використання Context.BIND_WAIVE_PRIORITY (API 14) для прив'язок між послугами, здається, допомагає. Context.BIND_IMPORTANT видається більш-менш гарною ідеєю при прив’язуванні від Activity до служби. Це зробить ваш пріоритет процесу на ступінь вище, коли Діяльність буде на передньому плані, не завдаючи жодної видимої шкоди, коли Діяльність буде призупинена або закінчена.
Але загалом, стратегія полягає у налаштуванні ваших прапорів bindService, доки sysdump не вкаже, що ваш процес отримав правильний пріоритет.
Для моїх цілей використання Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT для прив'язок Activity-to-service та Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY для прив'язок між послугами, схоже, робить правильно. Ваш пробіг може відрізнятися.
Мій додаток досить складний: дві фонові служби, кожна з яких може самостійно утримувати стан обслуговування переднього плану, плюс третя, яка також може приймати стан служби переднього плану; дві послуги пов'язані між собою умовно; третій прив'язується до першого, завжди. Крім того, Activites працює в окремому процесі (робить анімацію більш плавною). Запуск діяльності та служб у тому самому процесі, здавалося, не змінив.
Реалізація правил для очищення процесів (і вихідний код, що використовується для генерації вмісту файлів sysdump) можна знайти в основному файлі android
frameworks\base\services\java\com\android\server\am\ActivityManagerService.java.
Приємного шансу.
PS: Ось інтерпретація рядків sysdump для Android 5.0. Я з ними не працював, тож робіть із них, що хочете. Я вважаю, що ви хочете, щоб 4 були "A" або "S", а 5 мали бути "IF" або "IB", а 1 - якомога нижче (можливо, нижче 3, оскільки лише 3 процеси обслуговування на передньому плані залишаються активними у конфігурації за замовчуванням).
Example:
Proc # : prcp F/S/IF trm: 0 31719: neirotech.cerebrum.attention:blePrcs/u0a77 (fg-service)
Format:
Proc # {1}: {2} {3}/{4}/{5} trm: {6} {7}: {8}/{9} ({10}
1: Order in list: lower is less likely to get trimmed.
2: Not sure.
3:
B: Process.THREAD_GROUP_BG_NONINTERACTIVE
F: Process.THREAD_GROUP_DEFAULT
4:
A: Foreground Activity
S: Foreground Service
' ': Other.
5:
-1: procState = "N ";
ActivityManager.PROCESS_STATE_PERSISTENT: procState = "P ";
ActivityManager.PROCESS_STATE_PERSISTENT_UI:procState = "PU";
ActivityManager.PROCESS_STATE_TOP: procState = "T ";
ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND: procState = "IF";
ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND: procState = "IB";
ActivityManager.PROCESS_STATE_BACKUP:procState = "BU";
ActivityManager.PROCESS_STATE_HEAVY_WEIGHT: procState = "HW";
ActivityManager.PROCESS_STATE_SERVICE: procState = "S ";
ActivityManager.PROCESS_STATE_RECEIVER: procState = "R ";
ActivityManager.PROCESS_STATE_HOME: procState = "HO";
ActivityManager.PROCESS_STATE_LAST_ACTIVITY: procState = "LA";
ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: procState = "CA";
ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: procState = "Ca";
ActivityManager.PROCESS_STATE_CACHED_EMPTY: procState = "CE";
{6}: trimMemoryLevel
{8} Process ID.
{9} process name
{10} appUid