Activity的LaunchMode和taskAffinity

本文详细解析了Android中Activity的四种LaunchMode(standard、singleTop、singleTask、singleInstance),并通过示例展示了不同模式下Activity的启动行为及任务栈的变化。此外还介绍了如何通过设置taskAffinity和allowTaskReparenting属性实现更灵活的任务管理和Activity关联。

做项目到现在都一直没有理解LaunchMode有什么用,或许根本就没真正花心思去看,所以今天把这部分整理下。

  设置Activity的LaunchMode属性可以决定这个Activity是和当前Task保持关联,还是说每次运行这个Activity是新建一个实例,还是保持单例。

  Task和Back Stack简介

  task是一组Activities的集合,一组Activities被Stack(back stack)所管理。

  在一个应用中,有3个activities,分别是activity1,activity2,activity3,首先activity1被start,此时,如果应用没有创建task则创建,并把activity1压入栈顶,activity1触发onCreate->onStart->onResume。

  

  接着activity1转向到activity2时,activity1先触发onPause,activity2触发onCreate->onStart->onResume,然后activity1触发onPause->onStop,activity2压入栈顶。

  

  以此类推,activity2转向activity3也是一样的步骤。那么当前栈顶是activity3。

  

  当我们按下手机上的返回键时,栈顶的activity3触发onPause,activity2需要从状态stop到pause,所以触发了onPause->onStart->onResume,activity3触发onStop->onDestory,因为activity3从栈顶弹出,所以触发onDestory,此时,activity2在栈顶。

  back stack

  如果继续按返回键,当前栈顶的activity弹出并被destory,直到home界面。当所有的activity都弹出了,这个task也就消亡了。

  当开始一个新的task时,前一个task被设置为后台,在后台,所有的activity都处理stop状态,但是back stack保留了所有后台activity的状态信息,只是丢失了焦点。

task

  反复的在两个activity之间切换,activity会产生多个独立的实例。

  stack

  查阅有关Activity生命周期更多说明。

  两种方式设置LaunchMode属性

  1.  在 manifest文件中设置

复制代码
<activity android:name=".activity.ActivityA"
        android:launchMode="standard">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
复制代码

  2.  使用Intent flags设置

Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClass(ActivityA.this, ActivityB.class);
startActivity(intent);

  四种LaunchMode说明

  standard

   不做任何设置,默认模式就是standard,activity在每次start时,都会有一个新的实例被task管理。下面看下代码实例。

复制代码
//ActivityA.java
Intent intent = new Intent();
intent.setClass(ActivityA.this, ActivityB.class);
startActivity(intent);

//ActivityB.java
Intent intent = new Intent();
intent.setClass(ActivityB.this, ActivityA.class);
startActivity(intent);
复制代码

 操作1:在ActivityA(蓝)和ActivityB(绿)之间重复切换,按返回键推到home界面。

  可以发现(蓝色86和绿色79的taskID)ActivityA和ActivityB都在同一个task,并且每次resume的实例都是不一样的。这说明在一个activity可以有多个实例在同一个task中。

  在按返回按键时,将依次弹出stack。

singleTop

  和standard一样,可以多次实例,但,如果处于当前栈顶并且接受到一个与当前activity一样类型的intent,那么不会创建一个新实例,而是触发onNewIntent()事件。

复制代码
//ActivityA.java
Intent intent = new Intent();
intent.setClass(ActivityA.this, ActivityA.class);
startActivity(intent);

@Override
protected void onNewIntent(Intent intent) {
    logger.d("onNewIntent " + this.hashCode() + " taskID "
                + this.getTaskId());
    super.onNewIntent(intent);
}
复制代码
复制代码
<activity android:name=".activity.ActivityA" android:label="ActivityA"
            android:launchMode="singleTop">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
复制代码

