Android | launchMode启动模式详解

任务栈

回顾一下Activity的任务栈:如上图所示显示了一个时间轴,3个Activity的启动顺序是1->2->3,按返回键之后3被销毁, 并且会从任务栈中弹出,2恢复。

而当调用startActivity()来启动一个页面时,可以通过一系列的设置来管理任务栈,既可以在manifest中设置,也可以通过Intent.FLAG动态设置,常用设置如下:

通过manifest文件的 < activity > 中配置

  • launchMode
  • taskAffinity
  • allowTaskReparenting
  • clearTaskOnLaunch
  • alwaysRetainTaskState
  • finishOnTaskLaunch

通过Intent.FLAG标志位设置

  • FLAG_ACTIVITY_NEW_TASK
  • FLAG_ACTIVITY_CLEAR_TOP
  • FLAG_ACTIVITY_SINGLE_TOP
  • FLAG_ACTIVITY_REORDER_TO_FRONT
  • FLAG_ACTIVITY_CLEAR_TASK

注:如果在manifest中设置的某些行为与Intent.FLAG标志的行为不一致,那么以哪个为准呢

如activityA 启动了activityB,其中activityB 是在manifest清单中定义它与当前任务的关联方式,而activityA启动activityB时使用intent标志来与当前任务相关联。那么此时intent所定义标志位的优先级是高于manifest中的。

launchMode(静态)

在manifest文件中可以给 Activity 的 launchMode 属性指定五种启动模式(你没看错,现在有五种启动模式了哟,惊不惊喜,意不意外!),重温下对应的5种模式:

  • standard:系统默认的启动模式,该模式会在启动它的任务中创建 activity 的新实例,并向其传送 intent。
  • singleTop :如果系统启动Activity时,该Activity实例已经存在于task栈的栈顶,则会通过调用该实例的 onNewIntent() 方法向其传送 intent,而不是创建新的 activity 实例,即栈顶复用;如果要启动的Activity实例存在但不在栈顶,则会重新启动一个新的Activity(此时相当于standard模式)。singleTop启动模式常用于通知栏结果页中,可以增加复用性。
  • singleTask:当启动Acticity时,如果已存在该 Activity 实例,则系统会通过调用现有实例的 onNewIntent() 方法,而非创建新实例,向其传送 intent,同时它上面的所有其他 activity 都会被销毁。singleTask可以用于应用的首页,保证主页面只启动一次,其余情况走onNewIntent()。
  • singleInstance:行为与 "singleTask" 相同,只是系统不会将任何其他 activity 启动到包含该实例的任务中。该 activity 始终是其任务中的唯一 activity。由此 activity 启动的任何 activity 都会在单独的任务中打开。
  • singleInstancePerTask该 activity 只能作为task栈的根activity运行,因此在task栈中该activity 只有一个实例。singleInstancePerTask跟singleTask类似,不过singleInstancePerTask不需要设置taskAffinity也能创建一个新的task栈。而如果结合FLAG_ACTIVITY_MULTIPLE_TASK或FLAG_ACTIVITY_NEW_DOCUMENT的话,可以在不同task栈的多个实例中启动。 注:这个属性是API 12新增,也就是compileSdkVersion设置到31及以上的时候才可以设置,在API 12之前不起作用。

taskAffinity

taskAffinity 用于修改 activity 之间的"亲和性",表示 activity属于哪个任务栈。默认情况下,同一应用中的所有 activity 彼此具有亲和性,即他们在同一任务栈中。taskAffinity 属性接受一个字符串值,该值必须与 <manifest> 元素中声明的默认软件包名称不同,因为系统会使用该名称来识别应用的默认任务亲和性。

使用示例:有三个activity,A打开B,B打开C,即:A->B->C。其中AB为默认设置,C在<activity>中的 taskAffinity 设置如下:

ini 复制代码
<activity
    android:name="com.launchmode.example.mode.launchMode.ActivityC"
    android:taskAffinity="unique.mode"/>

给C设置了一个不同于包名的字符串,执行了A->B->C操作之后,通过 adb shell dumpsys activity activities 命令查看Activity组件信息,如果觉得觉得生成的结果太多,可以通过grep过滤关键信息,如:adb shell dumpsys activity activities | grep -e "Hist" -e "Task" -e "taskAffinity"

可以看到虽然C的taskAffinity设置的不同值,但是A、B、C还是在同一任务栈中,所以只设置taskAffinity并不能改变亲和性。继续修改如下:

ini 复制代码
<activity
     android:name="com.launchmode.example.mode.launchMode.ActivityC"
     android:launchMode="singleTask"
     android:taskAffinity="unique.mode" />

此时在看堆栈信息:

嗯,C已经在一个单独的任务栈里了,也就是taskAffinity + singleTask的配置可以改变亲和性。除此之外,taskAffinity还可以和singleInstance、singleInstancePerTask组合改变亲和性;taskAffinity还可以配合在启动C的Intent中设置intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK改变亲和性。

注:Mac控制台中,可以通过command + k进行清屏操作,另外其他 adb shell 常用命令如下:

复制代码
adb shell dumpsys activity :查看AMS所有信息
adb shell dumpsys activity activities:查看Activity组件信息
adb shell dumpsys activity services:查看Service组件信息
adb shell dumpsys activity providers:产看ContentProvider组件信息
adb shell dumpsys activity broadcasts:查看BraodcastReceiver信息
adb shell dumpsys activity intents:查看Intent信息
adb shell dumpsys activity processes:查看进程信息

