Фрагменты и уведомления: укажите различные действия из уведомлений; В зависимости от конфигурации экрана

Вопрос:

Как определить, какая Activity должна быть включена, если цель может зависеть от конфигурации (размер экрана, ориентация и т. Д.); Как часто бывает, когда используется Fragment s?


Детали:

Рассмотрим образец NewsReader, который демонстрирует, как использовать Fragment s для создания приложения, которое хорошо воспроизводится с несколькими размерами экрана и ориентациями. Это приложение структурировано следующим образом:

  • A HeadlinesFragment .
  • ArticleFragment .
  • «Основная» деятельность ( NewsReaderActivity ). В режиме двойной панели это действие содержит оба фрагмента. В режиме с одной панелью он содержит только HeadlinesFragment .
  • ArticleActivity . Это действие используется только в режиме одиночной панели; И он содержит ArticleFragment .

Теперь предположим, что я должен улучшить это приложение, чтобы добавить фоновый Service который слушает новости и уведомляет пользователя через уведомления о статусной строке всякий раз, когда появляются новые новости. Разумный список требований может выглядеть следующим образом:

  1. Если есть несколько обновлений новостей, нажатие на уведомление всегда должно включать пользователя в список заголовков.
  2. Если есть только одно обновление, нажатие на уведомление должно открыть новую новостную статью.

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

  1. Требование (1) в любом режиме = NewsReaderActivity .
  2. Требование (2) в режиме двойной панели = NewsReaderActivity .
  3. Требование (2) в режиме с одной ArticleActivity = ArticleActivity .

Каким будет элегантный способ достижения (2) и (3) выше? Я думаю, что можно с уверенностью исключить возможность того, что Service зондирование текущей конфигурации решит, какую активность нужно настроить с помощью PendingIntent .

Одним из решений, о которых я думал, было пропустить (2) и всегда делать (3) – т.е. всегда запускать ArticleActivity если есть только одно обновление новостей. Этот фрагмент статьи ArticleActivity выглядел многообещающим:

 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //... //... // If we are in two-pane layout mode, this activity is no longer necessary if (getResources().getBoolean(R.bool.has_two_panes)) { finish(); return; } //... //... } 

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

Однако это не будет работать в нашем случае, поскольку у намерения будет установлен флаг FLAG_ACTIVITY_NEW_TASK ; Мы бы создали новую задачу, и в стеке не было «предыдущего» действия. Итак, call finish() просто очистит весь стек.

Итак, как решить, какую активность следует запускать из уведомления, если запуск активности зависит от конфигурации экрана?

Solutions Collecting From Web of "Фрагменты и уведомления: укажите различные действия из уведомлений; В зависимости от конфигурации экрана"

Это действительно хороший вопрос!

Я думаю, что можно с уверенностью исключить возможность того, что сервисное зондирование текущей конфигурации решит, какую активность нужно настроить с помощью PendingIntent.

Я согласен с тем, что было бы предпочтительнее принимать решения пользовательского интерфейса на уровне пользовательского интерфейса, но предоставление решения для принятия решений, безусловно, было бы целесообразным выбором. Вы можете использовать статический метод для класса уровня пользовательского интерфейса, чтобы сохранить код решения технически вне службы (например, статический createArticlePendingIntent() для NewsReaderActivity который служба использует для создания своего Notification ).

Итак, как решить, какую активность следует запускать из уведомления, если запуск активности зависит от конфигурации экрана?

Используйте getActivity() PendingIntent для NewsReaderActivity в своем Notification , с достаточным количеством дополнительных NewsReaderActivity которые NewsReaderActivity знает, что в этом сценарии «показать статью». Прежде чем он setContentView() , определите, является ли ArticleActivity правильным ответом. Если так, NewsReaderActivity вызывает startActivity() чтобы запустить ArticleActivity , а затем вызывает finish() чтобы избавиться от себя (или нет, если вы хотите, чтобы BACK из статьи переходила в NewsReaderActivity ).

Или воспользуйтесь getActivity() PendingIntent для ICanHazArticleActivity в вашем Notification . ICanHazArticleActivity есть Theme.NoDisplay , поэтому он не будет иметь пользовательский интерфейс. Он принимает решение о запуске NewsReaderActivity или NewsReaderActivity , вызывает startActivity() при правильном ответе, а затем вызывает finish() . Преимущество перед предыдущим решением заключается в том, что нет краткой вспышки NewsReaderActivity если конечным пунктом назначения является ArticleActivity .

Или используйте параметр createArticlePendingIntent() упомянутый мной в первом абзаце моего ответа.

Могут быть и другие варианты, но это то, что приходит на ум.

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

Во-первых, вы контролируете внешний вид фрагментов, используя макеты XML для разных конфигураций, например:

