【问题标题】:Android: bug in launchMode="singleTask"? -> activity stack not preservedAndroid:launchMode =“singleTask”中的错误? -> 活动堆栈未保留
【发布时间】:2011-01-25 22:11:35
【问题描述】:

我的主要活动A 在清单中设置为android:launchMode="singleTask"。现在,每当我从那里开始另一项活动时,例如B 并按下手机上的HOME BUTTON 以返回主屏幕,然后再次返回我的应用程序,方法是按下应用程序的按钮或按下HOME BUTTONlong 以显示我最近没有的应用程序保留我的活动堆栈并直接返回到A,而不是预期的活动B

这里有两种行为:

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

是否有我遗漏的设置或者这是一个错误?如果是后者,在修复错误之前是否有解决方法?

仅供参考:这个问题已经讨论过here。但是,目前似乎还没有任何真正的解决方案。

【问题讨论】:

  • 只想补充一点,我也看到了同样的行为,根据我对文档的解释,这似乎不正确
  • 是的,这是错误,因为堆栈没有被保留,并且在删除单任务启动模式属性时,应用程序的行为不符合预期。
  • 如果它不是一个错误,那么文档中有一个。看一下图 4 和它前面的段落:developer.android.com/guide/components/…但是,如果您启动一个指定 singleTask 启动模式的活动,那么如果该活动的实例存在于后台任务中,则整个任务将被带到前景。此时,后退堆栈现在包括来自前移任务的所有活动,位于堆栈顶部。如果不是bug,singleTask和singleInstance在实际行为上有什么区别?
  • 你的活动B怎么样?它是标准的还是单任务的?
  • 在下面查看我的解决方案。

标签: android activity-stack


【解决方案1】:

这不是错误。当启动现有的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}

所以结果是 A > B > HOME > A。

A 的 launchMode 为“Standard”时不同。包含 A 的任务将进入前台并保持与之前相同的状态。

您可以创建一个“标准”活动,例如。 C作为启动器和C的onCreate方法中的startActivity(A)

只要删除launchMode="singleTask" 并设置FLAG_ACTIVITY_CLEAR_TOP|FLAG_ACTIVITY_SINGLE_TOP 标志,只要调用A 的意图

【讨论】:

  • 嗨,但在文档中,没有任何地方提到“当启动现有的“singleTask”活动时,堆栈中它上面的所有其他活动都将被销毁。”以及为什么启动模式为“标准”时行为会有所不同?如果任务已从最近的列表中恢复,那么至少应该恢复任务,不是吗?活动管理器应该设置FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY 而不是FLAG_ACTIVITY_RESET_IF_NEEDED
  • 没有证据表明这应该是一个错误。正如其他人已经说过的那样,文档表明该行为 一个错误,并且存在与此(错误)行为相关的问题 23735277 文件。
  • @renchenyu:我遇到了同样的问题。但我发现谷歌地图也使用singleTask,它不使用任何其他活动,如你说的C。你能检查一下吗?imgbin.org/images/15515.gif
  • 这真是太棒了。如何在不将新活动放在旧活动之上的情况下恢复堆栈顶部?
  • 这实际上是一个错误,或者它是一个文档错误。非此即彼。有没有人找到更新的解决方案?
【解决方案2】:

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

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

这意味着当 action.MAIN 和 category.LAUNCHER 标志从 Launcher 定位到您的应用程序时,系统宁愿将意图路由到现有的 ActivityA,而不是创建新任务并将新的 ActivityA 设置为根。它宁愿拆除现有任务 ActivityA 所在的所有活动,并调用它的 onNewIntent()。

如果您想同时捕获 singleTop 和 singleTask 的行为,请使用 singleTask launchMode 创建一个名为 SingleTaskActivity 的单独“委托”活动,该活动只需在其 onCreate() 中调用 singleTop 活动,然后自行完成。 singleTop 活动仍将具有 MAIN/LAUNCHER 意图过滤器以继续充当应用程序的主要 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);
    }
}

您的 singleTop 活动将继续使用其 singleTop 启动模式。

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

祝你好运。

【讨论】:

  • 很棒的解决方案。为了完整起见,您还需要重定向发送到SingleTaskActivityonNewIntent() 函数的意图。此外,如果您的应用使用深层链接,请确保在清单中的 SingleTaskActivity 上声明您的深层链接意图过滤器。
  • 非常有用,就像这篇文章一样:labs.comcast.com/…。我必须添加 intent.addFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);将意图转发给 SingleTop 活动以确保目标活动确实在顶部并作为 singleTop 处理,否则,如果此时在其顶部有另一个子活动,则会创建一个新实例。
  • 很好的解决方案!我已将android:noHistory="true" 添加到这两项活动中。
【解决方案3】:

Stefan,你找到答案了吗?我为此编写了一个测试用例,并且看到了相同的(令人困惑的)行为...我将粘贴下面的代码以防有人出现并看到明显的东西:

AndroidManifest.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>

ActivityA.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 );
  }
}

ActivityB.java:

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

我尝试更改 minSdkVersion 无济于事。这似乎只是一个错误,至少根据documentation,它声明如下:

如上所述,“singleTask”或“singleInstance”活动的实例永远不会超过一个,因此该实例应处理所有新意图。 “singleInstance”活动始终位于堆栈顶部(因为它是任务中唯一的活动),因此它始终处于处理意图的位置。但是,“singleTask”活动在堆栈中可能有也可能没有其他活动。如果是这样,它就无法处理该意图,并且该意图被丢弃。 (即使意图被丢弃,它的到来也会导致任务来到前台,它会保留在那里。)

【讨论】:

    【解决方案4】:

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

    singleTask 出于某种我不明白的迟钝原因在主页上重置堆栈。 解决方案是不使用 singleTask,而是使用 standardsingleTop 来进行启动器活动(不过,我迄今为止只尝试过 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 中添加

    如果设置,此活动将成为此活动的新任务的开始 历史堆栈。一个任务(从启动它的活动到下一个 任务活动)定义了一个原子活动组,用户可以 搬去。任务可以移到前台和后台;所有的 特定任务中的活动始终保持不变 命令。有关任务的更多信息,请参阅任务和返回堆栈。

    此标志通常由想要呈现 “启动器”风格的行为:他们给用户一个单独的列表 可以做的事情,否则完全独立运行 启动它们的活动。

    当使用这个标志时,如果一个任务已经在为这个活动运行 您现在正在开始,则不会开始新的活动; 相反,当前任务将简单地被带到前面 屏幕上的状态。请参阅 FLAG_ACTIVITY_MULTIPLE_TASK 用于禁用此行为的标志。

    当调用者从 正在启动的活动。

    还有:

    FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 在 API 级别 1 中添加

    如果设置,并且此活动正在新任务中启动,或者 将现有任务置于顶部,然后它将作为 前门的任务。这将导致应用任何 需要使该任务处于适当状态的亲和力(无论是移动 活动),或简单地将该任务重置为其 如果需要,初始状态。

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

    我相信没有收到最新 Intent 的问题可以这样解决(我想不到):

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

    试试看!

    【讨论】:

    • 我多年来一直在使用这种方法,除了没有 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 标志,只是新的任务标志。效果很好,除了 onNewIntent 似乎并没有用这种标志/模式的组合触发。任何想法如何获得所有相同的行为,但也让 onNewIntent 触发以将数据传递给现有实例?除了那个问题,我认为这是解决这个长期存在的 bug 的最佳方法。
    • 检查我更新的答案。我知道我解决了。我想是这样的。但在我的脑海里。请让我知道不是那样的。那我稍后在工作中检查解决方案。
    • 2 年后来到这里,这真的很重要,亲和力问题对我来说是最大的不同 - 它将活动拉入现有任务(即使它已被清除)
    【解决方案5】:

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

    所以,我创建了一个新的启动器活动,它的启动模式是标准或单顶。并让这个启动器活动调用我的启动模式为单任务的旧主要活动。

    LauncherActivity (standard/no history) -&gt; 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 始终不超过一个。

    【讨论】:

      【解决方案6】:

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

      android:launchMode="singleTask"
      

      来自您的 Activities 并进行测试,因为我认为默认行为是您所描述的预期行为。

      【讨论】:

      • 如果我删除 android:launchMode="singleTask" 它会在我调用 Intent 时创建一个新的 A 实例(我的主要活动),例如我有一个菜单快捷方式可以返回我的主要活动(我的应用程序主屏幕)。
      • 为了返回主屏幕,我使用带有 FLAG_ACTIVITY_CLEAR_TOP 的 Intent。这样,您就可以回到主 Activity 的第一个实例,并删除所有位于顶部的 Activity。
      • 默认为标准:默认。系统总是在目标任务中创建一个新的活动实例并将意图路由到它。 developer.android.com/guide/topics/manifest/…
      • 在下面查看我的回答,了解如何在删除 singleTask 时不创建新活动。
      【解决方案7】:

      每当您按下主屏幕按钮返回主屏幕时,活动堆栈都会终止一些之前启动和运行的应用程序。

      要验证这一事实,请尝试在您的应用程序中从 A 转到 B 后从通知面板启动一个应用程序,然后使用返回按钮.........您会发现您的应用程序在同一个离开时的状态。

      【讨论】:

        【解决方案8】:

        当使用作为 singleTop 的启动模式时,请确保在启动下一个活动(使用 startActivity(Intent) 方法说 B)时调用 finish()(在当前活动上说 A)。这样当前的活动就会被破坏。 A -> B -> 暂停应用程序并单击启动器图标,启动 A 在A的oncreate方法中,需要打勾,

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

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

        【讨论】:

          【解决方案9】:

          这就是我最终解决这种奇怪行为的方法。在 AndroidManifest 中,这是我添加的:

          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>"
          

          【讨论】:

            【解决方案10】:

            在 android manifest Activity 中添加下面的内容,它将在视图顶部添加新任务,从而破坏之前的任务。 android:launchMode="singleTop" 如下

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

            【讨论】:

              【解决方案11】:

              在子活动中或在 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();
                  
              
              }
              

              【讨论】:

                【解决方案12】:
                        <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" 来维护应用程序的单个实例。去显化和改变。

                【讨论】:

                • singleTop 可以创建一个活动的多个实例,但它只在堆栈顶部维护一个实例。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-01-04
                相关资源
                最近更新 更多