Подтвердить что ты не робот

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

Вопрос:

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


Детали:

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

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

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

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

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

  • Требование (1) в любом режиме = NewsReaderActivity.
  • Требование (2) в режиме двойной панели = NewsReaderActivity.
  • Требование (2) в режиме с одной панелью = 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; мы бы создали новую задачу, и в стеке не было "предыдущего" действия. Таким образом, вызов finish() просто очистит весь стек.

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

Ответ 2

Когда я использую подход с двойной панелью/одиночной панелью в своих приложениях, я использую только одно действие. В этом случае это означает избавиться от 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" .

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

Ответ 3

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

  • В Service постройте стек Intent с Intent для NewsReaderActivity в нижней части стека и для ArticleActivity вверху.
  • Используйте PendingIntent.getActivities() и передайте в стек, созданный в Шаге 1, чтобы получить PendingIntent, представляющий полный стек.
  • В 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. Можно продолжить добавление Intents в стек с помощью addNextIntent() и, наконец, вызвать getPendingIntent() для достижения чего-то похожего на PendingIntent.getActivities() (я полагаю).

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

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

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

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