Одиночная панель (res / layout):

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal" > <FrameLayout android:id="@+id/mainFragment" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout> 

Двойное стекло (res / layout-xlarge-land):

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal" > <FrameLayout android:id="@+id/mainFragment" android:layout_weight="2" android:layout_width="0dp" android:layout_height="fill_parent" /> <FrameLayout android:id="@+id/detailsFragment" android:layout_weight="1" android:layout_width="0dp" android:layout_height="fill_parent" /> </LinearLayout> 

В NewsReaderActivity вам нужно только проверить, какие фрагменты существуют в макете:

 boolean isMainFragment = (findViewById(R.id.mainFragment) != null); if (isMainFragment) { mainFragment = new ListFragment(); } boolean isDetailFragment = (findViewById(R.id.detailsFragment) != null); if (isDetailFragment) { detailFragment = new DetailsFragment(); } FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); if (isMainFragment ) { transaction.add(R.id.mainFragment, mainFragment); } if (isDetailFragment ) { transaction.add(R.id.detailFragment, detaislFragment); } transaction.commit(); 

Затем, когда вы находитесь в режиме одиночной панели (detailsFragment not existing) и хотите показать экран подробностей, вы просто запускаете ту же самую активность снова, но включаете параметр в намерение указать, какой контент нужен:

 void onViewDetails() { Intent i = new Intent(this, NewsReaderActivity.class); i.putExtra("showDetails", true); startActivity(i); } 

В onCreate () вашей активности вы выбираете фрагмент в зависимости от этого параметра:

 boolean isDetailFragment = (findViewById(R.id.detailsFragment) != null); if (!isDetailFragment) { boolean showDetails = getIntent().getBooleanExtra("showDetails", false); if (showDetails) { mainFragment = new DetailsFragment(); } else { mainFragment = new ListFragment(); } } 

Теперь, когда вы, наконец, запускаете мероприятие из уведомления, вы больше не заботитесь о текущей ориентации экрана!

Просто укажите флаг «showDetails» в своем намерении и установите его соответствующим образом, как это сделано в onViewDetails() . Затем ваша активность покажет оба фрагмента, когда вы находитесь в режиме двойной панели (вы все равно можете сделать какое-то особое поведение, если «showDetails» истинно), или иначе, когда вы находитесь в конфигурации, которая требует режима одиночной боли, то фрагмент, который вы указали в Отображается флаг showDetails.

Надеюсь, это поможет и даст вам хорошее представление об этом подходе.

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

  1. В Service создайте стек Intent с Intent для NewsReaderActivity в нижней части стека и для NewsReaderActivity вверху.
  2. Используйте PendingIntent.getActivities() и передайте в стек, созданный на PendingIntent 1, чтобы получить PendingIntent представляющий полный стек.
  3. В ArticleActivity у нас есть следующий код:

     @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //... //... // If we are in two-pane layout mode, this activity is no longer necessary if (getResources().getBoolean(R.bool.has_two_panes)) { finish(); return; } //... //... } 

Итак, вы сначала нацеливаете ArticleActivity – который делает своего рода самоанализ, чтобы решить, полезен ли он в текущей конфигурации. Если нет, он просто уходит с пути с finish() . Поскольку NewsReaderActivity уже присутствует «до» ArticleActivity в back-stack, это приведет вас к NewsReaderActivity .

Это казалось идеальным решением – за исключением одного момента, который я упустил: PendingIntent.getActivities() работает только с API 11 или выше. В библиотеке поддержки есть приблизительный эквивалент: TaskStackBuilder . Можно добавить дополнения в стек с addNextIntent() и, наконец, вызвать getPendingIntent() для достижения чего-то похожего на PendingIntent.getActivities() (я предполагаю).

Тем не менее, на устройствах pre-HC это приведет к тому, что в новой задаче будет запущено только самое Activity из новой задачи (выделение мое):

На устройствах под управлением Android 3.0 или новее вызовы метода startActivities () или отправки PendingIntent, сгенерированного getPendingIntent (int, int), построят синтетический задний стек в соответствии с предписаниями. На устройствах, работающих с более ранними версиями платформы, эти же вызовы будут вызывать самую верхнюю активность в поставляемом стеке, игнорируя остальную часть синтетического стека и позволяя обратному ключу вернуться к предыдущей задаче .

Таким образом, на устройствах pre-HC, отжатие назад от ArticleActivity все равно вернет вас к задаче, которая выполнялась до нашего. Это не то, что мы хотим.

Вероятно, я скоро поделюсь своим проектом. Я также беспокоился о том, чтобы начинать новые задачи, даже когда я делал уведомления внутри приложения (например, уведомление «новая новостная статья», когда я читаю статью). Надеюсь, это будет отдельный вопрос.