Android: launchMode 中的 bug = “ singleTask”?-> 活动堆栈没有保留

我的主要活动 A在清单中设置为 android:launchMode="singleTask"。现在,无论何时我从那里开始另一个活动,例如 B,然后按手机上的 HOME BUTTON返回到主屏幕,然后再次回到我的应用程序,或者通过按下应用程序的按钮或按下 HOME BUTTON长键来显示我最近的应用程序,它不保留我的活动堆栈,直接返回到 A而不是预期的活动 B

这里有两种行为:

Expected: A > B > HOME > B
Actual: A > B > HOME > A (bad!)

是我漏掉了什么设置,还是这是个漏洞?如果是后者,在修复 bug 之前是否有解决办法?

仅供参考: 这个问题已经讨论过 给你了。然而,似乎还没有任何真正的解决办法。

39376 次浏览

如果 A 和 B 都属于同一个 Application,尝试删除

android:launchMode="singleTask"

从您的 Activities和测试,因为我认为默认行为是您所描述的预期。

Stefan 你找到答案了吗?我为此组装了一个测试用例,看到了相同的(令人困惑的)行为... ... 我将粘贴下面的代码,以防有人过来看到显而易见的东西:

Xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example" >


<uses-sdk android:minSdkVersion="3"/>


<application android:icon="@drawable/icon" android:label="testSingleTask">


<activity android:name=".ActivityA"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>


<activity android:name=".ActivityB"/>


</application>
</manifest>

活动 A.java:

public class ActivityA extends Activity implements View.OnClickListener
{
@Override
public void onCreate( Bundle savedInstanceState )
{
super.onCreate( savedInstanceState );
setContentView( R.layout.main );
View button = findViewById( R.id.tacos );
button.setOnClickListener( this );
}


public void onClick( View view )
{
//Intent i = new Intent( this, ActivityB.class );
Intent i = new Intent();
i.setComponent( new ComponentName( this, ActivityB.class ) );
startActivity( i );
}
}

活动 B.java:

public class ActivityB extends Activity
{
@Override
public void onCreate( Bundle savedInstanceState )
{
super.onCreate( savedInstanceState );
setContentView( R.layout.layout_b );
}
}

我试过更改 minSdkVersion,但没有用。这似乎只是一个错误,至少根据 文件的说法是这样的:

如上所述,“ singleTask”或“ singleInstance”活动的实例永远不会超过一个,因此实例需要处理所有新意图。“ singleInstance”活动总是在堆栈的顶部(因为它是任务中唯一的活动) ,所以它总是处于处理意图的位置。但是,“ singleTask”活动在堆栈中可能有也可能没有其他活动。如果它这样做了,它就不能处理意图,意图就会被丢弃。(即使意图被放弃了,它的到来也会使任务显得更加突出,并保持原样。)

这不是窃听器。当启动一个现有的 singleTask活动时,堆栈中位于它上面的所有其他活动都将被销毁。

当您按下 HOME并再次启动活动时,ActivityManger调用一个意图

{act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER]flag=FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_RESET_IF_NEEDED cmp=A}

So the result is A > B > HOME > A.

当 A 的启动模式是“标准”时就不一样了。包含 A 的任务将出现在前台,并保持与以前相同的状态。

您可以在 C 的 onCreate 方法中创建一个“ Standard”活动,例如 C 作为启动程序和 startActivity (A)

或者

只要删除 launchMode="singleTask"并设置 FLAG_ACTIVITY_CLEAR_TOP|FLAG_ACTIVITY_SINGLE_TOP标志,每当调用一个意图到 A

每当你按下主页按钮返回到你的主页面,活动堆栈就会杀死一些先前启动和运行的应用程序。

为了验证这个事实,尝试从通知面板启动一个应用程序后,从 A 到 B 在您的应用程序,并返回使用返回按钮... ... 你会发现你的应用程序在同样的状态,因为你离开了它。

我认为这是你想要的行为:

SingleTask 在主机上重置堆栈,出于一些我不明白的迟钝的原因。 相反,解决方案是不使用 singleTask,而是使用 标准单人秀作为启动器活动(不过我迄今为止只尝试过 singleTop)。

因为应用程序之间有着密切的联系,所以开展这样的活动:

Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
if(launchIntent!=null) {
launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
}

将导致您的活动堆栈重新出现,而不会在旧堆栈上启动新活动(这是我以前的主要问题)。返回文章页面 旗帜是最重要的:

FLAG _ ACTIVITY _ NEW _ TASK 在 API 级别1中添加

如果设置了此属性,则此活动将成为关于此属性的新任务的开始 任务(从启动它的活动到下一个活动) 任务活动)定义用户可以执行的活动的原子组 任务可以移动到前台和后台; 所有 特定任务中的活动总是保持不变 有关任务的更多信息,请参见任务和后台堆栈。

此标志通常由希望呈现 “ launcher”样式的行为: 他们给用户一个单独的列表 可以完成的事情,否则完全独立运行 发射它们的活动。

当使用此标志时,如果活动的任务已经在运行 你现在开始,那么一个新的活动将不会开始; 相反,当前任务将被简单地放在 参见 FLAG _ ACTIVITY _ MULTIPLE _ TASK 标志来禁用此行为。