操作1:点击ActivityA上的按钮

  发现当点击按钮是ActivityA->onPause->onNewIntent->onResume,没有新建新的实例(蓝62)。

  这个模式在这个场景下比较有用,比如:如果有一个其他的应用想启动你的Activity(launch mode为singleTop),而你当前的Activity正好在栈顶,那么就会调用到onNewIntent方法。原文贴上:If an instance of the activity already exists at the top of the current task, the system routes the intent to that instance through a call to its onNewIntent() method。

 singleTask

  系统会创建一个新task(如果没有启动应用)和一个activity新实例在新task根部,然后,如果activity实例已经存在单独的task中,系统会调用已经存在activity的 onNewIntent()方法,而不是存在新实例,仅有一个activity实例同时存在。

复制代码
<activity android:name=".activity.ActivityA" android:label="ActivityA" android:launchMode="standard">
  <intent-filter>
     <action android:name="android.intent.action.MAIN" />
      <category android:name="android.intent.category.LAUNCHER" />
  </intent-filter>
</activity>
<activity android:name=".activity.ActivityB" android:label="ActivityB" android:launchMode="singleTask">
  <intent-filter>
     <action android:name="android.intent.action.MAIN" />
    </intent-filter>
</activity>
<activity android:name=".activity.ActivityC" android:label="ActivityC" android:launchMode="standard">
  <intent-filter>
     <action android:name="android.intent.action.MAIN" />
    </intent-filter>
</activity>
复制代码

操作1:ActivityA->ActivityB->ActivityC->ActivityA->ActivityB->ActivityC

  

  可以看到,当再次进入ActivityB时,没有onCreate,而是onNewIntent(绿55)。

   这里我们也可以发现一个现象,当在调用到ActivityB的onNewIntent时,之前的ActivityA和ActivityC都调用了onDestory。也就是说,系统发现栈中存在ActivityB的实例时,ActivityA和ActivityB都弹栈了。

   列出Log日志(这里设ActivityA的LaunchMode为singleTask),ActivityB和ActivityC都在onNewIntent前后调用了onDestory。

  

  singleInstance

    和singleTask相似,除了系统不会让其他的activities运行在所有持有的task实例中,这个activity是独立的,并且task中的成员只有它,任何其他activities运行这个activity都将打开一个独立的task。

复制代码
<activity android:name=".activity.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=".activity.ActivityB" android:launchMode="singleInstance">
  <intent-filter>
     <action android:name="android.intent.action.MAIN" />
  </intent-filter>
</activity>
<activity android:name=".activity.ActivityC">
  <intent-filter>
     <action android:name="android.intent.action.MAIN" />
   </intent-filter>
</activity>
复制代码

 操作1:ActivityA->ActivityB->ActivityA

  

  可以发现,两个Activity是在不同的Task中,其次,当调用到onNewIntent时,ActivityB没有被Destory,互不干涉。

  操作2:ActivityA->ActivityB->ActivityC,按返回键

  

  图解:

  

  刚进入应用,创建TaskA,ActivityA为栈顶,从ActivityA到ActivityB,ActivityB进入TaskB(如果再次进入ActivityB,则不创建Task,调用onNewIntent),此时TaskB中的ActivityB为栈顶,从ActitivyB到ActivityC,ActivityC为栈顶。

  一直按返回键,先从TaskA中依次将Activity弹出,然后再从TaskB中将ActiviyB弹出。ActiviyC->ActivityA->ActivityB。

  这里分析一个问题,浏览器的LaunchMode为singleTask,所以如果当你点击一个连接下载文件时(由一个activity来处理下载,launchmode为standard),如果再次进入浏览器,那么下载页面就被Destory了,那么这里我们可以把下载页面LaunchMode设置为singleInstance可以解决这个问题。

  Affinity定义

  Affinity更像是表明了activity属于哪个task,默认情况下,应用所有的activities都有相同的affinity,所以都是在相同的task中。然后你可以编辑默认的affinity。Activities定义在不同的应用可以共享一个affinity,或者activities定义在相同的应用中可以被不同的affinities所关联。

  你可以编辑在<activity>元素中activity的taskAffinity属性。

   先看看两种不同的情况下affinity的表现:

