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

START_STICKY не работает на Android KitKat

В одном из моих приложений есть служба backgrouod, которая использует код возврата START_STICKY из onStartCommand для автоматического перезапуска, когда система убивает его. Похоже, что это больше не работает на Android KitKat. Есть ли решение для этого? Должен ли я делать что-то другое в Kitkat, чтобы поддерживать работу?

Примечание. Аналогичная дискуссия о группе Android-Devlopers о том, как использовать приложение из недавнего списка приложений, ведет. Могут ли быть связаны эти два вопроса? https://groups.google.com/forum/#!topic/android-developers/H-DSQ4-tiac

Изменить: видели, что на Android-трекере обнаружены ошибки:

https://code.google.com/p/android/issues/detail?id=63793 https://code.google.com/p/android/issues/detail?id=63618

Edit2: То же самое происходит, даже если служба работает с помощью startForeground, в отдельном процессе и с флагом android:stopWithTask="false" в файле AndroidManifest.xml...

Edit3: больше связанных ошибок на Android-трекер:

https://code.google.com/p/android/issues/detail?id=62091 https://code.google.com/p/android/issues/detail?id=53313 https://code.google.com/p/android/issues/detail?id=104308

Есть ли какое-то обходное решение, чтобы получить предыдущее поведение?

4b9b3361

Ответ 1

Это не 100% -ное рабочее решение, но оно лучше всего, поскольку оно почти полностью устраняет проблему. До сих пор я интегрировал это решение вместе с переопределением onTaskRemoved (см. этот ответ) и уведомлением о сохранении (см. this ответить). Дополнительные ответы очень приветствуются!

После дальнейшего исследования кажется, что ошибка уже существует в желе Bean и выглядит как есть решение для этого (по крайней мере, в моем случае, что, похоже, работает. будет продолжать тестировать и обновлять ответ, если это необходимо).

Из того, что я наблюдал, это происходит только в службах, которые принимают широковещательные передачи, установленные AlarmManager.

Чтобы воспроизвести ошибку, выполните следующие действия:

  • Запустите приложение
  • запустите службу как службу переднего плана (используйте startForeground для этого) из приложения
  • Проведите по экрану из списка "Недавние приложения"
  • Отправьте широковещательную рассылку, обрабатываемую службой
  • Служба убита!

Используя adb shell dumpsys >C:\dumpsys.txt, вы можете отслеживать состояние службы между различными шагами. (ищите Process LRU list в выводе dumpsys) на шагах 2 и 3 вы увидите что-то вроде этого:

Proc # 2: prcp  F/S/IF trm: 0 11073:<your process name>/u0a102 (fg-service)

В частности, обратите внимание на F/S/IF и (fg-service), которые указывают, что служба работает как служба переднего плана (подробнее о том, как анализировать dumpsys по этой ссылке: fooobar.com/questions/87373/...).

После шага 4 вы не увидите свою службу в Process LRU list. Вместо этого вы можете посмотреть лог-код устройства, и вы увидите следующее:

I/ActivityManager(449): Killing 11073:<your process name>/u0a102 (adj 0): remove task

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

Чтобы избежать этого, вы можете использовать это простое решение при создании PendingIntent для AlarmManager (Источник: <а3 > )

AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent("YOUR_ACTION_NAME");
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 1, intent, 0);

Обратите внимание на следующие шаги:

  • Вызовите addFlags о намерении и используйте FLAG_RECEIVER_FOREGROUND
  • Использовать ненулевой код запроса в PendingIntent.getBroadcast

Если вы оставите любой из этих шагов, это не сработает.

Обратите внимание, что FLAG_RECEIVER_FOREGROUND был добавлен в API 16 (Jelly Bean), поэтому имеет смысл, что это когда ошибка сначала появился...

Скорее всего, KitKat более агрессивен, когда дело доходит до процессов убийства, и поэтому это было подчеркнуто с помощью KitKat, но похоже, что это уже было актуально для Jelly Bean.

Примечание 2: Обратите внимание на детали в вопросе о настройке службы - работайте в отдельном процессе, в качестве службы переднего плана, с параметром endWithTask, установленным на false в манифесте.

