十九、startActivity过程分析

概述

本文基于android-28版本分析 startActivity 的启动流程。

整体流程

当我们点击手机桌面上的一个应用图标时,最终是通过 startActivity 方法去打开一个 Activity页面。

Android中每一个App都是一个进程。

startActivity的操作,必须先判定当前应用进程是否存在,如果不存在则需要先创建。

在这里我们必须清楚的是,Activity在整个安卓系统中不是孤立存在的,我们创建一个 应用A,在其中自定义一个Activity,这个Activity不一定就出现在 应用A的进程中,它可以通过 taskAffinity 出现在其他应用的Activity栈内。 由此可见,Activity并没有死死绑在 某一个App进程中。

startActivity的流程如图所示:

startActiviy的核心动作,其实只是把一个跳转意图Intent转发给了 AndroidSystem(更具体来说是ActivityManagerService,简称AMS),AMS 来解析这个Intent,然后根据情况看是否需要创建 应用进程,确保应用进程存在的情况下,再去把这个Activity放入该进程的Activity栈中。

步骤分析

整个流程分为三大步骤:

  • Activity A ------> ActivityManagerService
  • ActivityManagerService ------> ApplicationThread
  • ApplicationThread ------> ActivityB

第一步 Activity A ------> ActivityManagerService

示意图

源码分析

  1. 执行 startActivity 后,转到 startActivityForResult,这个-1表示不需要关注跳转的结果。

  2. 跳转的实际动作,委托给了 Instrumention 。 注意:Instrumention这个类,主要用于当前进程与系统进程的交互。你看它在 startActivity时传入了一个 applicationThread 对象,它就是 我们自己app所在的进程,我们可以通过 这个application对象实现 我们自己的进程与系统进程之间的通信,通信方式是 Binder AIDL 。

  3. Instrumention 中的实际跳转动作

具体实现为,调用 AMS实例 (ActivityManager.getService())来执行系统级别的 startActivity跳转动作。

至此,startActivity的第一阶段,从当前app进程,工作重心,转移到了 系统进程中的AMS服务。

第二步 ActivityManagerService ------> ApplicationThread

首先剧透一下:每一个app,都会有 ApplicationThread实例保存在 系统进程中。当我们从ActivityA 跳转到 ActivityB,首先会通知到ActivityB所在进程的 ApplicationThread实例,最终由它去执行跳转动作。

