it-swarm.com.ru

Context.startForegroundService () тогда не вызывал Service.startForeground ()

Я использую класс Service на ОС Android O. 

Я планирую использовать Service в фоновом режиме. 

Рекомендация Android гласит, что startService должен использовать startForegroundService.

Если вы используете startForegroundService, Service выбрасывает Context.startForegroundService(), то не вызывает Service.startForeground() ошибку. 

Что с этим не так?

121
NiceGuy

Из документов Google на изменения поведения Android 8.0 :

Система позволяет приложениям вызывать Context.startForegroundService (), даже когда приложение находится в фоновом режиме. Однако приложение должно вызвать метод startForeground () этой службы в течение пяти секунд после создания службы.

Решение: Вызовите startForeground() в onCreate() для Service, который вы используете Context.startForegroundService()

Смотрите также: Пределы выполнения фона для Android 8.0 (Oreo)

63
zhuzhumouse

Я позвонил ContextCompat.startForegroundService(this, intent), чтобы запустить службу

В сервисе onCreate

 @Override
 public void onCreate() {
        super.onCreate();

        if (Build.VERSION.SDK_INT >= 26) {
            String CHANNEL_ID = "my_channel_01";
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);

            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                    .setContentTitle("")
                    .setContentText("").build();

            startForeground(1, notification);
        }
}
45
humazed

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

Это определенно проблема с фреймворком, но не все разработчики, сталкивающиеся с этой проблемой, делают все возможное:

  1. startForeground уведомление должно быть как в onCreate, так и в onStartCommand, потому что если ваша служба уже создана и каким-то образом ваша деятельность пытается запустить ее снова, onCreate вызываться не будет.

  2. iD уведомления не должен быть равен 0, иначе произойдет тот же сбой, даже если это не та же самая причина.

  3. stopSelf не должен вызываться до startForeground.

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

И обратите внимание, что, скорее всего, Android P по-прежнему будет нести эту проблему, потому что Google отказывается даже понимать, что происходит, и не считает, что это их вина, прочитайте # 36 здесь: https://issuetracker.google.com/issues/76112072 .

19
ZhouX

Ваше приложение будет зависать, если вы вызовете Context.startForegroundService(...), а затем вызовете Context.stopService(...) до вызова Service.startForeground(...).

У меня есть четкое подтверждение по адресу: https://github.com/paulpv/ForegroundServiceAPI26/blob/repro/app/src/main/Java/com/github/paulpv/foregroundserviceapi26/MainService.kt#L28

Я открыл ошибку по этому вопросу: https://issuetracker.google.com/issues/76112072

Несколько ошибок по этому вопросу были открыты и закрыты не исправят.

Надеюсь, мой с четкими репро шагами сделает разрез.

13
swooby

Я исследовал это в течение нескольких дней и получил решение . Теперь в Android O вы можете установить ограничение фона, как показано ниже

Служба, которая вызывает класс обслуживания

Intent serviceIntent = new Intent(SettingActivity.this,DetectedService.class);
                    if (Android.os.Build.VERSION.SDK_INT >= Android.os.Build.VERSION_CODES.O){

                        SettingActivity.this.startForegroundService(serviceIntent);
                    }else{
                        startService(serviceIntent);
                    }

и класс обслуживания должен быть как

public class DetectedService extends Service { 
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

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

    @Override
    public void onCreate() {
        super.onCreate();
        int NOTIFICATION_ID = (int) (System.currentTimeMillis()%10000);
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForeground(NOTIFICATION_ID, new Notification.Builder(this).build());
        }


        // Do whatever you want to do here
    }
}
8
Ahmad Arslan

Просто один на один, потому что я потратил слишком много часов на это. Я продолжал получать это исключение, даже несмотря на то, что я первым делом вызывал startForeground(..) в onCreate(..). В конце концов я обнаружил, что проблема была вызвана использованием NOTIFICATION_ID = 0. Использование любого другого значения, кажется, исправит это.

6
tobalr

У меня есть widget , который делает относительно частые обновления, когда устройство не работает, и я видел тысячи сбоев всего за несколько дней.

Триггер проблемы

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

