Android 9.0: не разрешено запускать сервис: приложение находится в фоновом режиме.. после onResume()

У меня есть музыкальный проигрыватель, который пытается запустить Service в onResume() Activity. Я удалил несколько строк для ясности, но код эффективен:

@Override
protected void onResume() {
    super.onResume();

    startService(new Intent(this, MusicService.class));
}

Согласно журналам сбоев, это исключение на некоторых устройствах под управлением Android P:

Caused by java.lang.IllegalStateException: Not allowed to start service Intent { cmp=another.music.player/com.simplecity.amp_library.playback.MusicService }: app is in background uid UidRecord{6a4a9c6 u0a143 TPSL bg:+3m25s199ms idle change:cached procs:1 seq(1283,1283,1283)}
       at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1577)
       at android.app.ContextImpl.startService(ContextImpl.java:1532)
       at android.content.ContextWrapper.startService(ContextWrapper.java:664)
       at android.content.ContextWrapper.startService(ContextWrapper.java:664)
       at com.simplecity.amp_library.utils.MusicServiceConnectionUtils.bindToService(SourceFile:36)
       at com.simplecity.amp_library.ui.activities.BaseActivity.bindService(SourceFile:129)
       at com.simplecity.amp_library.ui.activities.BaseActivity.onResume(SourceFile:96)

Как это возможно, что мое приложение находится в фоновом режиме, сразу после onResume()super.onResume())?

Это не имеет никакого смысла для меня. Может ли это быть ошибкой платформы? Все пользователи 3500+, затронутые этой аварией, работают на Android P.

Ответы

Ответ 1

ОБНОВЛЕНИЕ: Это работает для нас в Prod, но это не 100%. Я получил один отчет о сбое за последние полтора месяца, в противном случае было бы более сотни. Пока это не исправлено, это кажется нашим лучшим вариантом на данный момент. Может быть, если бы я поднял время больше 300, то такого падения никогда бы не произошло?

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

class ResumingServiceManager(val lifecycle: Lifecycle) : LifecycleObserver {

    init {
        lifecycle.addObserver(this)
    }

    val disposable: CompositeDisposable = CompositeDisposable()