//ActivityA.java
Intent intent = new Intent();    
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClass(ActivityA.this, ActivityB.class);
startActivity(intent);
复制代码
<activity android:name=".activity.ActivityA" android:taskAffinity="com.android.demo.affinity1">
  <intent-filter>
     <action android:name="android.intent.action.MAIN" />
       <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
<activity android:name=".activity.ActivityB" android:taskAffinity="com.android.demo.affinity2">
  <intent-filter>
     <action android:name="android.intent.action.MAIN" />
   </intent-filter>
</activity>
复制代码

操作1:不同的affinity值,ActivityA->ActivityB

如果已经存在相同affinity,那么新activity运行在这个task中,否则,系统创建新task。

操作2:相同的affinity值,ActivityA->ActivityB

复制代码
<activity android:name=".activity.ActivityA" android:taskAffinity="com.android.demo.affinity1">
  <intent-filter>
     <action android:name="android.intent.action.MAIN" />
       <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
<activity android:name=".activity.ActivityB" android:taskAffinity="com.android.demo.affinity2">
  <intent-filter>
     <action android:name="android.intent.action.MAIN" />
   </intent-filter>
</activity>
复制代码

可以看出ActivityA和ActivityB都运行在同一个task中。

  使用来表示是否允许activity重新附属其他Task,还是举例说明吧。

  有两个应用,Demo1和Demo2,Demo1中有2个Activity(ActivityA,ActivityC),ActivityA可以转向到ActivityC,Demo2中有一个Activity(ActivityB),也可以转向到ActivityC。

  操作1:设置ActivityC的allowTaskReparenting属性为true。

      运行Demo2,转向到ActivityC,在ActivityC中打印信息,返回到HOME界面,运行Demo1。

复制代码
//Demo1 
//ActivityA.java
Intent intent = new Intent();
intent.setClass(ActivityA.this, ActivityC.class);
startActivity(intent);

//ActivityC.java
tv.setText(ActivityC.this.toString());

//Demo2
//ActivityB.java
Intent intent = new Intent();
intent.setClassName("com.android.demo","com.android.demo.activity.ActivityC");
ActivityB.this.startActivity(intent);
复制代码
复制代码
//Demo1
<activity android:name=".activity.ActivityA">
  <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
  </intent-filter>
</activity>
<activity android:name=".activity.ActivityC" android:allowTaskReparenting="true">
  <intent-filter>
    <action android:name="android.intent.action.MAIN" />
  </intent-filter>
</activity>


//Demo2
<activity android:name=".ActivityB">
  <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
  </intent-filter>
</activity>
复制代码

运行结果:(黄色Demo1,绿色Demo2)

  

  ActivityB转向到ActivityC,此时ActivityC就关联到Demo2的Task中,TaskID都为231。在运行Demo1时,看到是ActivityC而不是ActivityA。当再次进入Demo2时就看不到ActivityC了。

  操作2:将ActivityC的taskAffinity设置为"com.android.demo.activityc"。

      运行Demo2,转向到ActivityC,在ActivityC中打印信息,返回到HOME界面,运行Demo1。

复制代码
//Demo1
<activity android:name=".activity.ActivityC"
            android:taskAffinity="com.android.demo.activityc"
            android:allowTaskReparenting="true">
  <intent-filter>
    <action android:name="android.intent.action.MAIN" />
  </intent-filter>
</activity>
复制代码

运行结果:

  

  从结果中可以看出,Demo1和Demo2都拥有ActivityC,也就是说有2个Task里存在ActivityC,分别被Demo1和Demo2所使用。

  操作3:将ActivityC和ActivityB的taskAffinity都设为"com.android.demo.activityc"。

      运行Demo2,转向到ActivityC,在ActivityC中打印信息,返回到HOME界面,运行Demo1。

