Проблема при переходе с IntentService на JobIntentService для Android O

Я использую Intent Service для отслеживания перехода Geofence. Для этого я использую следующий звонок из липкой службы.

 LocationServices.GeofencingApi.addGeofences(
                    mGoogleApiClient,
                    getGeofencingRequest(),
                    getGeofencePendingIntent()
            )

а ожидающее намерение вызывает службу перехода (IntentService), как показано ниже.

  private PendingIntent getGeofencePendingIntent() {
        Intent intent = new Intent(this, GeofenceTransitionsIntentService.class);
        // We use FLAG_UPDATE_CURRENT so that we get the 
          //same pending intent back when calling addgeoFences()
        return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    }

Это прекрасно работало Pre Oreo. Однако мне пришлось преобразовать мою липкую службу в JobScheduler, и мне нужно преобразовать GeofenceTransitionsIntentService, который является intentService, в JobIntentService.

Сказав, что я не уверен, как вернуть создание PendingIntent для JobIntentService, потому что мне нужно вызвать enqueueWork для JobIntentService.

Любые предложения / указатели будут оценены.


person Akshay    schedule 10.10.2017    source источник
comment
Я знаю, что размещать URL-адрес не рекомендуется, поскольку веб-сайты могут исчезнуть. Но вот пример веб-сайта, который я имел в виду, чтобы создать GeoFence, если кому-то интересно. mytrendin.com/android-geofences-google-api   -  person Akshay    schedule 10.10.2017
comment
Используйте BroadcastReceiver в качестве ожидающего намерения для Geofence API. Затем запланируйте задание в этом BroadcastReceive, как только оно будет запущено Geofence API.   -  person andrei_zaitcev    schedule 10.10.2017
comment
Я подумал, что Android O не рекомендует использовать широковещательный приемник.   -  person Akshay    schedule 10.10.2017
comment
@andrei_zaitcev, в таком случае, какие фильтры намерений вы бы порекомендовали для трансляции? Особенно помните об Android Oreo?   -  person Akshay    schedule 11.10.2017
comment
Android Oreo не имеет фоновых ограничений для широковещательных приемников. Вы можете оставить его без каких-либо фильтров намерений. Вы просто не можете запустить фоновую службу из этого приемника, если ваше приложение находится в так называемом фоновом режиме.   -  person andrei_zaitcev    schedule 12.10.2017
comment
@Akshay то, что говорят о широковещательных приемниках, сделано для неявных намерений. В любом случае вы можете зарегистрировать их программно и полностью опустить объявление в манифесте.   -  person Jose_GD    schedule 21.11.2018
comment
@Jose_GD хотите уточнить? У меня пока нет проблем с этим, и, насколько мне известно, я не видел ни одного документа Google, в котором упоминалось бы, что это плохая практика. В качестве альтернативы, если вы знаете какой-либо другой способ получить намерение от JobIntentService без создания трансляции, мне любопытно узнать.   -  person Akshay    schedule 21.11.2018
comment
@Akshay здесь: developer.android.com/about/versions/oreo/background. Ограничения широковещательной рассылки: За некоторыми исключениями приложения не могут использовать свой манифест для регистрации для неявных широковещательных рассылок. Они по-прежнему могут регистрироваться для этих широковещательных рассылок во время выполнения, и они могут использовать манифест для регистрации явных широковещательных рассылок, специально нацеленных на их приложение.   -  person Jose_GD    schedule 22.11.2018
comment
@Akshay Я отвечал только на ваш комментарий: Android O не рекомендует использовать широковещательный приемник. Отвечая на ваш второй вопрос, я не знаю другого способа, похоже, ваше решение в порядке   -  person Jose_GD    schedule 22.11.2018
comment
@Jose_GD Обратите внимание на исключение явной трансляции. Это явная трансляция   -  person Akshay    schedule 23.11.2018


Ответы (2)


Проблема

У меня возникла такая же проблема при переходе с IntentService на JobIntentService на устройствах Android Oreo +.

Все руководства и фрагменты, которые я нашел, неполны, они не учитывают критические изменения, которые эта миграция внесла в использование PendingIntent.getServce.

В частности, эта миграция прерывает любые Alarms, запланированные для запуска службы с AlarmManager, и любые Actions, добавленные к Notification, запускающие службу.


Решение

Замените PendingIntent.getService на PendingIntent.getBroadcast, который начинает BroastcastReceiver.

