Привязать к сервису из нового контекста для изменения конфигурации или привязки из контекста приложения?

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

Решение первое: привязка к активности

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

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

Решение numero 2:

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

Вопросы:

Итак, вопрос, на который я пытаюсь ответить сам себе: должен ли я использовать первый метод (действия с временными контекстами)? Или второй (просто привяжите службу к контексту приложения)?

Я правильно понял, что контекст приложения может связываться с сервисом несколько раз, а затем отвязать от него столько же раз? (Например, вы можете иметь несколько допустимых привязок PER-контекст)?

Может ли использование моего собственного контекста (new Context()) в первом решении вызвать какие-либо проблемы?

Edit

Найдена дополнительная информация: https://groups.google.com/forum/#!topic/android-developers/Nb58dOQ8Xfw

Также кажется, что будет сложно "создать" контекст произвольно, поэтому комбинация решений 1 и 2 кажется подходящей там, где соединение службы поддерживается во всех конфигурациях, но привязка к контексту приложения. Я по-прежнему обеспокоен возможностью отвязывания дважды из контекста приложения. Ведение подсчета привязок мне кажется ненужным - может ли кто-либо подтвердить/опровергнуть, что привязки относятся к соединению, а не к контексту?

Ответ 1

Итак, после некоторого копания, я думаю, что я придумал (пока) непроверенное решение.

Во-первых, на основе предложения Diane здесь: https://groups.google.com/forum/#!topic/android-developers/Nb58dOQ8Xfw Я должен быть привязан к контексту приложения - поэтому моя проблема потери контекста ушел - я могу поддерживать свой ServiceConnection в конфигурации, измененной с помощью фрагмента без UI - отлично. Затем, когда я закончил, я могу использовать контекст приложения, чтобы отменить подключение к службе и отменить привязку. Я не должен получать предупреждения об утечках службы. (Я должен, вероятно, указать, что это стандарт и рекомендуется поддерживать экземпляры изменений конфигурации)

Последний момент этой проблемы заключался в том, что я не знал, могу ли я несколько раз связываться с одним и тем же контекстом - документация по привязкам подразумевает наличие некоторой зависимости между привязкой и жизненным циклом контекста, и поэтому я был обеспокоен тем, что мне придется сделайте мою собственную форму отсчета ссылок. Я посмотрел исходный код и оказался здесь: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4.2_r1/android/app/LoadedApk.java#LoadedApk.forgetServiceDispatcher%28android.content.Context%2Candroid.content.ServiceConnection%29

Реально, эти строки:

sd = map.get(c);
    if (sd != null) {
        map.remove(c);
        sd.doForget();
        if (map.size() == 0) {
            mServices.remove(context);
        }

Выясните, что map используется для подсчета ссылок, о котором я беспокоился.

Итак, забрать домой:

  • Связанный сервис отлично работает с контекстом приложения, и мы ДОЛЖНЫ сделать это, чтобы предотвратить утечку служебного соединения из одной активности в другую во время изменения конфигурации.
  • Я могу безопасно поддерживать мое сервисное соединение на фрагменте, отличном от UI, и использовать его для отвязывания, когда я закончил.

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

ОБНОВЛЕНИЕ и протестированное решение: Я сделал код для проверки этого и опубликован здесь: https://github.com/samskiter/BoundServiceTest

Кажется, что это работает очень хорошо, и фрагмент non-ui (фрагмент данных) действует как хороший прокси-слушатель во время изменений поворота, чтобы уловить результаты из службы (намерение слушателей заключается в том, чтобы тесно привязывать запросы к пользовательскому интерфейсу в чтобы гарантировать, что он остается отзывчивым. Очевидно, что любые изменения модели могут распространяться на пользовательский интерфейс через наблюдателей.)

Изменить: я думал, что должен явно отвечать на вопросы в OP...

  • Должен ли я использовать первый метод (действия с временными контекстами)? Или второй (просто привяжите службу к контексту приложения)? Второй

  • Я правильно понимаю, что контекст приложения может несколько раз связываться с сервисом, а затем отвязать его от одного и того же количества раз? (Например, вы можете иметь несколько допустимых привязок PER-контекст)? Да

  • Может ли использование моего собственного контекста (новый Context()) в первом решении вызвать какие-либо проблемы? Это даже не возможно

Окончательное резюме:

Этот шаблон должен быть довольно мощным - я могу расставить приоритеты для сетевых IO (или других задач), поступающих из разных источников в моем приложении. Я мог бы сделать предварительную работу, сделав небольшую io, о которой попросил пользователь, одновременно я мог бы использовать функцию переднего плана для синхронизации всех моих данных пользователей. Как служба переднего плана, так и активность могут быть привязаны к одной и той же сетевой службе, чтобы выполнить свои запросы.

Все это при том, что сервис работает только в том случае, если это необходимо - то есть он отлично работает с Android.

Я очень рад получить это в приложении в ближайшее время.

ОБНОВЛЕНИЕ: Я попытался написать это и дать некоторый контекст более широкой проблеме фоновой работы в блоге здесь: http://blog.airsource.co.uk/2014/09/10/android-bound-services/

Ответ 2

Можете ли вы не просто выбрать конфигурации, которые вы бы хотели обработать с помощью атрибута configChanges в манифесте, и внести изменения ориентации в пользовательский интерфейс вручную? В этом случае вам нужно только привязать к службе в onCreate, а затем отключить в onDestroy.

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

  

    class MyServiceConnection implements ServiceConnection,Parcelable {
                public static final Parcelable.Creator CREATOR
                = new Parcelable.Creator() {
                    public MyServiceConnection createFromParcel(Parcel in) {
                        return new MyServiceConnection(in);
                    }

                    public MyServiceConnection[] newArray(int size) {
                        return new MyServiceConnection[size];
                    }
                };

                @Override
                public int describeContents() {
                    return 0;
                }

                @Override
                public void writeToParcel(Parcel dest, int flags) {

                }

                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {

                }

                @Override
                public void onServiceDisconnected(ComponentName name) {

                }
            }
            MyServiceConnection myServiceConnection;
            boolean configChange = false;

            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
                if (savedInstanceState != null) {
                    myServiceConnection = savedInstanceState.getParcelable("serviceConnection");
                } else {
                    myServiceConnection = new MyServiceConnection();
                }

            }
            @Override
            protected void onSaveInstanceState(Bundle outState) {
                super.onSaveInstanceState(outState);
                if (myServiceConnection != null) {
                    outState.putParcelable("serviceConnection",myServiceConnection);
                    configChange = true;
                }
            }
            @Override
            protected void onDestroy() {
                super.onDestroy();
                if (!configChange && myServiceConnection != null){
                    unbindService(myServiceConnection);
                }
            }
        }