Обходной путь/решение

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

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    stopForeground(true);
} else {
    stopSelf();
}
5
Roy Solberg

Эта ошибка также возникает в Android 8+, когда Service.startForeground (int id, Notification Notification) вызывается, когда id установлено в 0.

id int: идентификатор для этого уведомления согласно NotificationManager.notify (int, Notification); не должно быть 0.

4
almisoft

У меня есть решение этой проблемы. Я проверил это исправление в своем собственном приложении (300K + DAU), которое может уменьшить по крайней мере 95% этого вида сбоя, но все еще не может на 100% избежать этой проблемы.

Эта проблема возникает, даже если вы уверены, что вызываете startForeground () сразу после запуска службы, как это задокументировано Google. Это может быть связано с тем, что процесс создания и инициализации службы во многих сценариях уже стоит более 5 секунд, поэтому независимо от того, когда и где вы вызываете метод startForeground (), этот сбой неизбежен.

Мое решение состоит в том, чтобы гарантировать, что startForeground () будет выполняться в течение 5 секунд после метода startForegroundService (), независимо от того, сколько времени необходимо создать и инициализировать ваш сервис. Вот подробное решение.

  1. Не используйте startForegroundService в первую очередь, используйте bindService () с флагом auto_create. Он будет ждать инициализации сервиса. Вот код, мой пример сервиса MusicService:

    final Context applicationContext = context.getApplicationContext();
    Intent intent = new Intent(context, MusicService.class);
    applicationContext.bindService(intent, new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            if (binder instanceof MusicBinder) {
                MusicBinder musicBinder = (MusicBinder) binder;
                MusicService service = musicBinder.getService();
                if (service != null) {
                    // start a command such as music play or pause.
                    service.startCommand(command);
                    // force the service to run in foreground here.
                    // the service is already initialized when bind and auto_create.
                    service.forceForeground();
                }
            }
            applicationContext.unbindService(this);
        }
    
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    }, Context.BIND_AUTO_CREATE);
    
  2. Тогда вот реализация MusicBinder:

    /**
     * Use weak reference to avoid binder service leak.
     */
     public class MusicBinder extends Binder {
    
         private WeakReference<MusicService> weakService;
    
         /**
          * Inject service instance to weak reference.
          */
         public void onBind(MusicService service) {
             this.weakService = new WeakReference<>(service);
         }
    
         public MusicService getService() {
             return weakService == null ? null : weakService.get();
         }
     }
    
  3. Самая важная часть, реализация MusicService, метод forceForeground () обеспечит вызов метода startForeground () сразу после startForegroundService ():

    public class MusicService extends MediaBrowserServiceCompat {
    ...
        private final MusicBinder musicBind = new MusicBinder();
    ...
        @Override
        public IBinder onBind(Intent intent) {
            musicBind.onBind(this);
            return musicBind;
        }
    ...
        public void forceForeground() {
            // API lower than 26 do not need this work around.
            if (Build.VERSION.SDK_INT >= 26) {
                Intent intent = new Intent(this, MusicService.class);
                // service has already been initialized.
                // startForeground method should be called within 5 seconds.
                ContextCompat.startForegroundService(this, intent);
                Notification notification = mNotificationHandler.createNotification(this);
                // call startForeground just after startForegroundService.
                startForeground(Constants.NOTIFICATION_ID, notification);
            }
        }
    }
    
  4. Если вы хотите запустить фрагмент кода шага 1 в ожидающем намерении, например, если вы хотите запустить службу переднего плана в виджете (нажатие кнопки виджета), не открывая свое приложение, вы можете заключить фрагмент кода в приемник вещания. и запустить широковещательное событие вместо команды запуска службы.

Это все. Надеюсь, поможет. Удачи.

4
Hexise

Проблема с Android O API 26

Если вы сразу же остановите службу (так что ваша служба на самом деле не работает (формулировка/понимание) и вы находитесь в интервале ANR, вам все равно необходимо вызвать startForeground перед stopSelf).

https://plus.google.com/116630648530850689477/posts/L2rn4T6SAJ5

Пробовал этот подход, но он по-прежнему создает ошибку: -

