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

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

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

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

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

Решение numero 2:

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

Вопросов:

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

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

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

редактировать

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

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

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

Во-первых, на основе предложения Дайан здесь: 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

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

Редактировать: я думал, что должен явно ответить на вопросы в OP …

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

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

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

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

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

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

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

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

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

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

  

     Класс MyServiceConnection реализует ServiceConnection, Parcelable {
                 Публичный статический окончательный Parcelable.Creator CREATOR
                 = New Parcelable.Creator () {
                     Public MyServiceConnection createFromParcel (Parcel in) {
                         Вернуть новый MyServiceConnection (in);
                     }

                     Public MyServiceConnection [] newArray (int size) {
                         Вернуть новый MyServiceConnection [размер];
                     }
                 };

                 @Override
                 Public int describeContents () {
                     Return 0;
                 }

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

                 }

                 @Override
                 Public void onServiceConnected (имя_компьютера, сервис IBinder) {

                 }

                 @Override
                 Public void onServiceDisconnected (имя_компьютера) {

                 }
             }
             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 = новый 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);
                 }
             }
         }

Существует гораздо более простой способ справиться с этой ситуацией, называемой 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 исходный код для IntentService (см. Предыдущую ссылку), вы можете увидеть, что он уже работает уже с очереди, получая сообщения в onStart (), а затем выполняя их в порядке, полученном, как описано в комментарии. Переопределение onStart () позволит вам реализовать новую структуру очереди для удовлетворения ваших потребностей. Используйте пример кода для обработки входящего сообщения и получения Intent затем просто создайте собственную структуру данных для обработки приоритета. Вы должны иметь возможность запускать / останавливать свои веб-запросы в IntentService же, как и в Service . Таким образом, переопределяя onStart () и onHandleIntent (), вы сможете делать то, что хотите.

У меня была аналогичная проблема, когда у меня есть связанная служба, используемая в 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; } }; 

Используя этот метод, всякий раз, когда меняется ориентация, я получаю syncService 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; } };