taskAffinity + allowTaskReparenting

allowTaskReparenting为true时,activity 可以从其启动的任务栈移至与其有亲和性的任务栈中,前提是该任务栈出现在前台时。

示例:两个app应用,第一个应用中有A、B、C三个activity,打开顺序为A->B->C,其中C的设置如下:

ini 复制代码
<activity
      android:name="com.launchmode.example.mode.launchMode.ActivityC"
      android:allowTaskReparenting="true"
      android:taskAffinity="unique.mode" />

第二个应用中只有一个页面D,taskAffinity设置的值与C相同。运行第一个应用并执行A->B->C操作,然后按Home键,再打开第二个应用进入D页面,此时C因为设置了allowTaskReparenting=truetaskAffinity与D相同,所以会转移到包含D的任务栈里。这种在项目中暂时没用到过,有实践过的小伙伴可以讨论一下。

Intent常用FLAG标志位(动态)

启动 activity 时,可以通过在传递给 startActivity() 的 intent 中添加标志来修改 activity 与其任务的默认关联。常用标记位如下:

1、FLAG_ACTIVITY_NEW_TASK

系统会在新任务栈中启动 activity。单独设置没有作用,配合taskAffinity一起使用时(设置的taskAffinity需要保持不同)。如下所示在D跳转到B时,B在manifest中设置不同的taskAffinity,D启动B时设置了FLAG_ACTIVITY_NEW_TASK,结果也如预期一样B会在一个新的任务栈中启动:

除此之外,FLAG_ACTIVITY_NEW_TASK通常还可以和FLAG_ACTIVITY_CLEAR_TASK或FLAG_ACTIVITY_CLEAR_TOP一起使用。

a、FLAG_ACTIVITY_NEW_TASK 和 FLAG_ACTIVITY_CLEAR_TASK一起使用时: 如果有对应taskAffinity的task栈,会直接把栈清空,并创建新activity作为根成员放入。

b、FLAG_ACTIVITY_NEW_TASK 和 FLAG_ACTIVITY_CLEAR_TOP 一起使用时: 如果activity在对应taskAffinity的task栈里存在实例,会把它及以上的activity清空,并重新创建这个新activity。使用场景:在清除数据之后重新登录LoginActivity:

ini 复制代码
val intent = Intent(this, LoginActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(intent)

2、FLAG_ACTIVITY_SINGLE_TOP

Intent.FLAG_ACTIVITY_SINGLE_TOP 与在launchMode中设置singleTop 的功能相同。这个很简单,就不再贴代码验证了。

3、FLAG_ACTIVITY_CLEAR_TOP

Intent.FLAG_ACTIVITY_CLEAR_TOP 销毁目标Activity和它之上的所有Activity,重新创建目标Activity,示例: A、B、C、D四个Activity,启动模式均为默认,依次启动,最后在D中再启动B,即启动顺序为:A->B->C->D->B。

首先看A->B->C->D之后的任务栈信息: 在D启动B时,分别尝试下面几种情况:

1、设置Intent的flags为:intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP,代码如下:

kotlin 复制代码
val intent = Intent(this, ActivityB::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP //这里
startActivity(intent)

执行之后的任务栈信息如下:

此外B还执行了自身的 onCreate() 方法,由此可以说明Intent.FLAG_ACTIVITY_CLEAR_TOP的效果:B、C、D被清除出栈,且B重新启动,重走生命周期。

2、重新设置flags为:intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP,点击D->B之后的堆栈信息如下:

B还执行了onNewIntent()、onResume()方法,所以Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP的效果: C,D出栈,B调用onNewIntent()方法而不是重新启动。

结论:通过实验可以得知Intent.FLAG_ACTIVITY_CLEAR_TOP + Intent.FLAG_ACTIVITY_SINGLE_TOP的组合相当于launchMode中的singleTask功能了。

4、FLAG_ACTIVITY_REORDER_TO_FRONT

Context.startActivity()时,如果目标activity之前启动过,那么设置这个标志会将启动的activity重新调到前面来,并调用它的onNewIntent()方法。

示例:页面启动顺序A->B->C->D,此时D调用startActivity(),并设置intent 的flags为FLAG_ACTIVITY_REORDER_TO_FRONT,跳转之后,任务栈变成了:A->C->D->B。

注意,如果Intent中还指定了FLAG_ACTIVITY_CLEAR_TOP,那么FLAG_ACTIVITY_REORDER_TO_FRONT标志将被忽略。

5、FLAG_ACTIVITY_CLEAR_TASK

这个标志会将对应任务栈中的activity清空,然后再启动目标activity。也就是说,该activiy将成为一个空任务的根activity,任何旧activity都将结束。通常这个标志位与FLAG_ACTIVITY_NEW_TASK结合使用。

相关推荐
雨白1 小时前
Jetpack系列(二):Lifecycle与LiveData结合,打造响应式UI
android·android jetpack
kk爱闹3 小时前
【挑战14天学完python和pytorch】- day01
android·pytorch·python
每次的天空5 小时前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
恋猫de小郭5 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
断剑重铸之日6 小时前
Android自定义相机开发(类似OCR扫描相机)
android
随心最为安6 小时前
Android Library Maven 发布完整流程指南
android
岁月玲珑6 小时前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio
还鮟10 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡12 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi0012 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体