if (Util.SDK_INT > 26) {
    mContext.startForegroundService(playIntent);
} else {
    mContext.startService(playIntent);
}

Я использую это, пока ошибка не будет устранена 

mContext.startService(playIntent);
3
Andy Cass

Так много ответов, но никто не работал в моем случае.

Я начал службу как это.

              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    startForegroundService(intent);
                } else {
                    startService(intent);
                }

И в моем сервисе в onStartCommand 

   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        Notification.Builder builder = new Notification.Builder(this, Android_CHANNEL_ID)
                .setContentTitle(getString(R.string.app_name))
                .setContentText("SmartTracker Running")
                .setAutoCancel(true);
        Notification notification = builder.build();
        startForeground(NOTIFICATION_ID, notification);
    } else {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setContentTitle(getString(R.string.app_name))
                .setContentText("SmartTracker is Running...")
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setAutoCancel(true);
        Notification notification = builder.build();
        startForeground(NOTIFICATION_ID, notification);
    }

И не забывайте устанавливать NOTIFICATION_ID ненулевым

private static final String Android_CHANNEL_ID = "com.xxxx.Location.Channel"; private static final int NOTIFICATION_ID = 555;

ТАК все было отлично, но все еще терпел крах на 8.1, причина была как ниже.

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            stopForeground(true);
        } else {
            stopForeground(true);
        }

Я вызвал остановку переднего плана с удалением уведомлений, но как только служба удаленных уведомлений стала фоновой, фоновая служба не может работать в Android O из фона. началось после получения Push.

Так волшебное Слово

  stopSelf();

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

3
sandy

https://developer.Android.com/reference/Android/content/Context.html#startForegroundService(Android.content.Intent)

Аналогично startService (Intent), но с неявным обещанием, что Служба будет вызывать startForeground (int, Android.app.Notification) один раз начинается бег. Сервису предоставляется количество времени, сопоставимое для интервала ANR, чтобы сделать это, в противном случае система будет автоматически остановить сервис и объявить приложение ANR.

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

убедитесь, что вы вызываете Service.startForeground(int, Android.app.Notification) для onCreate (), чтобы убедиться, что он будет вызван ... если у вас есть какие-либо условия, которые могут помешать вам сделать это, тогда вам лучше использовать обычную Context.startService(Intent) и вызывать Service.startForeground(int, Android.app.Notification) самостоятельно.

Кажется, что Context.startForegroundService() добавляет сторожевой таймер, чтобы убедиться, что вы вызвали Service.startForeground(int, Android.app.Notification) до того, как он был уничтожен ...

3
Alécio Carvalho

Я сталкиваюсь с той же проблемой, и, потратив время на поиск солутонов, вы можете попробовать код ниже. Если вы используете Service, поместите этот код в onCreate, иначе вы используете Intent Service, а затем поместите этот код в onHandleIntent.

if (Build.VERSION.SDK_INT >= 26) {
        String CHANNEL_ID = "my_app";
        NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                "MyApp", NotificationManager.IMPORTANCE_DEFAULT);
        ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("")
                .setContentText("").build();
        startForeground(1, notification);
    }
2
Sambhaji Karad

Даже после вызова startForeground в Service на некоторых устройствах происходит сбой, если мы вызываем stopService непосредственно перед вызовом onCreate . Итак, я исправил эту проблему, запустив службу с дополнительным флагом:

Intent intent = new Intent(context,
                YourService.class);
intent.putExtra("request_stop", true);
context.startService(intent);

и добавил проверку в onStartCommand, чтобы увидеть, был ли он фактически остановлен:

@Override
public int onStartCommand(Intent intent,
    int flags, int startId) {

    //call startForeground first
    boolean stopService = false;
    if (intent != null) {
        stopService = intent.getBooleanExtra("request_stop", false);
    }
    if (stopService) {
        stopSelf();
        return START_STICKY;
    }
    //Continue with the background task
    return START_STICKY;
}

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

1
Gaurav Singla

Я просто проверяю PendingIntent на ноль или нет перед вызовом функция context.startForegroundService(service_intent).

это работает для меня

