如果目标可能取决于配置(屏幕大小,方向等),如何确定Activity
Notification
应该启动的内容;通常就是使用Fragment
s?
让我们考虑一下NewsReader sample,它演示了如何使用Fragment
生成一个能够很好地适应多种屏幕尺寸和方向的应用。这个应用程序的结构如下:
HeadlinesFragment
。ArticleFragment
。NewsReaderActivity
)。在双窗格模式下,此活动包含两个片段。在单窗格模式下,它仅包含HeadlinesFragment
。ArticleActivity
。此活动仅用于单窗格模式;它包含ArticleFragment
。现在,假设我要增强此应用程序以添加侦听新闻更新的后台Service
,并在有新消息时通过状态栏通知通知用户。合理的要求列表可能如下所示:
请注意,这些要求会根据当前配置转换为不同的目标活动。特别是,
NewsReaderActivity
。NewsReaderActivity
。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()
只会清除整个堆栈。
那么,如果要启动的活动取决于屏幕配置,如何决定从通知启动哪些活动?
答案 0 :(得分:7)
这是一个非常好的问题!
我认为可以安全地排除服务探测当前配置的可能性,以决定使用PendingIntent定位哪些活动。
我同意最好在UI层保留UI决策,但让服务做出决定肯定是一个权宜之计。您可以在UI层类上使用静态方法,以使决策代码在技术上保持在服务之外(例如,createArticlePendingIntent()
上的静态NewsReaderActivity
方法,该服务用于构建其Notification
)。
那么,如果要启动的活动取决于屏幕配置,如何决定从通知启动哪些活动?
在您的getActivity()
中使用PendingIntent
NewsReaderActivity
Notification
,NewsReaderActivity
知道它在“显示文章”方案中有足够的额外内容。在致电setContentView()
之前,请确定ArticleActivity
是否是正确答案。如果是,NewsReaderActivity
调用startActivity()
来启动ArticleActivity
,然后调用finish()
以摆脱自身(或者,如果您希望文章中的BACK返回{{} 1}})。
或者,在NewsReaderActivity
中为getActivity()
使用PendingIntent
ICanHazArticleActivity
。 Notification
有ICanHazArticleActivity
,因此它没有用户界面。它决定是启动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活动方法是我决定的。我尝试了另一个选项,但它没有成功。我试过的是:
Service
中,在堆栈底部为Intent
构建一个Intent
堆栈,在顶部构建NewsReaderActivity
。 ArticleActivity
并传入在步骤1中创建的堆栈,以获得代表完整堆栈的PendingIntent.getActivities()
。在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()
已经出现在后筹码中的“{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返回仍将返回到我们之前运行的任务。这不是我们想要的。
我很快就会分享我的项目。即使我正在进行应用程序内通知(例如,在阅读文章时发出“新闻报道”通知),我也担心开始新任务。我希望将其作为一个单独的问题发布。