12

问题:

如果目标可能取决于配置(屏幕尺寸、方向等),如何决定应该启动什么ActivityNotification就像人们使用Fragments?


细节:

让我们考虑一下NewsReader 示例,该示例演示了如何使用Fragments 来生成一个在多种屏幕尺寸和方向上都能正常运行的应用程序。这个应用程序的结构如下:

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

现在,假设我要增强这个应用程序以添加一个背景Service,用于监听新闻更新并在有新新闻项目时通过状态栏通知通知用户。一个合理的需求列表可能是这样的:

  1. 如果有多个新闻更新,单击通知应始终将用户带到头条新闻列表。
  2. 如果只有一个更新,点击通知应该会打开全新的新闻文章。

请注意,这些要求会根据当前配置转化为不同的目标活动。尤其,

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

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

4

3 回答 3

7

这是一个非常好的问题!

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

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

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

在你的 中使用getActivity() PendingIntentfor ,有足够的额外内容知道它在这个“展示文章”场景中。在它调用之前,让它确定是否是正确的答案。如果是这样,调用launch ,然后调用摆脱它自己(或者不调用,如果你想从文章中返回到)。NewsReaderActivityNotificationNewsReaderActivitysetContentView()ArticleActivityNewsReaderActivitystartActivity()ArticleActivityfinish()NewsReaderActivity

或者,getActivity() PendingIntentICanHazArticleActivity你的Notification. ICanHazArticleActivityhas Theme.NoDisplay,所以它不会有 UI。它决定是否启动NewsReaderActivityArticleActivity调用startActivity()正确的答案,然后调用finish()。与之前的解决方案相比的优势在于,NewsReaderActivity如果最终目的地是,则不会出现短暂的闪烁ArticleActivity

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

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

于 2012-05-28T11:57:24.563 回答
5

当我在我的应用程序中使用双窗格/单窗格方法时,我只使用一个活动。在这种情况下,这意味着摆脱 ArticleActivity。以下是您可以继续进行的方法:

首先,您可以通过对不同配置使用 XML 布局来控制片段的外观,例如:

单窗格(分辨率/布局):

<?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 不存在)并想要显示详细信息屏幕时,您只需再次启动相同的活动,但在意图中包含一个参数以告知需要哪些内容:

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”标志。

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

于 2012-06-04T09:12:26.807 回答
1

接受的答案中提到的无 UI Activity 方法是我决定的。我尝试了另一种选择,但没有成功。我尝试的是这样的:

  1. 在 中Service,构建一个Intent堆栈,其中IntentforNewsReaderActivity位于堆栈底部,forArticleActivity位于堆栈顶部。
  2. 使用PendingIntent.getActivities()并传入在步骤 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于后台堆栈中的“之前”,因此这会将您带到NewsReaderActivity.

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

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

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

因此,在 HC 之前的设备上,从 ArticleActivity 按下返回仍会将您带回到我们之前运行的任务。这不是我们想要的。

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

于 2012-06-05T14:55:13.010 回答