碎片和通知:将不同的活动定位于通知;取决于屏幕配置

时间:2012-05-28 11:44:49

标签: java android android-fragments android-notifications

问题:

如果目标可能取决于配置(屏幕大小,方向等),如何确定Activity Notification应该启动的内容;通常就是使用Fragment s?

的情况

详细说明:

让我们考虑一下NewsReader sample,它演示了如何使用Fragment生成一个能够很好地适应多种屏幕尺寸和方向的应用。这个应用程序的结构如下:

  • A HeadlinesFragment
  • ArticleFragment
  • “主要”活动(NewsReaderActivity)。在双窗格模式下,此活动包含两个片段。在单窗格模式下,它仅包含HeadlinesFragment
  • ArticleActivity。此活动仅用于单窗格模式;它包含ArticleFragment

现在,假设我要增强此应用程序以添加侦听新闻更新的后台Service,并在有新消息时通过状态栏通知通知用户。合理的要求列表可能如下所示:

  1. 如果有多个新闻更新,点击通知应该始终将用户带到标题列表。
  2. 如果只有一个更新,点击通知应打开全新的新闻文章。
  3. 请注意,这些要求会根据当前配置转换为不同的目标活动。特别是,

    1. 要求(1)处于任一模式= NewsReaderActivity
    2. 双窗格模式中的要求(2)= NewsReaderActivity
    3. 单窗格模式下的要求(2)= ArticleActivity
    4. 实现上述(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()只会清除整个堆栈。

      那么,如果要启动的活动取决于屏幕配置,如何决定从通知启动哪些活动?

3 个答案:

答案 0 :(得分:7)

这是一个非常好的问题!

  

我认为可以安全地排除服务探测当前配置的可能性,以决定使用PendingIntent定位哪些活动。

我同意最好在UI层保留UI决策,但让服务做出决定肯定是一个权宜之计。您可以在UI层类上使用静态方法,以使决策代码在技术上保持在服务之外(例如,createArticlePendingIntent()上的静态NewsReaderActivity方法,该服务用于构建其Notification )。

  

那么,如果要启动的活动取决于屏幕配置,如何决定从通知启动哪些活动?

在您的getActivity()中使用PendingIntent NewsReaderActivity NotificationNewsReaderActivity知道它在“显示文章”方案中有足够的额外内容。在致电setContentView()之前,请确定ArticleActivity是否是正确答案。如果是,NewsReaderActivity调用startActivity()来启动ArticleActivity,然后调用finish()以摆脱自身(或者,如果您希望文章中的BACK返回{{} 1}})。

或者,在NewsReaderActivity中为getActivity()使用PendingIntent ICanHazArticleActivityNotificationICanHazArticleActivity,因此它没有用户界面。它决定是启动Theme.NoDisplay还是NewsReaderActivity,在正确答案上调用ArticleActivity,然后调用startActivity()。与以前的解决方案相比,优势在于,如果最终目的地为finish(),则不会有NewsReaderActivity的短暂闪现。

或者,使用我在答案的第一段中提到的ArticleActivity选项。

也可能有其他选择,但这些都是我想到的。

答案 1 :(得分:5)

当我在我的应用程序中使用双窗格/单窗格方法时,我只使用一个活动。在这种情况下,它意味着摆脱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”为true,您仍然可以执行某些特殊操作),或者当您处于需要单一痛苦模式的配置中时,此时您指定的片段显示“showDetails”标志。

我希望这会有所帮助,并让您对这种方法有一个很好的理解。

答案 2 :(得分:1)

accepted answer中提到的无UI活动方法是我决定的。我尝试了另一个选项,但它没有成功。我试过的是:

  1. Service中,在堆栈底部为Intent构建一个Intent堆栈,在顶部构建NewsReaderActivity
  2. 使用ArticleActivity并传入在步骤1中创建的堆栈,以获得代表完整堆栈的PendingIntent.getActivities()
  3. PendingIntent中,我们有以下代码:

    ArticleActivity
  4. 因此,您首先定位@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()已经出现在后筹码中的“{1}}之前”,因此您需要NewsReaderActivity

    这似乎是完美的解决方案 - 除了我忽略的一点:ArticleActivity仅适用于API 11或更高版本。支持库中有一个粗略的等价物:TaskStackBuilder。可以继续使用NewsReaderActivity将Intent添加到堆栈中,最后调用PendingIntent.getActivities()来实现与addNextIntent()类似的东西(我假设)。

    但是,在HC之前的设备上,这将导致在新任务中启动堆栈中的最顶层 getPendingIntent()(强调我的):

      

    在运行Android 3.0或更高版本的设备上,调用startActivities()方法或发送由getPendingIntent(int,int)生成的PendingIntent将按照规定构造合成后端堆栈。 在运行旧版平台的设备上,这些相同的调用将调用提供的堆栈中的最顶层活动,忽略合成堆栈的其余部分并允许后退键导航回上一个任务。 / p>

    因此,对于pre-HC设备,从ArticleActivity返回仍将返回到我们之前运行的任务。这不是我们想要的。

    我很快就会分享我的项目。即使我正在进行应用程序内通知(例如,在阅读文章时发出“新闻报道”通知),我也担心开始新任务。我希望将其作为一个单独的问题发布。