PendingIntent pendingIntent=PendingIntent.getBroadcast(context,0,intent,PendingIntent.FLAG_NO_CREATE);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && pendingIntent==null){
            context.startForegroundService(service_intent);
        }
        else
        {
            context.startService(service_intent);
        }
}
1
SaimumIslam27

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

MyForegroundService.Java

public class MyForegroundService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(...);
    }
}

MainActivity.Java

Intent serviceIntent = new Intent(this, MyForegroundService.class);
startForegroundService(serviceIntent);
...
stopService(serviceIntent);

Исключение выдается в следующем блоке кода:

ActiveServices.Java

private final void bringDownServiceLocked(ServiceRecord r) {
    ...
    if (r.fgRequired) {
        Slog.w(TAG_SERVICE, "Bringing down service while still waiting for start foreground: "
                  + r);
        r.fgRequired = false;
        r.fgWaiting = false;
        mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),
                    AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);
        mAm.mHandler.removeMessages(
                    ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
        if (r.app != null) {
            Message msg = mAm.mHandler.obtainMessage(
                ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
            msg.obj = r.app;
            msg.getData().putCharSequence(
                ActivityManagerService.SERVICE_RECORD_KEY, r.toString());
            mAm.mHandler.sendMessage(msg);
         }
    }
    ...
}

Этот метод выполняется до onCreate() из MyForegroundService, потому что Android планирует создание службы на обработчике основного потока, но bringDownServiceLocked вызывается для BinderThread, что является условием состязания. Это означает, что у MyForegroundService не было возможности вызвать startForeground, что приведет к сбою.

Чтобы это исправить, мы должны убедиться, что bringDownServiceLocked не вызывается до onCreate() из MyForegroundService.

public class MyForegroundService extends Service {

    private static final String ACTION_STOP = "com.example.MyForegroundService.ACTION_STOP";

    private final BroadcastReceiver stopReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            context.removeStickyBroadcast(intent);
            stopForeground(true);
            stopSelf();
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(...);
        registerReceiver(
            stopReceiver, new IntentFilter(ACTION_STOP));
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unregisterReceiver(stopReceiver);
    }

    public static void stop(Context context) {
        context.sendStickyBroadcast(new Intent(ACTION_STOP));
    }
}

Используя липкие трансляции, мы убеждаемся, что трансляция не теряется, и stopReceiver получает намерение остановки, как только оно было зарегистрировано в onCreate() of MyForegroundService. К этому времени мы уже вызвали startForeground(...). Мы также должны удалить эту липкую трансляцию, чтобы в следующий раз не было уведомлено stopReceiver.

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

1
makovkastar

Пожалуйста, не вызывайте никаких StartForgroundServices внутри onCreate () метода, вы должны вызывать службы StartForground в onStartCommand () после создания рабочего потока, иначе вы всегда получите ANR, поэтому, пожалуйста, не пишите сложное войти в основную ветку onStartCommand () ;

public class Services extends Service {

    private static final String Android_CHANNEL_ID = "com.xxxx.Location.Channel";
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Notification.Builder builder = new Notification.Builder(this, Android_CHANNEL_ID)
                    .setContentTitle(getString(R.string.app_name))
                    .setContentText("SmartTracker Running")
                    .setAutoCancel(true);
            Notification notification = builder.build();
            startForeground(0, notification);
            Log.e("home_button","home button");
        } else {
            NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                    .setContentTitle(getString(R.string.app_name))
                    .setContentText("SmartTracker is Running...")
                    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                    .setAutoCancel(true);
            Notification notification = builder.build();
            startForeground(0, notification);
            Log.e("home_button_value","home_button_value");

        }
        return super.onStartCommand(intent, flags, startId);

    }
}
1
Shalu Gupta

для меня я добавил передний план службы в манифесте в

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

если это работает комментарий ниже, дайте мне знать

0
Arun Abimanyu

просто вызовите метод startForeground сразу после создания Service или IntentService. как это:

import Android.app.Notification;
public class AuthenticationService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(1,new Notification());
    }
}
0
Hossein Karami

Я исправил проблему с запуском службы с startService(intent) вместо Context.startForeground() и вызовом startForegound() сразу после super.OnCreate(). Кроме того, если вы запускаете службу при загрузке, вы можете запустить Activity, которая запускает службу при загрузке. Хотя это не постоянное решение, оно работает.