复制代码
//Demo2
<activity android:name=".ActivityB" android:taskAffinity="com.android.demo.activityc">
  <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
  </intent-filter>
</activity>

//Demo1
<activity android:name=".activity.ActivityC"
            android:taskAffinity="com.android.demo.activityc"
            android:allowTaskReparenting="true">
  <intent-filter>
    <action android:name="android.intent.action.MAIN" />
  </intent-filter>
</activity>
复制代码

  运行结果:

  

  和操作1相反,再进入Demo2时看到是ActivityC,进入Demo1都是看到ActivityA。

  写到最后越来越崩溃了,如果有什么地方写的不对或不清楚请指明。

  转帖请说明原文出处:http://www.cnblogs.com/SteveMing/archive/2012/04/24/2459575.html 

<think> 我们被要求回答关于Android中`excludeFromRecents``taskAffinity`的含义、使用方法及作用。 参考提供的引用内容,我们可以得到一些相关信息: 关于`taskAffinity`: - 引用[1]提到:Activity的亲力由`taskAffinity`属性定义,而Task的亲力由根Activity的亲力决定。根Activity总是位于相同亲力的任务里。 - 引用[2]提到:当使用非Activity的Context(如ApplicationContext)启动一个standard模式的Activity时,需要指定`FLAG_ACTIVITY_NEW_TASK`标记位,这样会创建一个新的任务栈,此时Activity以singleTask模式启动。这里隐含了`taskAffinity`的作用,因为新任务栈的亲力由该Activity的`taskAffinity`决定。 - 引用[3]解释了Task的概念,并提到singleInstance模式会创建一个独立的任务栈(其中只有一个Activity),这涉及到任务栈的亲力。 关于`excludeFromRecents`: 提供的引用中没有直接提到`excludeFromRecents`,但我们知道它是Android中任务管理的一个属性。 因此,我们需要结合知识来回答: 1. `taskAffinity`: - 含义:任务亲力(Task Affinity)是一个字符串,用于标识Activity属于哪个任务栈。默认情况下,同一个应用中的所有Activity具有相同的亲力(即应用包名)。但我们可以通过设置不同的亲力,将Activity放入不同的任务栈中。 - 作用:用于指定Activity希望归属的任务栈。当Activity启动时,系统会检查它的亲力,决定是放入当前任务栈还是新建一个任务栈(或移动到具有相同亲力的已有任务栈)。 - 使用方法:在AndroidManifest.xml中为Activity设置`android:taskAffinity`属性,例如: ```xml <activity android:name=".MyActivity" android:taskAffinity="com.example.customaffinity"/> ``` 注意:亲力字符串必须包含一个点(.)分隔符,通常使用包名形式。 2. `excludeFromRecents`: - 含义:该属性用于指定是否从最近任务列表(即用户通过系统“最近任务”键看到的列表)中排除当前任务。 - 作用:当设置为true时,整个任务(包含该Activity的任务)将不会出现在最近任务列表中。用户将无法通过最近任务列表切换回该任务。 - 使用方法:在Activity的声明中设置`android:excludeFromRecents`为true,或者通过Intent的`FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS`标记位。注意,这个属性通常作用于整个任务,因此只需要在任务的根Activity上设置即可(因为任务是否出现在最近任务列表中由根Activity决定)。 - 在AndroidManifest.xml中: ```xml <activity android:name=".MyRootActivity" android:excludeFromRecents="true"/> ``` - 在代码中: ```java Intent intent = new Intent(this, MyActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); startActivity(intent); ``` 另外,注意: - 当使用`excludeFromRecents`时,用户将无法通过最近任务列表回到该任务,但任务仍然存在,直到用户完成其中的所有Activity(或者系统回收)。 - 关于`taskAffinity`,它经常与启动模式(如`singleTask`)以及`FLAG_ACTIVITY_NEW_TASK`一起使用。例如,当启动一个Activity并设置`FLAG_ACTIVITY_NEW_TASK`时,系统会查找与Activity的`taskAffinity`相同的任务栈。如果找到,则将该任务栈调到前台并将Activity压入;如果没找到,则新建任务栈。 引用说明: - 引用[1][2]帮助我们理解`taskAffinity`的作用,以及它与启动模式、标记位的关系。 - 引用[3]帮助我们理解任务栈singleInstance模式,这间接与`taskAffinity`有关(因为singleInstance会创建独立的任务栈,其亲力由该Activity的`taskAffinity`决定)。 - 引用[4]介绍了Application,虽然与问题没有直接关系,但说明了应用级别的模块。 因此,我们根据以上信息回答用户问题。 注意:用户问题中并没有直接提到`excludeFromRecents`的引用,所以我们根据一般知识进行补充。 最后,按照要求,在回答中引用的段落末尾添加引用标识。 </think>### `excludeFromRecents` `taskAffinity` 详解 #### 1. `taskAffinity`(任务亲性) **含义**: 任务亲性(Task Affinity)定义了 Activity 所属的任务栈(Task)的逻辑分组。每个任务栈有唯一的亲性标识,默认情况下,同一应用的所有 Activity 共享应用包名的亲性。 **作用**: - 控制 Activity 被分配到的任务栈 - 与启动模式(如 `singleTask`)配合实现跨任务栈调度 - 允许不同应用的 Activity 共享同一任务栈(如地图应用共享)[^3] **使用方法**(在 `AndroidManifest.xml` 中): ```xml <activity android:name=".MyActivity" android:taskAffinity="com.example.custom.affinity" android:launchMode="singleTask"/> ``` > ✅ 规则: > - 根 Activity 决定整个任务栈的亲性[^1] > - `standard`/`singleTop` 模式可配合 `allowTaskReparenting` 调整任务栈[^1] > - 使用 `FLAG_ACTIVITY_NEW_TASK` 时,系统按亲性创建新任务栈[^2] --- #### 2. `excludeFromRecents`(最近任务排除) **含义**: 标记整个任务栈是否出现在系统最近任务列表(Overview Screen),即用户按“最近任务键”时是否可见。 **作用**: - 隐藏敏感任务(如支付流程) - 防止用户通过最近任务列表返回到特定流程 - 适用于临时性任务(如授权登录) **使用方法**: ① XML 声明(作用于整个任务栈): ```xml <activity android:name=".SensitiveActivity" android:excludeFromRecents="true" /> <!-- 只需根Activity设置 --> ``` ② Java 代码动态设置: ```java Intent intent = new Intent(this, SensitiveActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); startActivity(intent); ``` > ⚠️ 注意事项: > - 需设置在任务栈的**根 Activity** 上才生效 > - 任务仍在后台运行,只是从最近任务列表隐藏 > - 用户需通过 Back 键退出任务,否则驻留内存 --- ### 典型应用场景对比 | **特性** | `taskAffinity` | `excludeFromRecents` | |------------------|-----------------------------------|-----------------------------------| | **核心目的** | 管理任务栈分组 | 隐藏任务栈 | | **作用范围** | 单个 Activity | 整个任务栈 | | **常用组合** | `singleTask` + `NEW_TASK` 标记[^2] | 根 Activity 设置 | | **典型用例** | 跨应用共享 Activity(如地图)[^3] | 支付流程、临时授权 | --- ### 技术原理图示 ```mermaid graph LR A[任务栈1] -->|默认亲性<br>com.app1| B[ActivityA] C[任务栈2] -->|自定义亲性<br>com.map| D[MapActivity] E[任务栈3] -->|excludeFromRecents| F[PaymentActivity] G[最近任务列表] --> A & C G -.->|隐藏| E ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值