activity启动模式你所不知道的异常情况

2024-09-04 01:48

本文主要是介绍activity启动模式你所不知道的异常情况,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

虽然了解activity的四种启动模式,但是在一些复杂场景下,各种启动模式会出现的现象,以及现象的原因并不清楚,再加上个taskAffinity launchMode clearTaskOnLaunch 这些参数会使得更加懵逼。所以根据在实际应用中遇到的问题总结一下。

主要内容

要讲启动模式需要从Task ,taskAffinity 以及launchMode,还有标签四个方面入手,看这四个之前的关联以及影响。
在这里插入图片描述

Task

在这里插入图片描述

task跟activity的启动息息相关,因为activity启动后都是放在task里面进行管理的,task的数据结构是stack的,先进后出,新创建的activity放在task的顶部,如下图打开ActivityA->activityB->activityC:
在这里插入图片描述
task的特点:

  1. activity的集合
  2. 以栈的形式对activity进行管理(back stack)
  3. task里面至少包含一个activity
  4. 新创建的activity放在栈顶。
  5. 每一个task都有称为Affinity的name。

TaskAffinity

taskAffinity是activity可以在manifest文件里面设置的属性.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.coroutinescopedemo"><application android:allowBackup="true"><activityandroid:name=".MainActivity"android:taskAffinity="hanking.edu"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application></manifest>

用来确定启动的activity属于哪个task,或者确定task的名称。具体的功能如下:

  1. 用来决定持有activity的task是哪个。

  2. 默认情况下一个app里面的activity都有相同的affinity值(package name)

  3. task的affinity值由触发创建task的activity的affinity值决定。(也被称为root activity)

taskAffinity用来确定activity所在栈的名字,是不是任何时候都会生效?看下默认情况下的两个activity设置不同的affinity会发生什么情况。

1、给activity设置task affinity
如下创建了activityA和activityB,其中给activityB设置了task Affinity值为com.something.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.myApp"><applicationandroid:allowBackup="true"android:theme="@style/AppTheme"><activity android:name=".ActivityA"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><activityandroid:name=".ActivityB"android:taskAffinity="com.something" /></application></manifest>

流程:打开activityA 从activityA跳转到ActivityB,然后打印task的情况。
通过adb shell dumpsys activity activities | sed -En -e '/Running activities/,/Run #0/p’打印情况如下