Ответ 3

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

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

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

public class RSSPullService extends IntentService {

    @Override
    protected void onHandleIntent(Intent workIntent) {
    // Gets data from the incoming Intent
    String dataString = workIntent.getDataString();
    ...
    // Do work here, based on the contents of dataString
    ...
    }
}

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

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

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

Изменить - ответьте на вопросы в комментарии

IntentService - относительно небольшой класс. Это позволяет легко модифицировать. Код запаса для IntentService вызывает stopSelf() и умирает, когда заканчивается работа. Это можно легко устранить. Изучив источник для IntentService (см. Предыдущую ссылку), вы можете увидеть, что он уже работает уже с очереди, получая сообщения в onStart(), а затем выполняя их в порядке, полученном, как описано в комментарии. Переопределение onStart() позволит вам реализовать новую структуру очереди для удовлетворения ваших потребностей. Используйте пример кода для обработки входящего сообщения и получите Intent, а затем создайте собственную структуру данных для обработки приоритета. Вы должны иметь возможность запускать/останавливать свои веб-запросы в IntentService так же, как в Service. Таким образом, переопределяя onStart() и onHandleIntent(), вы сможете делать то, что хотите.

Ответ 4

У меня была аналогичная проблема, когда у меня есть связанная служба, используемая в Activity. Внутри действия я определяю ServiceConnection, mConnection, а внутри onServiceConnected я устанавливаю поле класса syncService, что ссылка на Сервис:

private SynchronizerService<Entity> syncService;

(...)

/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {

    @Override
    public void onServiceConnected(ComponentName className, IBinder service) {
        // We've bound to LocalService, cast the IBinder and get
        // LocalService instance
        Log.d(debugTag, "on Service Connected");
        LocalBinder binder = (LocalBinder) service;
        //HERE
        syncService = binder.getService();
        //HERE
        mBound = true;
        onPostConnect();
    }

    @Override
    public void onServiceDisconnected(ComponentName arg0) {
        Log.d(debugTag, "on Service Disconnected");
        syncService = null;
        mBound = false;
    }
};

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

Я собирался реализовать решение, предложенное Сэмом, используя сохранившийся фрагмент, чтобы сохранить переменную, но сначала вспомнил, чтобы попробовать простую вещь: установка переменной syncService в static. , а ссылка на соединение - поддерживается при изменении ориентации!

Итак, теперь у меня

private static SynchronizerService<Entity> syncService = null;

...

/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {

    @Override
    public void onServiceConnected(ComponentName className, IBinder service) {
        // We've bound to LocalService, cast the IBinder and get
        // LocalService instance
        Log.d(debugTag, "on Service Connected");
        LocalBinder binder = (LocalBinder) service;
        //HERE
        if(syncService == null) {
            Log.d(debugTag, "Initializing service connection");
            syncService = binder.getService();
        }
        //HERE
        mBound = true;
        onPostConnect();
    }

    @Override
    public void onServiceDisconnected(ComponentName arg0) {
        Log.d(debugTag, "on Service Disconnected");
        syncService = null;
        mBound = false;
    }
};