第19讲:startActivity 启动过程分析
在 13 课时中我介绍了几个与 Activity 交互相关的问题,包括不同 taskAffinity、不同 process 配置的情况。本课时我们来看一下 startActivity 过程的具体流程,课程中引用的源码版本是 android-28。
在手机桌面应用中点击某一个 icon 之后,实际上最终就是通过 startActivity 去打开某一个 Activity 页面。我们知道 Android 中的一个 App 就相当于一个进程,所以 startActivity 操作中还需要判断,目标 Activity 的进程是否已经创建,如果没有,则在显示 Activity 之前还需要将进程 Process 提前创建出来。
假设是从 ActivityA 跳转到另一个 App 中的 ActivityB,过程如下图所示:
整个 startActivity 的流程分为 3 大部分,也涉及 3 个进程之间的交互:
- ActivityA --> ActivityManagerService(简称 AMS)
- ActivityManagerService --> ApplicationThread
- ApplicationThread --> Activity
ActivityA --> ActivityManagerService 阶段
这一过程并不复杂,用一张图表示具体过程如下:
接下来看下源码中做了哪些操作。
Activity 的 startActivity
最终调用了 startActivityForResult 方法,传入的 -1 表示不需要获取 startActivity 的结果。
Activity 的 startActivityForResult
具体代码如下所示:
startActivityForResult 也很简单,调用 Instrumentation.execStartActivity 方法。剩下的交给 Instrumentation 类去处理。
解释说明:
- Instrumentation 类主要用来监控应用程序与系统交互。
- 蓝框中的 mMainThread 是 ActivityThread 类型,ActivityThread 可以理解为一个进程,在这就是 A 所在的进程。
- 通过 mMainThread 获取一个 ApplicationThread 的引用,这个引用就是用来实现进程间通信的,具体来说就是 AMS 所在系统进程通知应用程序进程进行的一系列操作,稍后会再介绍。
Instrumentation 的 execStartActivity
方法如下:
在 Instrumentation 中,会通过 ActivityManger.getService 获取 AMS 的实例,然后调用其 startActivity 方法,实际上这里就是通过 AIDL 来调用 AMS 的 startActivity 方法,至此,startActivity 的工作重心成功地从进程 A 转移到了系统进程 AMS 中。
ActivityManagerService --> ApplicationThread
接下来就看下在 AMS 中是如何一步一步执行到 B 进程的。
这里先剧透一下:刚才在看 Instrumentation 的时候,我们讲过一个 ApplicationThread 类,这个类是负责进程间通信的,这里 AMS 最终其实就是调用了 B 进程中的一个 ApplicationThread 引用,从而间接地通知 B 进程进行相应操作。
相比于 startActivity-->AMS,AMS-->ApplicationThread 流程看起来复杂好多了,实际上这里面就干了 2 件事:
- 综合处理 launchMode 和 Intent 中的 Flag 标志位,并根据处理结果生成一个目标 Activity B 的对象(ActivityRecord)。
- 判断是否需要为目标 Activity B 创建一个新的进程(ProcessRecord)、新的任务栈(TaskRecord)。
接下来就从 AMS 的 startActivity 方法开始看起:
AMS 的 startActivity
从上图可以看出,经过多个方法的调用,最终通过 obtainStarter 方法获取了 ActivityStarter 类型的对象,然后调用其 execute 方法。在 execute 方法中,会再次调用其内部的 startActivityMayWait 方法。
ActivityStarter 的 startActivityMayWait
ActivityStarter 这个类看名字就知道它专门负责一个 Activity 的启动操作。它的主要作用包括解析 Intent、创建 ActivityRecord、如果有可能还要创建 TaskRecord。startActivityMayWait 方法的部分实现如下:
从上图可以看出获取目标 Activity 信息的操作由 mSupervisor 来实现,它是 ActivityStackSupervisor 类型,从名字也能猜出它主要是负责 Activity 所处栈的管理类。
在上图中的 resolveIntent 中实际上是调用系统 PackageManagerService 来获取最佳 Activity。有时候我们通过隐式 Intent 启动 Activity 时,系统中可能存在多个 Activity 可以处理 Intent,此时会弹出一个选择框让用户选择具体需要打开哪一个 Activity 界面,就是此处的逻辑处理结果。
在 startActivityMayWait 方法中调用了一个重载的 startActivity 方法,而最终会调用的 ActivityStarter 中的 startActivityUnchecked 方法来获取启动 Activity 的结果。
ActivityStarter 的 startActivityUnchecked
解释说明:
- 图中 1 处计算启动 Activity 的 Flag 值。
- 注释 2 处处理 Task 和 Activity 的进栈操作。
- 注释 3 处启动栈中顶部的 Activity。
computeLaunchingTaskFlags 方法具体如下:
这个方法的主要作用是计算启动 Activity 的 Flag,不同的 Flag 决定了启动 Activity 最终会被放置到哪一个 Task 集合中。
- 图中 1 处 mInTask 是 TaskRecord 类型,此处为 null,代表 Activity 要加入的栈不存在,因此需要判断是否需要新建 Task。
- 图中 2 处的 mSourceRecord 的类型 ActivityRecord 类型,它是用来描述“初始 Activity”,什么是“初始 Activity”呢?比如 ActivityA 启动了ActivityB,ActivityA 就是初始 Activity。当我们使用 Context 或者 Application 启动 Activity 时,此 SourceRecord 为 null。
- 图中 3 处表示初始 Activity 如果是在 SingleInstance 栈中的 Activity,这种需要添加 NEW_TASK 的标识。因为 SingleInstance 栈只能允许保存一个 Activity。
- 图中 4 处表示如果 Launch Mode 设置了 singleTask 或 singleInstance,则也要创建一个新栈。
ActivityStackSupervisor 的 startActivityLocked
方法中会调用 insertTaskAtTop 方法尝试将 Task 和 Activity 入栈。如果 Activity 是以 newTask 的模式启动或者 TASK 堆栈中不存在该 Task id,则 Task 会重新入栈,并且放在栈的顶部。需要注意的是:Task 先入栈,之后才是 Activity 入栈,它们是包含关系。
这里一下子涌出了好几个概念 Stack、Task、Activity,其实它们都是在 AMS 内部维护的数据结构,可以用一张图来描述它们之间的关系。
关于它们之间实际操作过程可以参考 Android 8.0 Activity启动流程 这篇文章,不过需要注意这篇文章中分析的是基于 android-27 版本。
ActivityStack 的 resumeFocusedStackTopActivityLocked
经过一系列调用,最终代码又回到了 ActivityStackSupervisor 中的 startSpecificActivityLocked 方法。
ActivityStackSupervisor 的 startSpecificActivityLocked
解释说明:
- 图中 1 处根据进程名称和 Application 的 uid 来判断目标进程是否已经创建,如果没有则代表进程未创建。
- 图中 2 处调用 AMS 创建 Activity 所在进程。
不管是目标进程已经存在还是新建目标进程,最终都会调用图中红线标记的 realStartActivityLocked 方法来执行启动 Activity 的操作。
ActivityStackSupervisor 的 realStartActivityLocked
这个方法在 android-27 和 android-28 版本的区别很大,从 android-28 开始 Activity 的启动交给了事务(Transaction)来完成。
- 图中 1 处创建 Activity 启动事务,并传入 app.thread 参数,它是 ApplicationThread 类型。在上文 startActivity 阶段已经提过 ApplicationThread 是为了实现进程间通信的,是 ActivityThread 的一个内部类。
- 图中 2 处执行 Activity 启动事务。
Activity 启动事务的执行是由 ClientLifecycleManager 来完成的,具体代码如下:
可以看出实际上是调用了启动事务 ClientTransaction 的 schedule 方法,而这个 transaction 实际上是在创建 ClientTransaction 时传入的 app.thread 对象,也就是 ApplicationThread。如下所示:
解释说明:
- 这里传入的 app.thread 会赋值给 ClientTransaction 的成员变量 mClient,ClientTransaction 会调用 mClient.scheduleTransaction(this) 来执行事务。
- 这个 app.thread 是 ActivityThread 的内部类 ApplicationThread,所以事务最终是调用 app.thread 的 scheduleTransaction 执行。
到这为止 startActivity 操作就成功地从 AMS 转移到了另一个进程 B 中的 **ApplicationThread **中,剩下的就是 AMS 通过进程间通信机制通知 ApplicationThread 执行 ActivityB 的生命周期方法。
ApplicationThread -> Activity
刚才我们已近分析了 AMS 将启动 Activity 的任务作为一个事务 ClientTransaction 去完成,在 ClientLifecycleManager 中会调用 ClientTransaction的schedule() 方法,如下:
而 mClient 是一个 IApplicationThread 接口类型,具体实现是 ActivityThread 的内部类 ApplicationThread。因此后续执行 Activity 生命周期的过程都是由 ApplicationThread 指导完成的,scheduleTransaction 方法如下:
可以看出,这里还是调用了ActivityThread 的 scheduleTransaction 方法。但是这个方法实际上是在 ActivityThread 的父类 ClientTransactionHandler 中实现,具体如下:
调用 sendMessage 方法,向 Handler 中发送了一个 EXECUTE_TRANSACTION 的消息,并且 Message 中的 obj 就是启动 Activity 的事务对象。而这个 Handler 的具体实现是 ActivityThread 中的 mH 对象。具体如下:
最终调用了事务的 execute 方法,execute 方法如下:
在 executeCallback 方法中,会遍历事务中的 callback 并执行 execute 方法,这些 callbacks 是何时被添加的呢?
还记得 ClientTransaction 是如何创建被创建的吗?重新再看一遍:
在创建 ClientTransaction 时,通过 addCallback 方法传入了 Callback 参数,从图中可以看出其实是一个 LauncherActivityItem 类型的对象。
LaunchActivityItem 的 execute()
终于到了跟 Activity 生命周期相关的方法了,图中 client 是 ClientTransationHandler 类型,实际实现类就是 ActivityThread。因此最终方法又回到了 ActivityThread。
ActivityThread 的 handleLaunchActivity
这是一个比较重要的方法,Activity 的生命周期方法就是在这个方法中有序执行,具体如下:
解释说明:
- 图中 1 处初始化 Activity 的 WindowManager,每一个 Activity 都会对应一个“窗口”,下一节会详细讲解。
- 图中 2 处调用 performLaunchActivity 创建并显示 Activity。
- 图中 3 处通过反射创建目标 Activity 对象。
- 图中 4 处调用 attach 方法建立 Activity 与 Context 之间的联系,创建 PhoneWindow 对象,并与 Activity 进行关联操作,下节会详细讲解。
- 图中 5 处通过 Instrumentation 最终调用 Activity 的 onCreate 方法。
至此,目标 Activity 已经被成功创建并执行生命周期方法。
总结
这节课我带着你详细查看了 Activity 的启动在源码中的实现流程。这一过程主要涉及 3 个进程间的通信过程:
- 首先进程 A 通过 Binder 调用 AMS 的 startActivity 方法。
- 然后 AMS 通过一系列的计算构造目标 Intent,然后在 ActivityStack 与 ActivityStackSupervisor 中处理 Task 和 Activity 的入栈操作。
- 最后 AMS 通过 Binder 机制,调用目标进程中 ApplicationThread 的方法来创建并执行 Activity 生命周期方法,实际上 ApplicationThread 是 ActivityThread 的一个内部类,它的执行最终都调用到了 ActivityThread 中的相应方法。
第20讲:底层剖析 Window 、Activity、 View 三者关系
不管工作几年的 Android 工程师,或多或少都听说过 Window 的概念,并且隐隐约约感觉它在 Activity 与 View 之间应该发挥着某种连接的作用。但是如果需要说出这 3 者之间的关系,多数工程师不知道从何下手。
Activity 的 setContentView
Activity 是 Android 开发人员使用最频繁的 API 之一,最初在接触 Android 开发时,我始终认为它就是负责将 layout 布局中的控件渲染绘制出来的。原因很简单,每当我们想显示一个新的界面时,都是通过 start 一个新的 Activity 方式;对于想显示的内容或者布局,也只需要在 Activity 中添加一行 setContentView 即可,剩下的 Activity 都自动帮我们搞定。但是我们从来没有去创建一个 Window 来绑定 UI 或者 View 元素。
直到我点开 setContentView 源码的那一刻:
显然 Activity 几乎什么都没做,将操作直接交给了一个 Window 来处理。getWindow 返回的是 Activity 中的全局变量 mWindow,它是 Window 窗口类型。那么它是什么时候赋值的呢?
记得上节课分析 startActivity 的过程,最终代码会调用到 ActivityThread 中的 performLaunchActivity 方法,通过反射创建 Activity 对象,并执行其 attach 方法。Window 就是在这个方法中被创建,详细代码如下:
在 Activity 的 attach 方法中将 mWindow 赋值给一个 PhoneWindow 对象,实际上整个 Android 系统中 Window 只有一个实现类,就是 PhoneWindow。
接下来调用 setWindowManager 方法,将系统 WindowManager 传给 PhoneWindow,如下所示:
最终,在 PhoneWindow 中持有了一个 WindowManagerImpl 的引用。
PhoneWindow 的 setContentView
Activity 将 setContentView 的操作交给了 PhoneWindow,接下来看下其实现过程:
解释说明:
- 图中 1 处调用如果 mContentParent 为 null,则调用 installDecor 初始化 DecorView 和 mContentParent。
- 图中 2 处将我们调用 setContentView 传入的布局添加到 mContentParent 中。
可以看出在 PhoneWindow 中默认有一个 DecorView(实际上是一个 FrameLayout),在 DecorView 中默认自带一个 mContentParent(实际上是一个 ViewGroup)。我们自己实现的布局是被添加到 mContentParent 中的,因此经过 setContentView 之后,PhoneWindow 内部的 View 关系如下所示:
![在这里插入图片描述](https://s2.51cto.com/images/blog/202401/10204332_659e90f480bd039646.gif#pic_center)
目前为止 PhoneWindow 中只是创建出了一个 DecorView,并在 DecorView 中填充了我们在 Activity 中传入的 layoutId 布局,可是 DecorView 还没有跟 Activity 建立任何联系,也没有被绘制到界面上显示。那 DecorView 是何时被绘制到屏幕上的呢?
刚接触 Android,学习生命周期时,经常会看到相关文档介绍 Activity 执行到 onCreate 时并不可见,只有执行完 onResume 之后 Activity 中的内容才是屏幕可见状态。造成这种现象的原因就是,onCreate 阶段只是初始化了 Activity 需要显示的内容,而在 onResume 阶段才会将 PhoneWindow 中的 DecorView 真正的绘制到屏幕上。
在 ActivityThread 的 handleResumeActivity 中,会调用 WindowManager 的 addView 方法将 DecorView 添加到 WMS(WindowManagerService) 上,如下所示:
WindowManger 的 addView 结果有两个:
- DecorView 被渲染绘制到屏幕上显示;
- DecorView 可以接收屏幕触摸事件。
WindowManager 的 addView
PhoneWindow 只是负责处理一些应用窗口通用的逻辑(设置标题栏,导航栏等)。但是真正完成把一个 View 作为窗口添加到 WMS 的过程是由 WindowManager 来完成的。
WindowManager 是接口类型,上文中我们也了解到它真正的实现者是 WindowManagerImpl 类,看一下它的 addView 方法如下:
WindowManagerImpl 也是一个空壳,它调用了 WindowManagerGlobal 的 addView 方法。
WindowMangerGlobal 是一个单例,每一个进程中只有一个实例对象。如上图红框中所示,在其 addView 方法中,创建了一个最关键的 ViewRootImpl 对象,然后通过 ViewRootImpl 的 setView 方法将 view 添加到 WMS 中。
ViewRootImpl 的 setView
解释说明:
- 图中 1 处的 requestLayout 是刷新布局的操作,调用此方法后 ViewRootImpl 所关联的 View 也执行 measure - layout - draw 操作,确保在 View 被添加到 Window 上显示到屏幕之前,已经完成测量和绘制操作。
- 图中 2 处调用 mWindowSession 的 addToDisplay 方法将 View 添加到 WMS 中。
WindowSession 是 WindowManagerGlobal 中的单例对象,初始化代码如下:
sWindowSession 实际上是 IWindowSession 类型,是一个 Binder 类型,真正的实现类是 System 进程中的 Session。上图中红框中就是用 AIDL 获取 System 进程中 Session 的对象。其 addToDisplay 方法如下:
图中的 mService 就是 WMS。至此,Window 已经成功的被传递给了 WMS。剩下的工作就全部转移到系统进程中的 WMS 来完成最终的添加操作。
再看 Activity
上文中我提到 addView 成功有一个标志就是能够接收触屏事件,通过对 setContentView 流程的分析,可以看出添加 View 的操作实质上是 PhoneWindow 在全盘操作,背后负责人是 WMS,反之 Activity 自始至终没什么参与感。但是我们也知道当触屏事件发生之后,Touch 事件首先是被传入到 Activity,然后才被下发到布局中的 ViewGroup 或者 View。那么 Touch 事件是如何传递到 Activity 上的呢?
ViewRootImpl 中的 setView 方法中,除了调用 IWindowSession 执行跨进程添加 View 之外,还有一项重要的操作就是设置输入事件的处理:
如上图红框中所示,设置了一系列的输入通道。一个触屏事件的发生是由屏幕发起,然后经过驱动层一系列的优化计算通过 Socket 跨进程通知 Android Framework 层(实际上就是 WMS),最终屏幕的触摸事件会被发送到上图中的输入管道中。
这些输入管道实际上是一个链表结构,当某一个屏幕触摸事件到达其中的 ViewPostImeInputState 时,会经过 onProcess 来处理,如下所示:
可以看到在 onProcess 中最终调用了一个 mView的dispatchPointerEvent 方法,mView 实际上就是 PhoneWindow 中的 DecorView,而 dispatchPointerEvent 是被 View.java 实现的,如下所示:
最终调用了 PhoneWindow 中 Callback的dispatchTouchEvent 方法,那这个 Callback 是不是 Activity 呢?
在启动 Activity 阶段,创建 Activity 对象并调用 attach 方法时,有如下一段代码:
果然将 Activity 自身传递给了 PhoneWindow,再接着看 Activity的dispatchTouchEvent 方法:
Touch 事件在 Activity 中只是绕了一圈最后还是回到了 PhoneWindow 中的 DecorView 来处理。剩下的就是从 DecorView 开始将事件层层传递给内部的子 View 中了。
总结
这节课主要通过 setContentView 的流程,分析了 Activity、Window、View 之间的关系。整个过程 Activity 表面上参与度比较低,大部分 View 的添加操作都被封装到 Window 中实现。而 Activity 就相当于 Android 提供给开发人员的一个管理类,通过它能够更简单的实现 Window 和 View 的操作逻辑。
最后再简单列一下整个流程需要注意的点:
- 一个 Activity 中有一个 window,也就是 PhoneWindow 对象,在 PhoneWindow 中有一个 DecorView,在 setContentView 中会将 layout 填充到此 DecorView 中。
- 一个应用进程中只有一个 WindowManagerGlobal 对象,因为在 ViewRootImpl 中它是 static 静态类型。
- 每一个 PhoneWindow 对应一个 ViewRootImple 对象。
- WindowMangerGlobal 通过调用 ViewRootImpl 的 setView 方法,完成 window 的添加过程。
- ViewRootImpl 的 setView 方法中主要完成两件事情:View 渲染(requestLayout)以及接收触屏事件。
关于 View 被 WMS 添加到 Window 后,如何渲染到界面我将在下节课介绍。
第21讲:Android 如何通过 View 进行渲染?
在上一课时介绍 Activity、Window 和 View 之间的关系时,我们了解了 ViewRootImpl 在整个流程中,起着承上启下的作用。
一方面 ViewRootImpl 中通过 Binder 通信机制,远程调用 WindowSession 将 View 添加到 Window 中。
另一方面,ViewRootImpl 在添加 View 之前,又需要调用 requestLayout 方法,执行完整的 View 树的渲染操作。
屏幕绘制
ViewRootImpl requestLayout 流程
requestLayout 第一次被调用是在 setView 方法中,从名字也能看出,这个方法的主要目的就是请求布局操作,其中包括 View 的测量、布局、绘制等。具体代码如下:
说明:
注释 1 处检查是否为合法线程,一般情况下就是检查是否为主线程。
注释 2 处将请求布局标识符设置为 true,这个参数决定了后续是否需要执行 measure 和 layout 操作。
最后执行 scheduleTraversals 方法,如下:
说明:
注释 1 处向主线程消息队列中插入 SyncBarrier Message。该方法发送了一个没有 target 的 Message 到 Queue 中,在 next 方法中获取消息时,如果发现没有 target 的 Message,则在一定的时间内跳过同步消息,优先执行异步消息。这里通过调用此方法,保证 UI 绘制操作优先执行。
注释 2 处调用 Choreographer 的 postCallback 方法,实际上也是发送一个 Message 到主线程消息队列。
Choreographer 的 postCallback 的执行流程如下:
可以看出最终通过 Handler 发送到 MessageQueue 中的 Message 被设置为异步类型的消息。
mTraversalRunnable 是一个实现 Runnable 接口的 TraversalRunnable 类型对象,其 run 方法如下:
可以看出,在 run 方法中调用了 doTraversal 方法,并最终调用了 performTraversals() 方法,这个方法就是真正的开始 View 绘制流程:measure –> layout –> draw 。
ViewRootImpl 的 performTraversals 方法
这个方法是一个比较重的方法,查看源码发现总共将近 900 行代码。但是抽取一下核心部分代码,这个方法实际上只负责做 3 件事情:
很明显,实际就是执行了我们在自定义 View 课时中学习的 3 个主要过程。
接下来以测量 performMeasure 实现举例。
ViewRootImpl 的 measureHierarchy
我们知道 View 的测量是一层递归调用,递归执行子 View 的测量工作之后,最后决定父视图的宽和高。但是这个递归的起源是在哪里呢?答案就是 DecorView。因为在 measureHierarchy 方法中最终是调用 performMeasure 方法来进行测量工作的,所以我们直接看 performMeasure 方法的实现,如下所示:
在这个方法中,通过 getRootMeasureSpec 方法获取了根 View的MeasureSpec,实际上 MeasureSpec 中的宽高此处获取的值是 Window 的宽高。关于 MeasureSpec 的介绍可以查看第15课时“自定义 View”。
ViewRootImpl 的 performMeasure
这个方法很简单,只是执行了 mView 的 measure 方法,这个 mView 就是 DecorVIew。其 DecorView 的 measure 方法中,会调用 onMeasure 方法,而 DecorView 是继承自 FrameLayout 的,因此最终会执行 FrameLayout 中的 onMeasure 方法,并递归调用子 View 的 onMeasure 方法。
performLayout 也是类似的过程,就不再赘述。
ViewRootImpl 的 performDraw
从图中可以看出,在 performDraw 方法中,调用的 ViewRootImpl 的 draw 方法。在 draw 方法中进行 UI 绘制操作,Android 系统提供了 2 种绘制方式:
- 图中 1 处表示 App 开启了硬件加速功能,所以会启动硬件加速绘制;
- 图中 2 处表示使用软件绘制。
ViewRootImpl 中有一个非常重要的对象 Surface,之所以说 ViewRootImpl 的一个核心功能就是负责 UI 渲染,原因就在于在 ViewRootImpl 中会将我们在 draw 方法中绘制的 UI 元素,绑定到这个 Surface 上。如果说 Canvas 是画板,那么 Surface 就是画板上的画纸,Surface 中的内容最终会被传递给底层的 SurfaceFlinger,最终将 Surface 中的内容进行合成并显示的屏幕上。
软件绘制 drawSoftware
图中 1 处就是调用 DecorView 的 draw 方法将 UI 元素绘制到画布 Canvas 对象中,具体可以绘制的内容在自定义 View 课时已经介绍过了。
图中 2 处请求将 Canvas 中的内容显示到屏幕上,实际上就是将 Canvas 中的内容提交给 SurfaceFlinger 进行合成处理。
默认情况下软件绘制没有采用 GPU 渲染的方式,drawSoftware 工作完全由 CPU 来完成。
DecorView 并没有复写 draw 方法,因此实际是调用的顶层 View 的 draw 方法,如下:
解释说明:
- 图中 1 处绘制 View 的背景;
- 图中 2 处绘制 View 自身内容;
- 图中 3 处表示对 draw 事件进行分发,在 View 中是空实现,实际调用的是 ViewGroup 中的实现,并递归调用子 View 的 draw 事件。
启用硬件加速
是否启用硬件加速
可以在 ViewRootImpl 的 draw 方法中,通过如下方法判断是否启用硬件加速:
我们可以在 AndroidManifest 清单文件中,指定 Application 或者某一个 Activity 支持硬件加速,如下:
此外我们还可以进行粒度更小的硬件加速设置,比如设置某个 View 支持硬件加速:
之所以会有这么多级的支持区分,主要是因为并不是所有的 2D 绘制操作都支持硬件加速,当在自定义 View 中使用了如下 API,则有可能造成程序工作不正常:
Canvas
- clipPath()
- clipRegion()
- drawPicture()
- drawPosText()
- drawTextOnPath()
- drawVertices()
Paint
- setLinearText()
- setMaskFilter()
- setRasterizer()
硬件加速优势
接下来,看下为什么硬件加速能够提高 UI 渲染的性能。再看 ViewRootImpl 的 draw 方法:
图中 mThreadRenderer 是 ThreadRenderer 类型,其 draw 方法具体如下:
图中注释 1 处就是硬件加速的特殊之处,通过 updateRootDisplayList 方法将 View 视图抽象成一个 RenderNode 对象,并构建 View 的 DrawOp 树。
图中 2 处通知 RenderThread 进行绘制操作,RenderThread 是一个单例线程,每个进程最多只有一个硬件渲染线程,这样就不会存在多线程并发访问冲突问题。
updateRootDisplayList 具体如下:
Android 硬件加速过程中,View 视图被抽象成 RenderNode 节点,View 中的绘制操作都会被抽象成一个个 DrawOp,比如 View中drawLine,构建过程中就会被抽象成一个 DrawLineOp,drawBitmap 操作会被抽象成 DrawBitmapOp。每个子 View 的绘制被抽象成 DrawRenderNodeOp,每个 DrawOp 有对应的 OpenGL 绘制命令。
上图中 1 处就是遍历 View 递归构建 DrawOp,2 处就是根据 Canvas 将所有的 DrawOp 进行缓存操作。所有的 DrawOp 对应的 OpenGL 命令构建完成之后,就需要使用 RenderProxy 向 RenderThread 发送消息,请求 OpenGL 线程进行渲染。整个渲染过程是通过 GPU 并在不同线程绘制渲染图形,因此整个流程会更加顺畅。
Invalidate 轻量级刷新
如果你做过开发应该用过 invalidate 来刷新 View,这个方法跟 requestLayout 的区别在于,它不一定会触发 View 的 measure 和 layout 的操作,多数情况下只会执行 draw 操作。
在 View 的 measure 方法中,有如下几行代码:
可以看出,如果要触发 onMeasure 方法,需要对 View 设置 PFLAG_FORCE_LAYOUT 标志位,而这个标志位在 requestLayout 方法中被设置,invalidate 并没有设置此标志位。
再看下 onLayout 方法:
可以看出,当 View 的位置发送改变,或者添加 PFLAG_FORCE_LAYOUT 标志位后 onLayout 才会被执行。当调用 invalidate 方法时,如果 View 的位置并没有发生改变,则 View 不会触发重新布局的操作。
postInvalidate
说到 invalidate 就不得不说一下 postInvalidate,不光是因为面试中经常被问到,实际开发中使用频率也是较高。
它们两者之间的区别就是 invalidate 是在 UI 线程调用,postInvalidate 是在非 UI 线程调用。
postInvalidate 的实现如下:
最终还是在 ViewRootImpl 中进行操作。
ViewRootImpl 的 dispatchInvalidateDelayed
在非 UI 线程中,通过 Handler 发送了一个延时 Message,因为 Handler 是在主线程中创建的,所以最终 handlerMessage 会在主线程中被执行,方法如下:
上图中的 msg.obj 就是发送 postInvalidate 的 View 对象,可以看出最终还是回到 UI 线程执行了 View 的 invalidate 方法。
个人理解:做过 Android 开发的都知道只有 UI 线程才可以刷新 View 控件,但是事实却并非如此。在 ViewRootImpl 中对 View 进行刷新时,会检查当前线程的合法性:
图中 mThread 是被赋值为当前线程,而 ViewRootImpl 是在 UI 线程中被创建的,因此只有 UI 线程可以进行 View 刷新。但是如果我们能在非 UI 线程中创建 ViewRootImpl,并通过这个 ViewRootImpl 进行 View 的添加和绘制操作,那么后续理论上也是可以在非 UI 线程中刷新 View 控件的,只是维护成本较高,很少有人去做这件事情。
总结
至此 View 的工作流程的大致整体已经描述完毕了,做一下总结。本课时主要介绍了 ViewRootImpl 是如何执行 View 的渲染操作的,其中核心方法在 performTraversals 方法中会按顺序执行 measure-layout-draw 操作。并顺带介绍了软件绘制和硬件加速的区别,最后介绍了 View 刷新的两种方式 Invalidate 和 postInvalidate。
第22讲:Android App 的安装过程
之前的两节课我们了解了 Android 系统中两个比较重要的系统服务ActivityManagerService(AMS)和 WindowManagerService(WMS),这节课我们通过分析 apk 的安装过程来了解一下 Android 中另一个比较重要的系统服务——PackageManagerService(PMS)。
在分析安装过程之前,需要先了解一下 Android 项目是如何经过编译->打包生成最终的 .apk 格式的安装包。谷歌有一张官方图片来描述 apk 的打包流程,如下图所示。
一个完整的 Android 项目可能包含多个 module,而从宏观上看每一个 module 中的内容可以分为 2 部分:Resources 资源文件、Java 或者 Kotlin 源代码。因此整个项目的编译打包过程也是针对这 2 部分来完成。
编译阶段
Resources 资源文件
资源文件包括项目中 res 目录下的各种 XML 文件、动画、drawable 图片、音视频等。AAPT 工具负责编译项目中的这些资源文件,所有资源文件会被编译处理,XML 文件(drawable 图片除外)会被编译成二进制文件,所以解压 apk 之后无法直接打开 XML 文件。但是 assets 和 raw 目录下的资源并不会被编译,会被原封不动的打包到 apk 压缩包中。
资源文件编译之后的产物包括两部分:resources.arsc 文件和一个 R.java。前者保存的是一个资源索引表,后者定义了各个资源 ID 常量。这两者结合就可以在代码中找到对应的资源引用。比如如下的 R.java 文件:
可以看出,R.java 中的资源 ID 是一个 4 字节的无符号整数,用 16 进制表示。其中,最高的 1 字节表示 Package ID,次高的 1 个字节表示 Type ID,最低的 2 字节表示 Entry ID。
resources.arsc 相当于一个资源索引表,也可以理解为一个 map 映射表。其中 map 的 key 就是 R.java 中的资源 ID,而 value 就是其对应的资源所在路径。实际上 resources.arsc 里面还有其他信息
源码部分
项目中的源代码首先会通过 javac 编译为 .class 字节码文件,然后这些 .class 文件连同依赖的三方库中的 .class 文件一同被 dx 工具优化为 .dex 文件。如果有分包,那么也可能会生成多个 .dex 文件。
实际上源代码文件也包括 AIDL 接口文件编译之后生成的 .java 文件,Android 项目中如果包含 .aidl 接口文件,这些 .aidl 文件会被编译成 .java 文件。
打包阶段
最后使用工具 APK Builder 将经过编译之后的 resource 和 .dex 文件一起打包到 apk 中,实际上被打包到 apk 中的还有一些其他资源,比如 AndroidManifest.xml 清单文件和三方库中使用的动态库 .so 文件。
apk 创建好之后,还不能直接使用。需要使用工具 jarsigner 对其进行签名,因为 Android 系统不会安装没有进行签名的程序。签名之后会生成 META_INF 文件夹,此文件夹中保存着跟签名相关的各个文件。
- CERT.SF:生成每个文件相对的密钥
- MANIFEST.MF:数字签名信息
- xxx.SF:这是 JAR 文件的签名文件
- xxx.DSA:对输出文件的签名和公钥。
PMS 在安装过程中会检查 apk 中的签名证书的合法性,具体内容稍后介绍。
常理来说,签名之后的 apk 应该是可以正常安装使用了,但是实际打包过程还会多一步 apk 优化操作。就是使用工具 zipalign 对 apk 中的未压缩资源(图片、视频等)进行对齐操作,让资源按照 4 字节的边界进行对齐。这种思想同 Java 对象内存布局中的对齐空间非常类似,主要是为了加快资源的访问速度。如果每个资源的开始位置都是上一个资源之后的 4n 字节,那么访问下一个资源就不用遍历,直接跳到 4n 字节处判断是不是一个新的资源即可。
至此一个完整的 apk 安装包就创建成功,一个完整的 apk 解压缩之后的内容如下所示:
整个编译打包流程可以用下图来描述:
接下来看一下 PMS 是如何将其安装到手机设备中的。
PMS安装过程概览
当我们点击某一个 App 安装包进行安装时,首先会弹出一个系统界面指示我们进行安装操作。这个界面是 Android Framework 中预置的一个 Activity—PackageInstallerActivity.java。当点击安装后,PackageInstallerActivity 最终会将所安装的 apk 信息通过 PackageInstallerSession 传给 PMS,具体方法在 commitLocked 方法中,如下所示:
图中的 mPm 就是系统服务 PackageManagerService。installStage 方法就是正式开始 apk 的安装过程。
整个 apk 的安装过程可以分为两大步:
- 拷贝安装包;
- 装载代码。
拷贝安装包
从 installStage 方法开始看起,代码如下:
解释说明:
- 图中 1 处创建了类型为 INIT_COPY 的 Message。
- 图中 2 处创建 InstallParams,并传入安装包的相关数据。
Message 发送出去之后,由 PMS 的内部类 PackageHandler 接收并处理,如下:
解释说明:
- 图中 1 处从 Message 中取出 HandlerParams 对象,实际类型是 InstallParams 类型。
- 图中 2 处调用 connectToService 方法连接安装 apk 的 Service 服务。
PackageHandler 的 connectToService 方法
通过隐式 Intent 绑定 Service,实际绑定的 Service 类型是 DefaultContainerService 类型。当绑定 Service 成功之后,会在 onServiceConnection 方法中发送一个绑定操作的 Message,如下所示:
MCS_BOUND 的 Message 接收者还是 PackageHandler,具体如下:
mPendingInstalls 是一个等待队列,里面保存所有需要安装的 apk 解析出来的 HandlerParams 参数,从 mPendingInstalls 中取出第一个需要安装的 HandlerParams 对象,并调用其 startCopy 方法,在 startCopy 方法中会继续调用一个抽象方法 handleStartCopy 处理安装请求。通过之前的分析,我们知道 HandlerParams 实际类型是 InstallParams 类型,因此最终调用的是 InstallParams 的 handlerStartCopy 方法,
InstallParams 的 handlerStartCopy 方法
这个方法是整个安装包拷贝的核心方法,具体如下:
解释说明:
- 图中 1 处设置安装标志位,决定是安装在手机内部存储空间还是 sdcard 中。
- 图中 2 处判断 apk 安装位置。
如果安装位置合法,则执行图中 3 处逻辑,创建一个 InstallArgs,实际上是其子类 FileInstallArgs 类型,然后调用其 copyApk 方法进行安装包的拷贝操作。
FileInstallArgs 的 copyApk 方法
可以看出在 copyApk 方法中调用了 doCopyApk 方法,doCopyAPk 方法中主要做了 3 件事情:
- 图中 1 处创建存储安装包的目标路径,实际上是 /data/app/ 应用包名目录;
- 图中 2 处调用服务的 copyPackage 方法将安装包 apk 拷贝到目标路径中;
- 图中 3 处将 apk 中的动态库 .so 文件也拷贝到目标路径中。
上图中的 IMediaContainerService 实际上就是在开始阶段进行连接操作的 DefaultContainerService 对象,其内部 copyPackage 方法本质上就是执行 IO 流操作,具体如下:
最终安装包在 data/app 目录下以 base.apk 的方式保存,至此安装包拷贝工作就已经完成。
装载代码
代码拷贝结束之后,就开始进入真正的安装步骤。
代码回到上述的 HandlerParams 中的 startCopy 方法:
![在这里插入图片描述](https://s2.51cto.com/images/blog/202401/10204627_659e91a32eb4273261.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=#pic_center)
可以看出当安装包拷贝操作结束之后,继续调用 handleReturnCode 方法来处理返回结果,最终调用 processPendingInstall 方法处理安装过程,代码具体如下:
解释说明:
- 图中 1 处执行预安装操作,主要是检查安装包的状态,确保安装环境正常,如果安装环境有问题会清理拷贝文件。
- 图中 2 处是真正的安装阶段,installPackageTraceLI 方法中添加跟踪 Trace,然后调用 installPackageLI 方法进行安装。
- 图中 3 处处理安装完成之后的操作。
installPackageLI 是 apk 安装阶段的核心代码,方法实现很长,部分核心代码如下:
解释说明:
- 图中 1 处调用 PackageParser 的 parsePackage 方法解析 apk 文件,主要是解析 AndroidManifest.xml 文件,将结果记录在 PackageParser.Package 中。我们在清单文件中声明的 Activity、Service 等组件就是在这一步中被记录到系统 Framework 中,后续才可以通过 startActivity 或者 startService 启动相应的活动或者服务。
- 图中 2 处对 apk 中的签名信息进行验证操作。collectCertificates 做签名验证,collectManifestDigest 主要是做包的项目清单摘要的收集,主要适合用来比较两个包的是否一样。如果我们设备上已经安装了一个 debug 版本的 apk,再次使用一个 release 版本的 apk 进行覆盖安装时,会在这一步验证失败,最终导致安装失败。
- 图中 3 处时执行 dex 优化,实际为 dex2oat 操作,用来将 apk 中的 dex 文件转换为 oat 文件。
- 图中 4 处调用 installNewPackageLI 方法执行新 apk 的安装操作
installNewPackageLI 方法负责完成最后的 apk 安装过程,具体代码如下:
解释说明:
- scanPackageLI 继续扫描解析 apk 安装包文件,保存 apk 相关信息到 PMS 中,并创建 apk 的 data 目录,具体路径为 /data/data/应用包名。
- updateSettingsLI 如果安装成功,更新系统设置中的应用信息,比如应用的权限信息。
- deletePackageLI 如果安装失败,则将安装包以及各种缓存文件删除
至此整个 apk 的安装过程结束,实际上安装成功之后,还会发送一个 App 安装成功的广播 ACTION_PACKAGE_ADDED。手机桌面应用注册了这个广播,当接收到应用安装成功之后,就将 apk 的启动 icon 显示在桌面上。
总结
这节课主要介绍了一个 Android 项目从编译成 apk 文件,然后被安装到手机设备上的简要过程。其中编译分 2 块内容:资源 + 源代码。并且生成 apk 之后还要经过签名、对齐等操作。apk 安装也分 2 块进行:安装包拷贝和代码装载。