当调用方从 正在进行的活动。

还有:

FLAG _ ACTIVITY _ RESET _ TASK _ IF _ NEEDED 在 API 级别1中添加

如果设置了,则此活动将在新任务中启动,或者 把一项现有的工作带到最高层,然后作为 任务的前门。这将导致应用任何 亲缘关系需要在适当的状态(移动 活动) ,或者简单地将该任务重置为其 如果需要,初始状态。

如果没有它们,启动的活动只会被推到旧堆栈的顶部或其他一些不受欢迎的行为(当然是在这种情况下)

我相信没有收到最新意图的问题可以这样解决(从我的头脑中) :

@Override
public void onActivityReenter (int resultCode, Intent data) {
onNewIntent(data);
}

试试看!

http://developer.android.com/guide/topics/manifest/activity-element.htmlsingleTask

系统在新任务的根目录创建活动,并将意图路由到它。但是,如果活动的实例已经存在,系统将通过调用其 onNewInent ()方法将意图路由到现有实例,而不是创建新的实例。

这意味着,当 action.MAIN 和 classy.LAUNCHER 标志从 Launcher 指向你的应用程序时,系统宁愿将意图路由到现有的 ActivityA,而不是创建一个新任务并设置一个新的 ActivityA 作为根。它更愿意删除 ActivityA 所存在的所有活动,并调用它的 onNewInent ()。

如果你想同时捕获 singleTop 和 singleTask 的行为,那么使用 singleTask launchMode 创建一个单独的“委托”活动 SingleTaskActivity,它只是在 onCreate ()中调用 singleTop 活动,然后自己完成。SingleTop 活动仍然有 MAIN/LAUNCHER 意图过滤器继续作为应用程序的主启动器活动,但是当其他活动希望调用这个 singleTop 活动时,它必须调用 SingleTaskActivity 以保留 singleTask 行为。传递给 singleTask 活动的意图也应该传递给 singleTop 活动,因此下面的内容对我很有用,因为我想同时使用 singleTask 和 singleTop 启动模式。

<activity android:name=".activities.SingleTaskActivity"
android:launchMode="singleTask"
android:noHistory="true"/>


public class SingleTaskActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
intent.setClass(this, SingleTop.class);
startActivity(intent);
}
}

并且您的单个 Top 活动将继续保持其单个 Top 启动模式。

    <activity
android:name=".activities.SingleTopActivity"
android:launchMode="singleTop"
android:noHistory="true"/>

祝你好运。

当使用启动模式作为 singleTop 时,请确保在启动下一个活动时调用 Finish ()(对于当前活动,请说 A)(使用 startActivity (Inent)方法,请说 B)。这样当前的活动就会被破坏。 A-> B-> 暂停应用程序,点击启动图标,开始 在 a 的 oncreate 方法中,你需要一个检查,

if(!TaskRoot()) {
finish();
return;
}

这样,当启动应用程序时,我们检查根任务,以前的根任务是 B 而不是 A。因此,这个检查会破坏活动 A,并将我们带到活动 B,它当前位于堆栈的顶部。 希望对你有用。

这就是我最终解决这个奇怪行为的方法:

Application & Root activity
android:launchMode="singleTop"
android:alwaysRetainTaskState="true"
android:taskAffinity="<name of package>"


Child Activity
android:parentActivityName=".<name of parent activity>"
android:taskAffinity="<name of package>"
        <activity android:name=".MainActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

//尝试在主活动中使用 launchMode = “ singleTop”来维护应用程序的单个实例。

在 android 清单活动中添加以下内容,它将在视图顶部添加新任务,以销毁早期任务。 LaunchMode = “ singleTop”,如下所示

 <activity
android:name=".MainActivity"
android:label="@string/app_name"
android:launchMode="singleTop"
android:theme="@style/AppTheme.NoActionBar">
</activity>

我发现这个问题只有在启动器活动的启动模式设置为 singleTask 或 singleInstance 时才会发生。

因此,我创建了一个新的启动活动,其启动模式为标准或 singleTop。并使这个启动器活动调用我的旧主活动的启动模式是单任务。

LauncherActivity (standard/no history) -> MainActivity (singleTask).

设置启动画面为发射器活动。在我调用主活动后立即关闭发射器活动。

public LauncherActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(this, HomeActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
startActivity(intent);
finish();
}
}
<activity
android:name=".LauncherActivity"
android:noHistory="true"
android:theme="@style/Theme.LauncherScreen">


<intent-filter>
<action android:name="android.intent.action.MAIN"/>


<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>


</activity>


<!-- Launcher screen theme should be set for the case that app is restarting after the process is killed. -->
<activity
android:name=".MainActivity"
android:launchMode="singleTask"
android:theme="@style/Theme.LauncherScreen"/>

优点: 可以将 MainActivity 的启动模式保持为 singleTask,以确保总是不超过一个 MainActivity。

在儿童活动中或在 B 活动中

@Override
public void onBackPressed() {
   

Intent intent = new Intent(getApplicationContext(), Parent.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
finish();
    



}