Подтвердить что ты не робот

Почему мой Android-сервис перезапускается, когда процесс был убит, хотя я использовал START_NOT_STICKY?

В моем приложении используется шаблон, в котором я запускаю службу с Context # startService(), а также привязываюсь к ней с помощью Контекст # bindService(). Это значит, что я могу контролировать время жизни службы независимо от того, привязаны ли к ней какие-либо клиенты. Однако недавно я заметил, что всякий раз, когда мое приложение убивает система, оно в скором времени перезапускает любые запущенные службы. На этом этапе службе никогда не будет сказано останавливаться, и это вызывает утечку батареи всякий раз, когда это происходит. Вот минимальный пример:

Я нашел кого-то с аналогичной проблемой здесь, но он никогда не был диагностирован или не разрешен.

Услуги:

@Override
public void onCreate() {
    Toast.makeText(this, "onCreate", Toast.LENGTH_LONG).show();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    return START_NOT_STICKY;
}

@Override
public IBinder onBind(Intent intent) {
    return new Binder();
}

Активность:

@Override
protected void onStart() {
    super.onStart();
    Intent service = new Intent(this, BoundService.class);
    startService(service);
    bindService(service, mServiceConnection, 0);
}

@Override
protected void onStop() {
    unbindService(mServiceConnection);
    Toast.makeText(this, "unbindService", Toast.LENGTH_SHORT).show();
    super.onStop();
}

Чтобы проверить это, я запустил приложение, которое запустило сервис и привязалось к нему. Затем я отступил от приложения, которое отвязывает (но не работает). Тогда я сделал

$ adb shell am kill com.tavianator.servicerestart

и, конечно, через 5 секунд появится надпись "onCreate", указывающая, что служба снова запустилась. Logcat показывает это:

$ adb logcat | grep BoundService
W/ActivityManager(  306): Scheduling restart of crashed service com.tavianator.servicerestart/.BoundService in 5000ms
I/ActivityManager(  306): Start proc com.tavianator.servicerestart for service com.tavianator.servicerestart/.BoundService: pid=20900 uid=10096 gids={1028}

Если заменить шаблон startService() на BIND_AUTO_CREATE, проблема не возникает (даже если я врежу приложение, пока оно все еще связано с сервисом). Он также работает, если я никогда не свяжусь с сервисом. Но комбинация start, bind и unbind, кажется, никогда не позволяет моему служению умереть.

Использование dumpsys перед убийством приложения показывает это:

$ adb shell dumpsys activity services com.tavianator.servicerestart
ACTIVITY MANAGER SERVICES (dumpsys activity services)
  Active services:
  * ServiceRecord{43099410 com.tavianator.servicerestart/.BoundService}
    intent={cmp=com.tavianator.servicerestart/.BoundService}
    packageName=com.tavianator.servicerestart
    processName=com.tavianator.servicerestart
    baseDir=/data/app/com.tavianator.servicerestart-2.apk
    dataDir=/data/data/com.tavianator.servicerestart
    app=ProcessRecord{424fb5c8 20473:com.tavianator.servicerestart/u0a96}
    createTime=-20s825ms lastActivity=-20s825ms
    executingStart=-5s0ms restartTime=-20s825ms
    startRequested=true stopIfKilled=true callStart=true lastStartId=1
    Bindings:
    * IntentBindRecord{42e5e7c0}:
      intent={cmp=com.tavianator.servicerestart/.BoundService}
      [email protected]
      requested=true received=true hasBound=false doRebind=false
4b9b3361

Ответ 1

Посмотрите на этот раздел : изменения жизненного цикла службы (начиная с версии 1.6)

обновляется

после некоторых исследований (создали проект, запустите описанную команду шаг за шагом и т.д.), я обнаружил, что код работает точно так, как описано в "Изменения жизненного цикла службы"

что я нашел:
adb shell am kill com.tavianator.servicerestart убивает ничего
(попробуйте adb shell, затем в оболочке am kill com.tavianator.servicerestart
Вы столкнетесь с сообщением Error: Unknown command: kill)

так, Запустите приложение,
run adb shell
в командной строке командной строки ps
найти номер PID вашего приложения
в командной строке командной строки kill <app_xx_PID>
где находится ваш номер PID Повторить действия по убийству для обслуживания (если он работает в своем собственном процессе)
проверить, работает ли сервис (не должен), перезапустить через 5-7 секунд
обновление Одно решение (недостаточно хорошее, но применимое в некоторых случаях) stopSelf() например:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Toast.makeText(this, "onStartCommand", Toast.LENGTH_LONG).show();
    stopSelf();
    return START_NOT_STICKY;
}


обновление Обновленное решение