Затем этот приемник запускает JobIntentService, используя enqueueWork.


Это может повторяться и приводить к ошибкам при переносе нескольких служб.

Чтобы сделать это проще и не зависеть от обслуживания, я создал общий StartJobIntentServiceReceiver, который принимает идентификатор задания и Intent, предназначенный для JobIntentService.

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

/**
 * A receiver that acts as a pass-through for enqueueing work to a {@link android.support.v4.app.JobIntentService}.
 */
public class StartJobIntentServiceReceiver extends BroadcastReceiver {

    public static final String EXTRA_SERVICE_CLASS = "com.sg57.tesladashboard.extra_service_class";
    public static final String EXTRA_JOB_ID = "com.sg57.tesladashboard.extra_job_id";

    /**
     * @param intent an Intent meant for a {@link android.support.v4.app.JobIntentService}
     * @return a new Intent intended for use by this receiver based off the passed intent
     */
    public static Intent getIntent(Context context, Intent intent, int job_id) {
        ComponentName component = intent.getComponent();
        if (component == null)
            throw new RuntimeException("Missing intent component");

        Intent new_intent = new Intent(intent)
                .putExtra(EXTRA_SERVICE_CLASS, component.getClassName())
                .putExtra(EXTRA_JOB_ID, job_id);

        new_intent.setClass(context, StartJobIntentServiceReceiver.class);

        return new_intent;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        try {
            if (intent.getExtras() == null)
                throw new Exception("No extras found");


            // change intent's class to its intended service's class
            String service_class_name = intent.getStringExtra(EXTRA_SERVICE_CLASS);

            if (service_class_name == null)
                throw new Exception("No service class found in extras");

            Class service_class = Class.forName(service_class_name);

            if (!JobIntentService.class.isAssignableFrom(service_class))
                throw new Exception("Service class found is not a JobIntentService: " + service_class.getName());

            intent.setClass(context, service_class);


            // get job id
            if (!intent.getExtras().containsKey(EXTRA_JOB_ID))
                throw new Exception("No job ID found in extras");

            int job_id = intent.getIntExtra(EXTRA_JOB_ID, 0);


            // start the service
            JobIntentService.enqueueWork(context, service_class, job_id, intent);


        } catch (Exception e) {
            System.err.println("Error starting service from receiver: " + e.getMessage());
        }
    }

}

Вам нужно будет заменить имена пакетов своими собственными и зарегистрировать это BroadcastReceiver как обычно в вашем AndroidManifest.xml:

<receiver android:name=".path.to.receiver.here.StartJobIntentServiceReceiver"/>

Теперь вы можете безопасно использовать Context.sendBroadcast или PendingIntent.getBroadcast где угодно, просто оберните Intent, который вы хотите доставить на ваш JobIntentService статический метод приемника, StartJobIntentServiceReceiver.getIntent.


Примеры

Вы можете запустить приемник, а затем и ваш JobIntentService, сразу, выполнив следующие действия:

Context.sendBroadcast(StartJobIntentServiceReceiver.getIntent(context, intent, job_id));

Везде, где вы не запускаете службу немедленно, вы должны использовать PendingIntent, например, при планировании Alarms с AlarmManager или добавлении Actions к Notifications:

PendingIntent.getBroadcast(context.getApplicationContext(),
    request_code,
    StartJobIntentServiceReceiver.getIntent(context, intent, job_id),
    PendingIntent.FLAG_UPDATE_CURRENT);
person Cord Rehn    schedule 15.03.2018
comment
На ваш взгляд This can be repetitive and error prone when migrating multiple services. это субъективно. Предоставленное вами решение может работать, но не лучшее, imho, или, по крайней мере, в то время, когда я получил ответы, просматривая документы Google, заключалось в том, чтобы поставить задание в очередь. - person Akshay; 07.06.2019

Как предложил @andrei_zaitcev, я реализовал свой собственный BroadCastReceiver и вызвал enqueueWork() службы, которая работает отлично.

person Akshay    schedule 10.10.2017
comment
А есть ли у вас пример вашей реализации? Искал подобное решение, когда обновлял приложение до Oreo. - person Kyle; 21.12.2017
comment
Вы можете увидеть этот подход в их примере геозоны, но я не смог заставить его работать, и, что удивительно, он устарел! Как может код, который был обновлен для O, уже устарел? Господи, Google получил пятерку за эту кучу навоза идиотизма. - person Rob; 30.07.2018