0
mystogan

Я знаю, что это поздний ответ, но, думаю, это могло бы помочь в будущем, я просто использовал JobIntentService вместо IntentService, который включает его JobScheduler и обрабатывает все для меня. посмотрите на это пример

0
raed

Я знаю, слишком много ответов уже опубликовано, однако правда в том, что startForegroundService нельзя исправить на уровне приложения, и вы должны прекратить его использовать. Рекомендация Google использовать API Service # startForeground () в течение 5 секунд после вызова Context # startForegroundService () - это не то, что приложение всегда может сделать.

Android запускает много процессов одновременно, и нет никакой гарантии, что Looper вызовет вашу целевую службу, которая должна вызывать startForeground () в течение 5 секунд. Если ваша целевая служба не получила вызов в течение 5 секунд, вам не повезло, и ваши пользователи столкнутся с ситуацией ANR. В трассировке стека вы увидите что-то вроде этого:

Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{1946947 u0 ...MessageService}

main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x763e01d8 self=0x7d77814c00
  | sysTid=11171 Nice=-10 cgrp=default sched=0/0 handle=0x7dfe411560
  | state=S schedstat=( 1337466614 103021380 2047 ) utm=106 stm=27 core=0 HZ=100
  | stack=0x7fd522f000-0x7fd5231000 stackSize=8MB
  | held mutexes=
  #00  pc 00000000000712e0  /system/lib64/libc.so (__epoll_pwait+8)
  #01  pc 00000000000141c0  /system/lib64/libutils.so (Android::Looper::pollInner(int)+144)
  #02  pc 000000000001408c  /system/lib64/libutils.so (Android::Looper::pollOnce(int, int*, int*, void**)+60)
  #03  pc 000000000012c0d4  /system/lib64/libandroid_runtime.so (Android::Android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44)
  at Android.os.MessageQueue.nativePollOnce (MessageQueue.Java)
  at Android.os.MessageQueue.next (MessageQueue.Java:326)
  at Android.os.Looper.loop (Looper.Java:181)
  at Android.app.ActivityThread.main (ActivityThread.Java:6981)
  at Java.lang.reflect.Method.invoke (Method.Java)
  at com.Android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.Java:493)
  at com.Android.internal.os.ZygoteInit.main (ZygoteInit.Java:1445)

Как я понимаю, Лупер проанализировал очередь здесь, нашел "обидчика" и просто убил ее. Теперь система счастлива и здорова, а разработчики и пользователи - нет, но, поскольку Google ограничивает свои обязанности системой, почему они должны заботиться о последних двух? Видимо, нет. Могут ли они сделать это лучше? Конечно, например они могли бы обслуживать диалог "Приложение занято", предлагая пользователю принять решение о том, ждать или убить приложение, но зачем беспокоиться, это не их ответственность. Главное, что система сейчас здорова.

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

В этом потоке было хорошее предложение использовать "bind" вместо "start", а затем, когда служба готова, обрабатывать onServiceConnected, но, опять же, это означает, что вообще не следует использовать вызовы startForegroundService.

Я думаю, что правильным и честным действием со стороны Google было бы сообщить всем, что startForegourndServcie имеет недостаток и не должен использоваться.

Вопрос все еще остается: что использовать вместо этого? К счастью для нас, есть JobScheduler и JobService, которые являются лучшей альтернативой для приоритетных служб. Это лучший вариант, потому что:

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

Это означает, что вам больше не нужно заботиться об обработке wakelocks, и поэтому он ничем не отличается от приоритетных сервисов. Начиная с реализации, PoV JobScheduler - это не ваша служба, а системная, и, по-видимому, она будет правильно обрабатывать очередь, и Google никогда не прекратит свой собственный дочерний элемент :)

Samsung перешла от startForegroundService к JobScheduler и JobService в своем дополнительном протоколе Samsung (SAP). Это очень полезно, когда такие устройства, как умные часы, должны общаться с такими хостами, как телефоны, где работа должна взаимодействовать с пользователем через главный поток приложения. Поскольку задания публикуются планировщиком в главном потоке, это становится возможным. Однако вы должны помнить, что задание выполняется в главном потоке и перекладывать все тяжелые вещи на потоки и асинхронные задачи.

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