void writeState(int state) {
    Editor editor = getSharedPreferences("serviceStart", MODE_MULTI_PROCESS)
            .edit();
    editor.clear();
    editor.putInt("normalStart", state);
    editor.commit();
}

int getState() {
    return getApplicationContext().getSharedPreferences("serviceStart",
            MODE_MULTI_PROCESS).getInt("normalStart", 1);
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if (getState() == 0) {
        writeState(1);
        stopSelf();
    } else {
        writeState(0);
        Toast.makeText(this, "onStartCommand", Toast.LENGTH_LONG).show();
    }
    return START_NOT_STICKY;
}

Почему сервис становится рестриктированным, когда процесс убит?

В соответствии с этим :

Когда служба запускается, у нее есть жизненный цикл, который не зависит от компонент, который запустил его, и служба может работать в фон неопределенно, даже если компонент, который начал его, уничтожены. Таким образом, служба должна останавливаться, когда ее работа выполняется путем вызова stopSelf(), или другой компонент может остановить его вызов stopService().
Внимание. Важно, чтобы ваш приложение прекращает свои услуги, когда оно работает, чтобы избежать теряя ресурсы системы и потребляя энергию батареи. Если необходимо, другие компоненты могут остановить службу, вызвав stopService(). Даже если вы включите привязку к службе, вы всегда должны остановить сервис, если он когда-либо получил вызов onStartCommand()

из другого документа говорится:

* START_NOT_STICKY * - Если система убивает службу после возвращения onStartCommand(), не воссоздавайте службу, если только в ожидании намерений доставить. Это самый безопасный вариант, чтобы избежать запускать службу, когда это не необходимо, и когда ваше приложение может просто перезапустите любые незавершенные задания.

Итак, прочитав этот документ и некоторые эксперименты, я думаю, что система рассматривает вручную убитые службы как незавершенные (crashed: @see W/ActivityManager(306): Scheduling restart of crashed service) и перезапускает их, несмотря на значение, возвращаемое onStartCommand.


stopSelf() или stopService() - нет перезагрузки, почему бы не выполнить работу?

Ответ 2

Я считаю, что проблема заключается в том, что для службы, которая была запущена при вызове startService(), внутренний учет для привязок (затронутых вызовами bindService()/unbindService()) каким-то образом препятствует тому, чтобы служба была сбита правильно после того, как он запланирован для перезапуска, когда процесс будет убит.

В зависимости от ваших предпочтений, кажется, что есть три альтернативы, чтобы избежать этой проблемы (вы упомянули первые два уже в своем вопросе):

  • Используйте только startService()/stopService(), не используйте bindService()/unbindService() вообще.

  • Используйте только bindService()/unbindService()BIND_AUTO_CREATE), не используйте startService()/stopService() вообще.

  • Если вам нужно startService()/stopService() и bindService()/unbindService(), переопределите onUnbind() в вашем сервисе и вызовите stopSelf() из него:

    @Override
    public boolean onUnbind(Intent intent) {
        stopSelf();
        return super.onUnbind(intent);
    }
    

Из моего тестирования:

  • Использование первого варианта приведет к печати этой строки в журнале:

    W/ActivityManager (nnn): Планирование перезапуска аварийной службы xxxx в 5000 мс

    но после этого служба не будет перезапущена.

  • Вторая альтернатива приведет к уничтожению вашего сервиса после того, как вы отвяжете (в Activity onStop() в вашем примере).

  • Третья альтернатива в основном заставит ваш код вести себя как вторая альтернатива (без необходимости многого менять).

Надеюсь, это поможет!

Ответ 3

Как пересмотреть ваш шаблон?

Флаг START_NOT_STICKY появился в какой-то момент во время эволюции Android (после 1.6?). Скорее всего, вы столкнулись с некоторой тонкой регрессией в жизненном цикле обслуживания, которая была введена в то время. Таким образом, даже если на данный момент обнаружено не менее тонкое решение - и магически проверено для работы на всех возможных устройствах - это не обязательно будет работать в новых версиях Android.

Во всяком случае, этот шаблон жизненного цикла выглядит необычно, и это может быть самой причиной вашего столкновения с этой экзотической проблемой.

Рассмотрим два случая.

  • Обычный (?). Некоторые действия начинают службу. Вскоре после этого он привязывается, используется, несвязан - тогда что? Как-то остановился? Почему в этом случае нет значительного разряда батареи?

  • Аномальные. Служба убита в некоторой (случайной?) Точке. При перезапуске он реализует некоторые неправильные предположения и остается активным, делая что-то, что истощает батарею?

Все выглядит довольно странно.

Я бы проанализировал параллельные процессы в вашем продукте. Все существенные случаи. Некоторые диаграммы и т.д. Скорее всего, в результате сама потребность в таком шаблоне будет устранена.

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