Служба переднього плану знищена Android


85

Оновлення : Я не знайшов справжнього рішення проблеми. Що я придумав, це спосіб автоматичного повторного підключення до попереднього пристрою Bluetooth у будь-який час, коли з’єднання втрачається. Це не ідеально, але, здається, працює досить добре. Хотілося б почути ще якісь пропозиції щодо цього.

У мене майже така ж проблема, як і в цьому питанні: Служба вбита, утримуючи функцію блокування пробудження та після виклику startForeground, включаючи пристрій (Asus Transformer), тривалість часу до зупинки служби (30-45 хвилин), використання блокування пробудження, використання startForeground () і той факт, що проблема не виникає, якщо програма відкрита, коли екран вимикається.

Мій додаток підтримує з'єднання Bluetooth з іншим пристроєм і передає дані між ними, тому він повинен бути активним постійно, щоб прослуховувати дані. Користувач може запускати та зупиняти послугу за власним бажанням, і насправді це єдиний спосіб, який я застосував для запуску або зупинки послуги. Після перезапуску служби втрачається з'єднання Bluetooth з іншим пристроєм.

Відповідно до відповіді на пов'язане питання, startForeground () "зменшує ймовірність вбивства служби, але не запобігає цьому". Я розумію, що це так, однак я бачив багато прикладів інших програм, у яких немає цієї проблеми (наприклад, Tasker).

Корисність мого додатка значно зменшиться без можливості запуску служби, доки користувач не зупинить її. Чи є спосіб уникнути цього ???

Я бачу це у своєму logcat щоразу, коли службу зупиняємо:

ActivityManager: No longer want com.howettl.textab (pid 32321): hidden #16
WindowManager: WIN DEATH: Window{40e2d968 com.howettl.textab/com.howettl.textab.TexTab paused=false
ActivityManager: Scheduling restart of crashed service com.howettl.textab/.TexTabService in 5000ms

РЕДАГУВАТИ: Я також повинен зауважити, здається, це не відбувається на іншому пристрої, до якого я підключений: HTC Legend під управлінням Cyanogen

EDIT: Ось результат adb shell dumpsys activity services:

* ServiceRecord{40f632e8 com.howettl.textab/.TexTabService}

intent={cmp=com.howettl.textab/.TexTabService}

packageName=com.howettl.textab

processName=com.howettl.textab

baseDir=/data/app/com.howettl.textab-1.apk

resDir=/data/app/com.howettl.textab-1.apk

dataDir=/data/data/com.howettl.textab

app=ProcessRecord{40bb0098 2995:com.howettl.textab/10104}

isForeground=true foregroundId=2 foregroundNoti=Notification(contentView=com.howettl.textab/0x1090087 vibrate=null,sound=null,defaults=0x0,flags=0x6a)

createTime=-25m42s123ms lastActivity=-25m42s27ms

 executingStart=-25m42s27ms restartTime=-25m42s124ms

startRequested=true stopIfKilled=false callStart=true lastStartId=1

Bindings:

* IntentBindRecord{40a02618}:

  intent={cmp=com.howettl.textab/.TexTabService}

  binder=android.os.BinderProxy@40a9ff70

  requested=true received=true hasBound=true doRebind=false

  * Client AppBindRecord{40a3b780 ProcessRecord{40bb0098 2995:com.howettl.textab/10104}}

    Per-process Connections:

      ConnectionRecord{40a76920 com.howettl.textab/.TexTabService:@40b998b8}

All Connections:

  ConnectionRecord{40a76920 com.howettl.textab/.TexTabService:@40b998b8}

І вихід adb shell dumpsys activity:

* TaskRecord{40f5c050 #23 A com.howettl.textab}

numActivities=1 rootWasReset=false

affinity=com.howettl.textab

intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.howettl.textab/.TexTab}

realActivity=com.howettl.textab/.TexTab

lastActiveTime=4877757 (inactive for 702s)

* Hist #1: ActivityRecord{40a776c8 com.howettl.textab/.TexTab}

    packageName=com.howettl.textab processName=com.howettl.textab

    launchedFromUid=2000 app=ProcessRecord{40bb0098 2995:com.howettl.textab/10104}

    Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.howettl.textab/.TexTab }

    frontOfTask=true task=TaskRecord{40f5c050 #23 A com.howettl.textab}

    taskAffinity=com.howettl.textab

    realActivity=com.howettl.textab/.TexTab

    base=/data/app/com.howettl.textab-1.apk/data/app/com.howettl.textab-1.apk data=/data/data/com.howettl.textab

    labelRes=0x7f060000 icon=0x7f020000 theme=0x0

    stateNotNeeded=false componentSpecified=true isHomeActivity=false

    configuration={ scale=1.0 imsi=0/0 loc=en_CA touch=3 keys=2/1/1 nav=1/2 orien=L layout=0x10000014 uiMode=0x11 seq=6}

    launchFailed=false haveState=true icicle=Bundle[mParcelledData.dataSize=1644]

    state=STOPPED stopped=true delayedResume=false finishing=false

    keysPaused=false inHistory=true visible=false sleeping=true idle=true

    fullscreen=true noDisplay=false immersive=false launchMode=2

    frozenBeforeDestroy=false thumbnailNeeded=false

    connections=[ConnectionRecord{40a76920 com.howettl.textab/.TexTabService:@40b998b8}]

...

Proc #15: adj=prcp /F 40e75070 959:android.process.acore/10006 (provider)

          com.android.providers.contacts/.ContactsProvider2<=Proc{40bb0098 2995:com.howettl.textab/10104}

Proc #16: adj=bak+2/F 40bb0098 2995:com.howettl.textab/10104 (foreground-service)

Вони, здається, показують, що служба працює на передньому плані.


Погляньте на цю відповідь - може спрацювати для вас stackoverflow.com/a/21157035/624109
Muzikant

Відповіді:


219

Окі докі. Я пережив пекло і повернувся до цієї проблеми. Ось як далі. Є помилки. У цій публікації описується, як аналізувати помилки в реалізації та обходити проблеми.

Щоб підсумувати, ось як слід працювати. Служби, що працюють, регулярно збиратимуться та припинятимуться приблизно кожні 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 #31: adj=prcp /FS trm= 0 2205:tunein.service/u0a10068 (fg-service)

Це приклад служби переднього плану, яка зробила все правильно. Ключовим полем тут є поле "adj =". Це вказує на пріоритет, якому ваш процес був призначений ActivityManagerService після того, як все було сказано. Ви хочете, щоб це було "adj = prcp" (видима послуга переднього плану); або "adj = vis" (видимий процес із діяльністю) або "fore" (процес із передньою планією). Якщо це "adj = svc" (процес обслуговування), або "adj = svcb" (застаріла служба?), Або "adj = bak" (порожній фоновий процес), тоді ваш процес, ймовірно, є кандидатом на припинення і буде припинений не рідше, ніж кожні 30 хвилин, навіть якщо немає ніякого тиску на повернення пам'яті. Решта прапорів на рядку - це переважно інформація про діагностичну налагодження для інженерів Google. Рішення про припинення приймаються на основі полів коригування. Коротко, / FS вказує послугу на перший план; / FA вказує на процес на передньому плані з деякою діяльністю. / B позначає фонову службу. Мітка в кінці вказує загальне правило, згідно з яким процесу було призначено пріоритет. Зазвичай воно повинно відповідати полю adj =; але значення adj = можна коригувати вгору або вниз у деяких випадках за рахунок прив'язки прапорів на активних прив'язках з іншими службами чи діями.

Якщо ви спіткнулися про помилку з прив'язуючими прапорами, рядок dumpsys буде виглядати так:

  Proc #31: adj=bak /FS trm= 0 2205:tunein.service/u0a10068 (fg-service)

Зверніть увагу, як значення поля 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 

4
@Robin Davies, у мене невелике запитання. Чи справді мені потрібно телефонувати, bindService()якщо мені потрібна постійно запущена Служба? Чи недостатньо просто зателефонувати startForeground()в службу? Для зв'язку з сервером я використовую EventBus.
ar-g

Ви викликаєте Context.bindService з Activity, щоб спочатку запустити службу. Метод Service.startService викликається кодом у службі для переміщення запущеної служби у стан "переднього плану". Я вважаю, що бібліотека EventBus у певний момент викликає Context.bindService від вашого імені, щоб розпочати роботу служби. Якщо є інший спосіб запустити послугу, я з нею не знайомий.
Робін Девіс,

3
Чудовий пост! Прихильний. Я хотів додати до цього коментаря один фрагмент, який, на мою думку, є актуальним. Якщо ви хочете постійно запускати послугу, як згадував Робін, її потрібно якось запустити. Можна зателефонувати startService (служба намірів) безпосередньо всередині вашої активності, а не bindService (), а після запуску служби ви можете викликати метод startForeground (). Я називаю це в onStartCommand () класу обслуговування. Наскільки мені відомо, це повинно зробити вашу службу необмеженою, але продовжувати її працювати до вирішення проблем із ресурсами. Сподіваємось, це комусь допомагає.
Дейв,

Чудова робота!! Я хотів би додати оновлення до цього. По-перше, вихідний формат adb дещо змінився (січень 2016 р.). Я протестував цей процес на двох пристроях LG Volt 4.4.2 та Nexus 5x 6.0.1, обидва пристрої все ще страждають від помилки. Я можу відтворити проблему лише за допомогою Context.BIND_ABOVE_CLIENT: Proc # 4: cch F / S / SF trm: 0 12354: com.test / u0a78 (fg-service) Використання проблемного прапора спричиняє миттєве вбивство більшу частину часу на старшому пристрій після виходу з активності. Здається, всі інші прапори працюють нормально в обох версіях для Android.
user3259330

1
@Dave, привіт, Дейве, я використовую саме цей метод, а також повертаю START_STICKY, але моя служба завжди вмирає приблизно через годину, коли пристрій не працює. Чи маєте ви уявлення про те, що може відбуватися
Ruchir Baronia

7

Якщо в ньому сказано "більше не хочу ...", тоді в цьому процесі не активна служба, яка наразі перебуває у старті startForeground (). Переконайтеся, що ваш дзвінок до цього насправді успішний - що ви бачите опубліковане сповіщення, на той момент у журналі немає повідомлень, які б скаржились на щось і т.д. стану вашої послуги та переконайтесь, що вона насправді позначена як передній план. Крім того, якщо це правильно на передньому плані, тоді у результатах "adb shell dumpsys activity" ви побачите в розділі, що відображає налаштування OOM процесів, які ваш процес зараз знаходиться на рівні переднього плану завдяки цій службі.


Дякуємо за допомогу! Я відредагував своє запитання результатами команд, про які ви згадали. Здається, вони вказують на те, що служба працює на передньому плані.
howettl

Чи є розділ коду, який я міг би опублікувати, який може допомогти з діагностикою?
howettl

1
Її однозначно не слід вбивати, перебуваючи на передньому плані, і я точно знаю, що такі речі, як Музика у стандартній платформі, ні. Подумайте про те, щоб подати помилку з кодом, щоб відтворити проблему. Одне, на що слід звернути увагу, - це якщо ви рухаєтесь на передньому плані чи поза ним у будь-яку точку, яка може дозволити його вбити.
хакбод

1
Чи можливо, що оновлення поточного сповіщення за допомогою виклику notify () замість повторного виклику startForeground () може вивести його зі стану переднього плану? Я також увімкнув FLAG_ALERT_ONLY_ONCE у сповіщенні, якщо це важливо.
howettl

2
Безумовно, не оновлюйте його через менеджер сповіщень. Ви публікуєте це через службу і повинні продовжувати оновлювати через службу.
хакбод
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.