adb shell dumpsys activity activities | sed -En -e '/Running activities/,/Run #0/p'Running activities (most recent first):TaskRecord{5938ae7 #1669 A=com.example.coroutinescopedemo U=0 StackId=287 sz=2}Run #1: ActivityRecord{5d93c09 u0 com.myApp/.ActivityB t1669}Run #0: ActivityRecord{5ce5f59 u0 com.myApp/.ActivityA t1669}

在这里插入图片描述
有上面流程可以知道,activity默认启动情况下加task affinity属性并不会新建task,也不会改变task名称,task的名称和taskRoot的activity中设置的task affinity值一致,如果没设置默认就是包名,这里taskRoot Activity是activityA。

2、添加FLAG_ACTIVITY_NEW_TASK
由上面可见,默认模式下就算给activity添加了taskAffinity属性也不会多创建一个task,原因是这个taskAffinity应该和FLAG_ACTIVITY_NEW_TASK一起使用才会创建新task。

 val i = Intent(this, ActivityB::class.java)i.flags = Intent.FLAG_ACTIVITY_NEW_TASKstartActivity(i)

在ActivityA中启动ActivityB的时候加上Intent.FLAG_ACTIVITY_NEW_TASK的flag,再尝试从ActivityA打开ActivityB。

  Running activities (most recent first):TaskRecord{59b097b #1675 A=com.something U=0 StackId=293 sz=1}Run #0: ActivityRecord{5d93041 u0 com.example.myApp/.ActivityB t1675}Running activities (most recent first):TaskRecord{59b09a0 #1674 A=com.example.myApp U=0 StackId=292 sz=1}Run #0: ActivityRecord{5d2de41 u0 com.example.myApp/.ActivityA t1674}

由上面的信息可以看到有两个task,ActivityB所在的task 名称是com.something, stackId=293, ActivityA所在的task名称是com.example.myApp stackId=292
在这里插入图片描述
思考:加上Intent.FLAG_ACTIVITY_NEW_TASK的tag后由于启动了一个新的task,这时候退到任务管理器可以看到activityA和activityB所在的task,如果这个时候点开activityB再点击返回还会返回到activityA吗?
答案是不会?因为从activityA打开activityB后再切到后台,这个时候这两个activity的task都属于background状态,再打开activityB的task,activityB的task属于foreground task,返回会直接返回到桌面。

activity启动模式

activity的启动模式一般分为以下四种,四种模式的特点如下:
在这里插入图片描述

Standard:

activity默认的启动模式,在standard模式下每次打开一个activity的时候都会生成一个新的实例。
如下已经有了A,B,C,D在stack中,再启动B,B是standard模式。
A →B→ C→D
启动B,
A → B → C→D→ B
可以看到会再次生成B的实例,并放到栈顶。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.myApp"><applicationandroid:allowBackup="true"android:theme="@style/AppTheme"><activity android:name=".ActivityA"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><activityandroid:name=".ActivityB"android:taskAffinity="com.something" /></application></manifest>
 val i = Intent(this, ActivityB::class.java)startActivity(i)

在标准模式下启动ActivityA->ActivityB->activityB如下图:
在这里插入图片描述

standard模式看起来非常简单,每次生成activity实例并放在栈顶,但是当standard模式和flag一起使用的时候又会产生很多不一样的效果。
1、standard+Intent.FLAG_ACTIVITY_NEW_TASK
按照下面的方式启动activity

启动 Activity A 
ActivityA 启动 ActivityB (no flags)
ActivityB 启动 Activity A
ActivityA 启动 Activity B (with flag NEW_TASK)
ActivityB 启动 ActivityA

在这里插入图片描述
由上图可知当activityA启动activityB,并且此时intent加上NEW_TASK标签时,会生成一个新的task com.something,并且activityB作为taskRootActivity,此时activityB再启动activityA,activityA也会在com.something的栈上生成实例。

SingleTop

如果需要打开的activity的实例已经处于当前栈顶,那么会复用当前栈顶的activity,不会重新创建activity,但是会通过调用onNewIntent().所以如果需要刷新页面数据,就要在onNewIntent().进行处理。如果栈顶的activity和需要打开的activity不相同,那么会重新创建一个activity,并进栈。
假设栈里面已经有A ,B,C几个activity,
. A →B →C
如果需要再打开activity B那么如下:
A →B →C →B
如果这个时候再调用打开activity B会直接复用栈顶的B,并且调用B的onNewIntent()方法,栈如下
A →B →C →B

Step 1: Launch A -> A
Step 2: Launch B -> A-B 
Step 3: Launch C -> A-B-C 
Step 4: Launch B -> A-B-C-B
Step 5: Launch B -> A-B-C-B

singleTop总结一句:就是复用栈顶activity。

SingleTask

如果栈中存在这个Activity的实例就会复用这个Activity,不管它是否位于栈顶,复用时,会将它上面的Activity全部出栈,并且会回调该实例的onNewIntent方法。其实这个过程还存在一个任务栈的匹配,因为这个模式启动时,会在自己需要的任务栈中寻找实例,这个任务栈就是通过taskAffinity属性指定。如果这个任务栈不存在,则会创建这个任务栈。

Step 1: Launch A -> A
Step 2: Launch B -> A-B 
Step 3: Launch C -> A-B-C 
Step 4: Launch B -> A-B
Step 5: Launch B -> A-B*

如上当栈中有A-B-C此时再启动B,会遍历栈,然后找到B,将B上面的C出栈。

singleInstance

singleInstance模式下的Activity会单独占用一个Task栈,具有全局唯一性,即整个系统中就这么一个实例,由于栈内复用的特性,后续的请求均不会创建新的Activity实例,除非这个特殊的任务栈被销毁了。以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activiyt时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。
假设有三个activity:A,B,C,B是singleInstance的,

Step 1: Launch A -> Task 1: A
Step 2: Launch B -> Task 1: ATask 2: B    // Visible to the user
Step 3: Launch C -> Task 1: A-C  // Visible to the userTask 2: B
Step 4: Launch B -> Task 1: A-CTask 2: B*   // Visible to the user

singleInstance看起来很简单,但是和task一起的话又会出现很奇怪的现象,如下面的例子

<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.myApp"><activity android:name=".ActivityA"></activity><activityandroid:name=".ActivityB"android:launchMode="singleInstance"/></manifest>

启动activityA,activityA是standard模式的,activityA启动activityB,B是singleInstance模式。
在这里插入图片描述

然后打印task相关信息如下:

  Running activities (most recent first):TaskRecord{5a92078 #1709 A=com.myApp U=0 StackId=328 sz=1}Run #0: ActivityRecord{594e679 u0 com.myApp/.ActivityB t1709}Running activities (most recent first):TaskRecord{59a5a09 #1708 A=com.myApp U=0 StackId=327 sz=1}Run #0: ActivityRecord{5a66809 u0 com.myApp/.ActivityA t1708}

分析上面的信息,可以看到activityA所在task 名字是com.myApp id t1708, activityB所在的task名字也是com.myApp id t1709,虽然task的名称相同但是id不同,可以确定是两个不同的task。

但是非常神奇的事情是:当切到应用管理器时:只看到一个task为com.myApp,而且再切回activityB时,点击返回就直接返回到桌面。
在这里插入图片描述

FLAG_ACTIVITY_NEW_TASK

Start the activity in a new task. If a task is already running for the activity you are now starting, that task is brought to the foreground with its last state restored and the activity receives the new intent in onNewIntent().

复杂场景分析

上面都是简单的场景,如果选择更加复杂的场景,又会出现意想不到的现象。
在这里插入图片描述

1、task切换后打不开activity

  1. 启动 ActivityA
  2. Activity A 启动 Activity B
  3. Activity B 启动 Activity C with FLAG_NEW_TASK
  4. Activity C 启动 Activity D
  5. 用户切换到 com.myApp
  6. Activity B 启动 Activity C with FLAG_NEW_TASK

在这里插入图片描述
上面有个比较奇怪的现象activityB启动activityC,activityC 启动activityD,都是new_task方式,C,D,都是在com.something的task里面,这里正常,但是activityB,再次调用activityC的时候却没有启动activityC。
2、task切换后打开多个activity
在这里插入图片描述

1. 启动 Activity A2. Activity A 启动 Activity B3. Activity B 启动 Activity C with FLAG_NEW_TASK4. Activity C 启动 Activity D5. User switches to com.myApp6. Activity B 启动 Activity D with FLAG_NEW_TASK

上面流程启动activity的,task是什么情况?
在这里插入图片描述
看上图,最后当activityB其次启动activityD的时候又创建了一个activityD,

当activityB启动activityD的时候为什么会新创建一个activityD的实例?
这里重新创建activityD的原因是:通过activityC创建activityD的intent和通过activityB创建activityD的intent的不一样导致的。也就是说只有当intent的一样时才不会创建多个实例。

startActivityForResult 异常
在这里插入图片描述
activityA是singleInstance,activityB是standard模式,当activityA调用startActivityForResult打开activityB时,activityA收到的回调函数onActivityResult能接受activityB的返回结果吗?
正常情况下activityA通过startActivityForResult打开activityB时的流程如下:

  1. activityA 重写onActivityResult接受activityB中的返回值。
  2. activityB通过setResult确定返回值。
    3.当activityB返回时activityA的onActivityResult才会被回调。

但是当activityA定义为singleInstance时通过startActivityForResult打开activityB时如下:
在这里插入图片描述
因为activityA是singleInstance的所以独享一个task,当activityA打开activityB时,也创建一个新的task,但是onActivityResult是立马就回调,不是activityB finish的时候回调的。这是什么原因?
注意:当一个activity 调用startActivityForResult打开另一个activity当时另一个activity在另一个创建的task里面的时候,onActivityResult就会立刻回调。

补充

1、service 启动activity为什么要加FLAG_ACTIVITY_NEW_TASK标签?

1、在Android 10以下启动activity会一下方式:

 class LocationService extends Service {@Overridepublic void onStart(Intent intent, int startId) {super.onStart(intent, startId);Intent dIntent = new Intent(this, MyActivity.class);dIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(dIntent);}
}

2、Android 10以上
Start an activity from service (foreground or background) is no longer allowed.

参考

1、https://developer.android.com/guide/components/activities/tasks-and-back-stack
2、https://stackoverflow.com/questions/9772927/flag-activity-new-task-clarification-needed
3、https://community.commonsware.com/t/intent-flag-activity-new-task-is-doing-what-we-think-it-should-do/180
4、https://stackoverflow.com/questions/17872989/android-task-affinity-explanation
5、https://developer.android.com/guide/components/activities/background-starts

这篇关于activity启动模式你所不知道的异常情况的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1134656

相关文章

SpringBoot项目启动后自动加载系统配置的多种实现方式

《SpringBoot项目启动后自动加载系统配置的多种实现方式》:本文主要介绍SpringBoot项目启动后自动加载系统配置的多种实现方式,并通过代码示例讲解的非常详细,对大家的学习或工作有一定的... 目录1. 使用 CommandLineRunner实现方式:2. 使用 ApplicationRunne

bat脚本启动git bash窗口,并执行命令方式

《bat脚本启动gitbash窗口,并执行命令方式》本文介绍了如何在Windows服务器上使用cmd启动jar包时出现乱码的问题,并提供了解决方法——使用GitBash窗口启动并设置编码,通过编写s... 目录一、简介二、使用说明2.1 start.BAT脚本2.2 参数说明2.3 效果总结一、简介某些情

MySQL数据库宕机,启动不起来,教你一招搞定!

作者介绍:老苏,10余年DBA工作运维经验,擅长Oracle、MySQL、PG、Mongodb数据库运维(如安装迁移,性能优化、故障应急处理等)公众号:老苏畅谈运维欢迎关注本人公众号,更多精彩与您分享。 MySQL数据库宕机,数据页损坏问题,启动不起来,该如何排查和解决,本文将为你说明具体的排查过程。 查看MySQL error日志 查看 MySQL error日志,排查哪个表(表空间

springboot3打包成war包,用tomcat8启动

1、在pom中,将打包类型改为war <packaging>war</packaging> 2、pom中排除SpringBoot内置的Tomcat容器并添加Tomcat依赖,用于编译和测试,         *依赖时一定设置 scope 为 provided (相当于 tomcat 依赖只在本地运行和测试的时候有效,         打包的时候会排除这个依赖)<scope>provided

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

内核启动时减少log的方式

内核引导选项 内核引导选项大体上可以分为两类:一类与设备无关、另一类与设备有关。与设备有关的引导选项多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导选项。比如,如果你想知道可以向 AHA1542 SCSI 驱动程序传递哪些引导选项,那么就查看 drivers/scsi/aha1542.c 文件,一般在前面 100 行注释里就可以找到所接受的引导选项说明。大多数选项是通过"_

用命令行的方式启动.netcore webapi

用命令行的方式启动.netcore web项目 进入指定的项目文件夹,比如我发布后的代码放在下面文件夹中 在此地址栏中输入“cmd”,打开命令提示符,进入到发布代码目录 命令行启动.netcore项目的命令为:  dotnet 项目启动文件.dll --urls="http://*:对外端口" --ip="本机ip" --port=项目内部端口 例: dotnet Imagine.M

Linux服务器Java启动脚本

Linux服务器Java启动脚本 1、初版2、优化版本3、常用脚本仓库 本文章介绍了如何在Linux服务器上执行Java并启动jar包, 通常我们会使用nohup直接启动,但是还是需要手动停止然后再次启动, 那如何更优雅的在服务器上启动jar包呢,让我们一起探讨一下吧。 1、初版 第一个版本是常用的做法,直接使用nohup后台启动jar包, 并将日志输出到当前文件夹n

衡石分析平台使用手册-单机安装及启动

单机安装及启动​ 本文讲述如何在单机环境下进行 HENGSHI SENSE 安装的操作过程。 在安装前请确认网络环境,如果是隔离环境,无法连接互联网时,请先按照 离线环境安装依赖的指导进行依赖包的安装,然后按照本文的指导继续操作。如果网络环境可以连接互联网,请直接按照本文的指导进行安装。 准备工作​ 请参考安装环境文档准备安装环境。 配置用户与安装目录。 在操作前请检查您是否有 sud

SpringBoot项目是如何启动

启动步骤 概念 运行main方法,初始化SpringApplication 从spring.factories读取listener ApplicationContentInitializer运行run方法读取环境变量,配置信息创建SpringApplication上下文预初始化上下文,将启动类作为配置类进行读取调用 refresh 加载 IOC容器,加载所有的自动配置类,创建容器在这个过程