这一步骤比较复杂,但是整体来说分为两个过程:

  1. 处理intent中的 launchModeintent中的flag标志位, 并通过结果生成一个 ActivityB的实例对象(ActivityRecord
  2. 判断是否需要为目标ActivityB创建新的 进程(processRecord) 和 新的Activity任务栈 (TaskRecord

源码分析

  • 以下是 ActivityManagerService.java中 跟踪startActivity方法到达的最终代码位置:

它最终通过 obtainStarter方法获取了 ActivityStarter类型的对象,然后执行了 execute() 方法来执行跳转。 ActivityStarter这个类专门用于Activity的启动工作。主要作用 为 :解析Intent创建ActivityRecord,如有必要还要创建TaskRecord对象。

  • 以下是 ActivityStarterstartActivityMayWait方法,

它其中调用了 mSuperVisorresolveActivity 方法。mSuperVisor 的类型是 ActivityStackSuperVisor, 它负责Activity所处栈的管理。上图中还调用了它的 resloveIntent()实际上是根据传入的intent,调用了系统的PackageManagerService来获取最佳Activity。 有时候我们通过隐式Intent来启动Activity时,系统中可能存在多个符合条件的Activity,此时会弹出一个选择框,让用户选择启动哪一个Activity

  • 在 startActivityMayWait() 中最后调用了 一个重载的startActivity(),它其中实际上走到了下图的代码: startActivityUnchecked(),返回值int表示启动Activity的结果。 上图中3个关键代码分别代表的3个关键步骤:

1. computeLaunchingTaskFlags

计算Activity启动的Flag值

  • 不同的Flag将会决定启动Activity放置在哪一个Task集合中
  • 图中1处的 mInTaskTaskRecord 类型,此处为 null,表示 Activity要加入的栈不存在,因此需要判断是否需要新建Task。
  • 图中2处的 mSourceRecordActivityRecord 类型,它是用来描述初始Activity,比如 由ActivityA启动了ActivityB,那么ActivityA就是 初始Activity。当我们用 Context或者Application启动Activity时,mSourceRecord就是null
  • 图中3处表示 初始Activity如果是在 SINGLE_INTANCE(Activity容量为1),那么必须添加NEW_TASK标识。
  • 图中4处,如果我们启动Activity时设置了launchMode设置了 SINGLE_INTANCE 或者 SINGLE_TASK, 则也要创建一个新栈。

2. mTargetStack.startActivityLocked()

处理Task和Activity的入栈操作 图中的 insertTaskAtTop(),尝试将 task和Activity入栈。如果Activity是以 NEW_TASK的模式启动,或者 task堆栈中不存在该taskId,则task会重新入栈,并且放在栈的顶部。 需要注意的是,task先入栈,然后才是Activity入栈。 上图是 Task和Activity的关系,Task属于Activity的父集。ActivityStack是系统级别的容器,存放所有Activity,但是存放的过程有2个层次的,最大的是ActivityStack,接下来是 Task,而最小的是 Activity。

3. mSupervisor.resumeFocusedStackTopActivityLocked()

启动栈中顶部的Activity

mSuperVisor 的类型是 ActivityStackSuperVisor, 这个方法实际上走到了ActvityStack的resumeFocusedStackTopActivityLocked() 最终走到了 ActivityStackSuperVisorstartSpecificActivityLocked() 图中1处,根据进程名称和 applicaiton的uid来判定目标进程是否已经创建。

图中2处,调用AMS创建Activity所在进程。

不管目标进程是否存在,最终都会调用, realStartActivityLocked() 来启动Activity

这个 realStartActivityLocked() 的实现,从android-28开始交给了事务(Transaction)来处理。 图中1处,创建了Activity启动事务,传入了App.thread对象。它是ApplicationThread类型。这个ApplicationThread是用于处理进程间(Binder)通信的,它是 ActivityThread的内部类。

图中2处,执行activity的启动事务。

Activity的启动事务是由 ClientLifecycleManager 来完成。 上图中的 transaction对象的创建,则是通过传入的app.thread来创建的。 所以 事务最终是调用了 ApplicationThread的 scheduleTranscation 来执行。

至此,startActivity的流程就从 AMS转移到了 另一个进程的ApplicationThread中。

第三步 ApplicationThread ------> ActivityB

刚才我们提到了 AMSstartActivity 的操作当做一个事务,传递给了ActivityB的ApplicationThreadscheduleTranscation 去完成. 后续的Activity生命周期过程都是 ApplicationThread这个内部类去完成的。

看到这个Stub,就应该能想起来,这是AIDL的一个抽象类。上图中的 scheduleTransaction 中,虽然是调用了 ActivityThread.this.schedueTransaction() 看着像是它在执行外部 ActivityThread的某个过程. 但是实际上,过程在 ActivityThread 父类 ClientTransactionHandler中,如下图:

通过handler发送了 EXECUTE_TRANSACTION 消息。 而这里的 大写 H,是 ActivityThread的内部类:

这里调用了 mTransactionExecutor的 execute方法,参数则是外部传入的 事务对象。

TransactionExecutor源代码如下:

execute()最终走到了 executeCallbacks()。上图中,对多个事务进行了遍历,逐个执行它们的 execute方法。这些callback是如何添加的呢? 其实是我们创建事务的时候,我们通过addCallback方法传入了 callback对象。如下图:

这个callback,其实是 LaunchActivityItem 类型的对象。 那么我们跟踪的重点就是 LaunchActivityItem 的 execute(),

终于到了和Activity生命周期相关的方法。

图中的 client是 ClientTransactionHandler类型,实际实现类是ActivityThread

下图是 本次startActivity的重点内容 ActivityThread 源代码如下:

图中1处,初始化Activity的WindowManager。每一个Activity都会对应一个窗口。 图中2处,创建并显示Activity。 图中3处,通过反射创建 目标Activity对象。 图中4处,建立Activity与context之间的联系。创建PhoneWindow对象并与Activity进行关联。 图中5处,通过 Instrumention调用activity的onCreate方法。

至此,目标Activity已经成功创建并执行了生命周期方法。

总结

本文详细跟踪了 Activity 跳转的详细源码流程。

整个流程涉及到3个进程间通信:

  • 进程A通过Binder调用了AMS的startActivity
  • AMS通过一系列计算 构造目标intent,然后 ActivityStack与 ActivityStackSupervisor中处理了Task和Activity的入栈操作
  • AMS通过 Binder机制 调用了 目标进程的 ApplicationThread方法来执行Activity的生命周期方法。ApplicationThrad是ActivityThread一个内部类,所以这一过程最终都执行到了 ActivityThread中。
相关推荐
测试19988 小时前
2024软件测试面试热点问题
自动化测试·软件测试·python·测试工具·面试·职场和发展·压力测试
马剑威(威哥爱编程)8 小时前
MongoDB面试专题33道解析
数据库·mongodb·面试
独行soc10 小时前
#渗透测试#SRC漏洞挖掘#深入挖掘XSS漏洞02之测试流程
web安全·面试·渗透测试·xss·漏洞挖掘·1024程序员节
理想不理想v11 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
sszmvb123412 小时前
测试开发 | 电商业务性能测试: Jmeter 参数化功能实现注册登录的数据驱动
jmeter·面试·职场和发展
测试杂货铺12 小时前
外包干了2年,快要废了。。
自动化测试·软件测试·python·功能测试·测试工具·面试·职场和发展
王佑辉12 小时前
【redis】redis缓存和数据库保证一致性的方案
redis·面试
真忒修斯之船12 小时前
大模型分布式训练并行技术(三)流水线并行
面试·llm·aigc
ZL不懂前端12 小时前
Content Security Policy (CSP)
前端·javascript·面试
测试界萧萧13 小时前
外包干了4年,技术退步太明显了。。。。。
自动化测试·软件测试·功能测试·程序人生·面试·职场和发展