Единственная ловушка перехода на JobScheduler/JobService заключается в том, что вам нужно будет реорганизовать старый код, и это не весело. Последние два дня я потратил на это, чтобы использовать новую реализацию SAP от Samsung. Я посмотрю свои отчеты о сбоях и сообщу, увидит ли снова сбой. Теоретически это не должно происходить, но всегда есть детали, о которых мы могли бы не знать.

0
Oleg Gryb

Обновление данных в onStartCommand(...)

onBind (...)

onBind(...) - лучшее событие жизненного цикла для инициирования startForeground против onCreate(...), потому что onBind(...) передает Intent, которая может содержать важные данные в Bundle, необходимые для инициализации Service. Однако в этом нет необходимости, поскольку функция onStartCommand(...) вызывается при первом создании Service или после последующих вызовов.

onStartCommand (...)

startForeground в onStartCommand(...) важна для обновления Service после его создания.

Когда ContextCompat.startForegroundService(...) вызывается после того, как Service был создан, onBind(...) и onCreate(...) не вызываются. Таким образом, обновленные данные могут быть переданы в onStartCommand(...) через IntentBundle для обновления данных в Service.

Образец

Я использую этот шаблон для реализации PlayerNotificationManager в приложении новостей криптовалюты Coinverse .

Activity/Fragment.kt

context?.bindService(
        Intent(context, AudioService::class.Java),
        serviceConnection, Context.BIND_AUTO_CREATE)
ContextCompat.startForegroundService(
        context!!,
        Intent(context, AudioService::class.Java).apply {
            action = CONTENT_SELECTED_ACTION
            putExtra(CONTENT_SELECTED_KEY, contentToPlay.content.apply {
                audioUrl = uri.toString()
            })
        })

AudioService.kt

private var uri: Uri = Uri.parse("")

override fun onBind(intent: Intent?) =
        AudioServiceBinder().apply {
            player = ExoPlayerFactory.newSimpleInstance(
                    applicationContext,
                    AudioOnlyRenderersFactory(applicationContext),
                    DefaultTrackSelector())
        }

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    intent?.let {
        when (intent.action) {
            CONTENT_SELECTED_ACTION -> it.getParcelableExtra<Content>(CONTENT_SELECTED_KEY).also { content ->
                val intentUri = Uri.parse(content.audioUrl)
                // Checks whether to update Uri passed in Intent Bundle.
                if (!intentUri.equals(uri)) {
                    uri = intentUri
                    player?.prepare(ProgressiveMediaSource.Factory(
                            DefaultDataSourceFactory(
                                    this,
                                    Util.getUserAgent(this, getString(app_name))))
                            .createMediaSource(uri))
                    player?.playWhenReady = true
                    // Calling 'startForeground' in 'buildNotification(...)'.          
                    buildNotification(intent.getParcelableExtra(CONTENT_SELECTED_KEY))
                }
            }
        }
    }
    return super.onStartCommand(intent, flags, startId)
}

// Calling 'startForeground' in 'onNotificationStarted(...)'.
private fun buildNotification(content: Content): Unit? {
    playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
            this,
            content.title,
            app_name,
            if (!content.audioUrl.isNullOrEmpty()) 1 else -1,
            object : PlayerNotificationManager.MediaDescriptionAdapter {
                override fun createCurrentContentIntent(player: Player?) = ...
                override fun getCurrentContentText(player: Player?) = ...
                override fun getCurrentContentTitle(player: Player?) = ...
                override fun getCurrentLargeIcon(player: Player?,
                                                 callback: PlayerNotificationManager.BitmapCallback?) = ...
            },
            object : PlayerNotificationManager.NotificationListener {
                override fun onNotificationStarted(notificationId: Int, notification: Notification) {
                    startForeground(notificationId, notification)
                }
                override fun onNotificationCancelled(notificationId: Int) {
                    stopForeground(true)
                    stopSelf()
                }
            })
    return playerNotificationManager.setPlayer(player)
}
0
Adam Hurwitz