Привязать к сервису из нового контекста для изменения конфигурации или привязки из контекста приложения?
Я пытаюсь работать, если связанная служба подходит для выполнения фоновой работы в моем приложении. Требования заключаются в том, что различные компоненты приложения могут передавать через него веб-запросы с различным приоритетом. (Таким образом, служба должна поддерживать какую-то очередь и иметь возможность отменить текущие запросы для других с более высоким приоритетом). Я хотел бы, чтобы служба была относительно ненавязчивой для пользователя, так что они не находят, что она работает после того, как они выполнены с приложением - если я хочу сделать что-то более важное, которое продолжается, пока приложение закрыто, я могу использовать 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;
}
};