    fun startService(context: Context, intent: Intent) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            context.startService(intent)
        } else {
            Single.just(true)
                    .delaySubscription(300, TimeUnit.MILLISECONDS)
                    .subscribeOn(AndroidSchedulers.mainThread())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribeBy(
                            onSuccess = {
                                context.startService(intent)
                            }

                    ).addTo(disposable)
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun stopped() {
        disposable.clear()
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun destroy() {
        lifecycle.removeObserver(this)
    }
}

В onCreate() инициализируйте его, а затем в любое время, когда вы захотите запустить службу в onResume, просто вызовите resumingServiceManager.startService resumingServiceManager.startService(this, intent)

Он осведомлен о жизненном цикле, поэтому он будет очищать одноразовое устройство, если приостановит отмену запуска onSuccess, когда он может быть на пути к фону с немедленным открытием/закрытием.

Ответ 2

Они исправили сбой (в некоторых будущих выпусках, не уверен, что именно) и предоставили обходной путь на данный момент: https://issuetracker.google.com/issues/110237673#comment9

Ответ 3

Это было помечено как "исправлено" в трекере проблем Android:

Предположительно исправление будет выпущено в одном из выпусков Android Q.

По словам Гуглера, который закрыл вопрос,

Существует обходной путь, чтобы избежать сбоя приложения. Приложения могут получить состояние процесса в Activity.onResume(), вызвав ActivityManager.getRunningAppProcesses() и избежать запуска Service, если уровень важности ниже, чем ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND. Если устройство не полностью проснулось, действия будут немедленно приостановлены и, в конце концов, возобновлены после его полного пробуждения.

Ответ 4

Существует обходной путь от Google:

Проблема была решена в будущем выпуске Android.

Существует обходной путь, чтобы избежать сбоя приложения. Приложения могут получить состояние процесса в Activity.onResume(), вызвав ActivityManager.getRunningAppProcesses() и избежать запуска Service, если уровень важности ниже, чем ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND. Если устройство не полностью проснулось, действия будут немедленно приостановлены и, в конце концов, возобновлены после его полного пробуждения.

Поэтому я думаю, что так должно быть:

// hack for https://issuetracker.google.com/issues/113122354
    ActivityManager activityManager = ContextCompat.getSystemService(this, ActivityManager.class);
    int importance = activityManager.getRunningAppProcesses().get(0).importance;
    // higher importance has lower number (?)
    if (importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND)
        URLPlayerService.startActionBroadcastServiceData(PlayerActivity.this);

Я использовал обработчик как обходной путь, и он работает довольно хорошо, но не на 100%:

// hack for https://issuetracker.google.com/issues/113122354
   handler.postDelayed(() -> URLPlayerService.startService(PlayerActivity.this),200);

Ответ 5

Вы должны принять разрешение переднего плана в android 9 pie, и rest u может создать службу переднего плана с помощью уведомления, и это нужно сделать в течение 5 секунд после запуска службы для Oreo и выше.

@Override
public void onCreate() {
    super.onCreate();
    this.context = this;
    showNotification();
    Log.i("onCreate", "completed");

}    
private void showNotification() {
    Notification notification;
    Intent notificationIntent = new Intent(this, BackupsActivity.class);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
            notificationIntent, 0);
    int icon = R.mipmap.ic_launcher;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
        icon = android.R.drawable.stat_notify_sync;

    if (android.os.Build.VERSION.SDK_INT >= 26) {
        //This only needs to be run on Devices on Android O and above
        NotificationManager mNotificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        String id = "id";
        CharSequence name = Constants.CHANNEL_BACKUP_RESTORE; //user visible
        String description = Constants.CHANNEL_BACKUP_RESTORE_DESCRIPTION; //user visible
        int importance = NotificationManager.IMPORTANCE_HIGH;
        NotificationChannel mChannel = new NotificationChannel(id, name, importance);
        mChannel.setDescription(description);
        mChannel.enableLights(true);
        mChannel.setLightColor(Color.RED);
        mChannel.enableVibration(true);
        mChannel.setVibrationPattern(new long[]{0, 1000});
        mNotificationManager.createNotificationChannel(mChannel);
        notification = new Notification.Builder(getApplicationContext(), id)
               .setContentTitle("your title")
                .setTicker("process details")
                .setContentText("Your text")
                .setSmallIcon(icon)  .setLargeIcon(Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.app_logo), 128, 128, false))
                .setContentIntent(pendingIntent)
                .setOngoing(true).build();
    } else {
        notification = new Notification.Builder(getApplicationContext())
                .setContentTitle("TITLE")
                .setTicker("ypur sticker")
                .setContentText("Text")
                .setSmallIcon(icon)
                .setLargeIcon(Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.app_logo), 128, 128, false))
                .setContentIntent(pendingIntent)
                .setOngoing(true).build();

    }

    startForeground(Constants.CATEGORY, notification);
    Log.i("notification", "show");
}

Если таргетинг на пирог, в манифесте добавить:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

Вы можете начать обслуживание:

public static void startBackupService(Context context) {
        Intent service = new Intent(context, UploadService.class);
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) {
            context.startForegroundService(service);
        } else {
            context.startService(new Intent(service));
        }
}

Ответ 6

Может быть, android.arch.lifecycle можно использовать в качестве обходного пути для этой ошибки Android 9?

public class MyActivity extends Activity implements LifecycleObserver {

    protected void onResume() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (ProcessLifecycleOwner.get().getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
                startService(intent);
            } else {
                ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
            }
        } else {
            startService(intent);
        }
    }


    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    void onEnterForeground() {
        startService(intent);
        ProcessLifecycleOwner.get().getLifecycle().removeObserver(this);
    }
}

Я нашел его здесь.