Android убивает службу переднего плана

85

Обновление : я не нашел верного решения проблемы. Что я придумал, так это метод автоматического переподключения к предыдущему устройству Bluetooth в любое время, когда соединение потеряно. Это не идеально, но, похоже, работает неплохо. Я хотел бы услышать больше предложений по этому поводу.

У меня почти такая же проблема, как и в этом вопросе: служба убита при удерживании блокировки пробуждения и после вызова startForeground, включая устройство (Asus Transformer), время до остановки службы (30-45 минут), использование wake lock, использование startForeground () и тот факт, что проблема не возникает, если приложение открыто, когда экран гаснет.

Мое приложение поддерживает соединение Bluetooth с другим устройством и отправляет данные между ними, поэтому оно должно быть постоянно активным для прослушивания данных. Пользователь может запускать и останавливать службу по своему желанию, и фактически это единственный способ, который я реализовал для запуска или остановки службы. После перезапуска службы соединение Bluetooth с другим устройством теряется.

Согласно ответу на связанный вопрос, startForeground () «снижает вероятность того, что служба будет уничтожена, но не предотвращает это». Я понимаю, что это так, однако я видел много примеров других приложений, в которых нет этой проблемы (например, Tasker).

Полезность моего приложения будет значительно снижена без возможности запуска службы до остановки пользователем. Есть ли способ избежать этого ???

Я вижу это в моем журнале всякий раз, когда служба останавливается:

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

РЕДАКТИРОВАТЬ: Вот результат 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)

Похоже, они показывают, что служба работает на переднем плане.

Howettl
источник
Взгляните на этот ответ - может сработать для вас 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 dumpsys activity процессы> tmp.txt

Используйте блокнот (не блокнот / запись), чтобы изучить содержимое.

Сначала убедитесь, что вам удалось успешно запустить службу в состоянии переднего плана. Первый раздел файла dumpsys содержит описание свойств ActivityManager для каждого процесса. Найдите в первом разделе файла dumpsys строку вроде следующей, которая соответствует вашему приложению:

APP UID 10068 ProcessRecord {41937d40 2205: tunein.service / u0a10068}

Убедитесь, что foregroundServices = true в следующем разделе. Не беспокойтесь о скрытых и пустых настройках; они описывают состояние Activity в процессе и не кажутся особенно актуальными для процессов, в которых есть сервисы. Если 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 "бак", и долго он не проживет .. Проще говоря, это баг.

Итак, цель состоит в том, чтобы ваш процесс всегда получал "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 находится на переднем плане, без какого-либо видимого вреда, когда Activity приостановлено или завершено.

Но в целом стратегия состоит в том, чтобы настроить флаги bindService до тех пор, пока sysdump не укажет, что ваш процесс получил правильный приоритет.

Для моих целей, используя Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT для привязок операций к услугам и 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
@ Робин Дэвис, у меня небольшой вопрос. Мне действительно нужно звонить, bindService()если мне нужен постоянно работающий Сервис? Мало ли просто позвонить startForeground()в сервис? Для связи с сервером я использовал EventBus.
ar-g
Вы вызываете Context.bindService из Activity, чтобы запустить службу в первую очередь. Метод Service.startService вызывается кодом в службе, чтобы переместить запущенную службу в состояние «переднего плана». Я предполагаю, что библиотека EventBus в какой-то момент вызывает Context.bindService от вашего имени, чтобы запустить службу. Если есть другой способ запустить службу, я с ним не знаком.
Робин Дэвис
3
Отличный пост! Проголосовали. Я хотел добавить к этому комментарию один фрагмент, который я считаю актуальным. Если вам нужна постоянно работающая служба, как упоминал Робин, вам нужно как-то ее запустить. Можно вызвать startService (службу Intent) непосредственно внутри вашей активности, а не 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) Использование флага Troubled вызывает мгновенное уничтожение большей части времени на более старых устройство после выхода из активности. Все остальные флаги работают нормально на обеих версиях Android.
user3259330 01
1
@ Дэйв, привет, Дэйв, я использую именно этот метод, а также возвращаю START_STICKY, но моя служба всегда умирает через час или около того, когда устройство не используется. У тебя есть идеи, что может происходить?
Ручир Барония
7

Если он говорит «больше не хочу ...», то в этом процессе нет активной службы, которая в настоящее время находится в состоянии startForeground (). Убедитесь, что ваш вызов к этому действительно успешен - вы видите опубликованное уведомление, в этот момент в журнале нет сообщений с жалобами на что-либо и т. Д. Также используйте "службы действий dumpsys оболочки adb", чтобы посмотреть на состояние вашей службы и убедитесь, что он действительно отмечен как передний план. Также, если это правильно, то в выводе «adb shell dumpsys activity» вы увидите раздел, показывающий OOM adj для процессов, которые ваш процесс в настоящее время находится на уровне переднего плана из-за этой службы.

хакбод
источник
Спасибо за помощь! Я отредактировал свой вопрос с выводом упомянутых вами команд. Кажется, они указывают на то, что служба работает на переднем плане.
howettl
Могу ли я опубликовать фрагмент кода, который может помочь в диагностике?
howettl
1
Его точно не следует убивать, пока он находится на переднем плане, и я точно знаю, что такие вещи, как Музыка, на стандартной платформе - нет. Рассмотрите возможность регистрации ошибки в коде, чтобы воспроизвести проблему. Одна вещь, на которую стоит обратить внимание, - это если вы входите и выходите с переднего плана в любой момент, который может позволить его убить.
hackbod
1
Возможно ли, чтобы обновление текущего уведомления путем вызова notify () вместо повторного вызова startForeground () могло вывести его из состояния переднего плана? У меня также включен FLAG_ALERT_ONLY_ONCE в уведомлении, если это имеет значение.
howettl
2
Однозначно не обновляйте через диспетчер уведомлений. Вы публикуете это через службу и должны продолжать обновлять ее через службу.
hackbod