Примечание 3: То же самое происходит, когда приложение получает сообщение android.appwidget.action.APPWIDGET_CONFIGURE и отображает активность конфигурации для нового виджета (замените шаг 4 выше с созданием нового виджета). Я обнаружил, что это происходит только тогда, когда поставщик виджета (получатель, который обрабатывает android.appwidget.action.APPWIDGET_UPDATE) настроен на запуск в другом процессе, чем процесс активности. После того, как вы изменили это, и активность конфигурации, и поставщик виджетов находятся в одном процессе, этого больше не происходит.

Ответ 2

Кажется, что это ошибка, присутствующая в Android 4.4, обошла ее со следующим:

@Override
public void onTaskRemoved(Intent rootIntent) {
    Intent restartService = new Intent(getApplicationContext(),
            this.getClass());
    restartService.setPackage(getPackageName());
    PendingIntent restartServicePI = PendingIntent.getService(
            getApplicationContext(), 1, restartService,
            PendingIntent.FLAG_ONE_SHOT);
    AlarmManager alarmService = (AlarmManager)getApplicationContext().getSystemService(Context.ALARM_SERVICE);
    alarmService.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() +1000, restartServicePI);

}

Нашел этот ответ от этот пост

Ответ 3

Проблема здесь не возникает на ПЗУ на основе AOSP. То есть я могу легко воссоздать это на компакт-диске CyanogenMod 11, но на AOSP ROM (и на эмуляторе) START_STICKY ведет себя точно так, как я ожидал. Тем не менее, я вижу отчеты от людей на Nexus 5, которые, похоже, видят это поведение, поэтому, возможно, это все еще проблема в AOSP.

На эмуляторе и на AOSP ROM я вижу следующее из logcat, когда я делаю "kill 5838" против процесса (как и ожидалось):

12-22 18:40:14.237 D/Zygote  (   52): Process 5838 terminated by signal (15)
12-22 18:40:14.247 I/ActivityManager(  362): Process com.xxxx (pid 5838) has died.
12-22 18:40:14.247 W/ActivityManager(  362): Scheduling restart of crashed service com.xxxx/com.xxxx.NotifyingService in 5000ms
12-22 18:40:19.327 I/ActivityManager(  362): Start proc com.xxxx for service xxxx.pro/com.xxxx.NotifyingService: pid=5877 uid=10054 gids={50054, 3003, 3002, 1028}

Я вижу одно и то же поведение перезапуска, если я завершаю задачу, "прокручивая" из списка недавних задач. Таким образом, это все хорошо - это означает, что основной код AOSP ведет себя так, как на предыдущих уровнях.

Я смотрю код службы Cyanogenmod, чтобы попытаться выяснить, почему вещи не планируются для перезапуска - пока не удалась. Похоже, что он должен перепланировать его. Cyanogenmod использует служебную карту, которую AOSP не делает - но неясно, является ли это проблемой или нет (сомнительно) https://github.com/CyanogenMod/android_frameworks_base/blob/cm-11.0/services/java/com/android/server/am/ActiveServices.java#L2092

Довольно хакерское обходное решение, которое вы можете сделать, это использовать аналогичный механизм, аналогичный вашему onTaskRemoved AlarmService, чтобы включить будильник через X минут позже. Затем каждые несколько минут, пока ваше приложение работает и работает, вы можете reset тревогу, поэтому он отключается только в том случае, если все действительно было убито и не перезапущено. Это не является надежным - использование Handler дает вам время безотказной работы и аварийную службу, которая использует в реальном времени, поэтому вы можете активировать свой будильник, даже если он был установлен на более длительное время, чем ваш обработчик reset. Но если вы добавите намерение дополнительно, вы можете игнорировать onStartCommand, если ваша служба уже запущена и запущена, превращая это в noop.

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

Сначала создайте вспомогательный метод, который будет устанавливать будильник в течение 20 минут, что приведет к запуску onStartCommand для вашей службы. Каждые 2 минуты имеет обработчик, который будет reset 20-минутным сигналом тревоги. Если обработчик работает в режиме реального времени 20 минут, будильник никогда не погаснет. Обработчик не может работать, хотя если устройство спит (что хорошо).

private void ensureServiceStaysRunning() {
    // KitKat appears to have (in some cases) forgotten how to honor START_STICKY
    // and if the service is killed, it doesn't restart.  On an emulator & AOSP device, it restarts...
    // on my CM device, it does not - WTF?  So, we'll make sure it gets back
    // up and running in a minimum of 20 minutes.  We reset our timer on a handler every
    // 2 minutes...but since the handler runs on uptime vs. the alarm which is on realtime,
    // it is entirely possible that the alarm doesn't get reset.  So - we make it a noop,
    // but this will still count against the app as a wakelock when it triggers.  Oh well,
    // it should never cause a device wakeup.  We're also at SDK 19 preferred, so the alarm
    // mgr set algorithm is better on memory consumption which is good.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
    {
        // A restart intent - this never changes...        
        final int restartAlarmInterval = 20*60*1000;
        final int resetAlarmTimer = 2*60*1000;
        final Intent restartIntent = new Intent(this, NotifyingService.class);
        restartIntent.putExtra("ALARM_RESTART_SERVICE_DIED", true);
        final AlarmManager alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
        Handler restartServiceHandler = new Handler()
        {
            @Override
            public void handleMessage(Message msg) {
                // Create a pending intent
                PendingIntent pintent = PendingIntent.getService(getApplicationContext(), 0, restartIntent, 0);
                alarmMgr.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + restartAlarmInterval, pintent);
                sendEmptyMessageDelayed(0, resetAlarmTimer);
            }            
        };
        restartServiceHandler.sendEmptyMessageDelayed(0, 0);  
    }
}

В своем onCreate вы можете вызвать этот метод. Кроме того, в вашем onStartCommand обязательно проигнорируйте это, если ваша служба уже запущена и работает. EG:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    ...
    if ((intent != null) && (intent.getBooleanExtra("ALARM_RESTART_SERVICE_DIED", false)))
    {
        Log.d(TAG, "onStartCommand after ALARM_RESTART_SERVICE_DIED");
        if (IS_RUNNING)
        {
            Log.d(TAG, "Service already running - return immediately...");
            ensureServiceStaysRunning();
            return START_STICKY;
        }
    }
    // Do your other onStartCommand stuff..
    return START_STICKY;
}

Ответ 4

Я нашел этот простой прием для решения этой проблемы без использования AlarmManager.

  • создать широковещательный приемник, который прослушивает трансляцию каждый раз, когда вызывается метод onDestroy() в службе:

    public class RestartService extends BroadcastReceiver {
    
    private static final String TAG = "RestartService";
    
    public RestartService() {
    }
    
    @Override
    public void onReceive(Context context, Intent intent) {
    Log.e(TAG, "onReceive");
    context.startService(new Intent(context, YourService.class));
    }
    }
    
  • добавить настроенное трансляцию для вашего манифеста

    <receiver
        android:name=".RestartService"
        android:enabled="true" >
        <intent-filter>
            <action android:name="restartApps" />
        </intent-filter>
    </receiver>
    
  • тогда отправьте трансляцию с onDestroy(), возможно, вот так:

    @Override
    public void onDestroy() {
    Intent intent = new Intent("restartApps");
    sendBroadcast(intent);
    super.onDestroy();
    stopThread();
    }
    
  • вызов onDestroy() из onTaskRemoved(Intent intent)

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

Ответ 5

От android Сервис .START_STICKY

Константа для возврата из onStartCommand (Intent, int, int): если это сервисный процесс убивается во время его запуска (после возвращения из onStartCommand (Intent, int, int)), а затем оставить его в начальном состоянии но не сохраняют этого поставленного намерения. Позже система попытается заново создайте службу. Поскольку он находится в начальном состоянии, он будет гарантировать вызов onStartCommand (Intent, int, int) после создания новый экземпляр службы; если нет ожидающих команд запуска для быть доставлен на службу, он будет вызван с нулевым намерением объект, поэтому вы должны позаботиться об этом.

Этот режим имеет смысл для вещей, которые будут явно запущены и перестали работать в течение произвольных периодов времени, таких как служба воспроизведение фоновой музыки.

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

Если вы хотите, чтобы ваш сервис был убит, вы должны запустить его в foreground. Также рекомендуется отделить ваш сервис от жизненного цикла приложения, запустив его в своем собственном process.