作为 Android 四大组件之一的 Activity 我的印象中就是用来展示界面的,在很长一段时间里,只要提及界面、UI、View 我脑子里第一个闪过的就是 Activity ,我的理解中一直认为 界面、UI、View == Activity。其实呢非也!如果你曾经和我一样,说起 Actvity 就是聊他的生命周期,各个方法背的滚瓜烂熟,但也仅限于此而已并没有真正的去认识 Activity 的话,那么请跟我一起从【 Activity 的组成】、【启动】、【显示】来重新认识一下 Activity

一 Activity 的本质

首先明确一下 Activity 的基本概念,我们先来看下 Google 官方对 Activity 给出的定义:

Activity 是一个应用组件,用户可与其提供的屏幕进行交互,以执行拨打电话、拍摄照片、发送电子邮件或查看地图等操作。每个 Activity 都会获得一个用于绘制其用户界面的窗口。窗口通常会充满屏幕,但也可小于屏幕并浮动在其他窗口之上。

读完了好像也没整明白。

我们抛开之前对 Activity 的印象,从代码层面上来看他就是一个普通的 Java 类,本质上并不是一个 View 或 ViewGroup,他没有继承任何 View 和 ViewGroup 相关类。

从源码中可以看到 Activity 实现了 Window 、KeyEvent 等一系列的 Callback 以及 Listener 。看到这些大概可以知道 Activity 的职责相当于一个控制器了,接收各种回调以及处理监听事件。

基于 Google 官方的定义和 Activity 源码,Activity 的大概职责可以总结成一张图:

1.1 小结

  1. Activity 本身并不具备 View 任何属性,从代码层面看就是普通的类。

  2. 本身不是 View ,所以 Activity 本身并不做和 UI、视图控制相关的事,控制 UI、视图的另有其人。

  3. 在整体架构中扮演控制器角色,负责控制生命周期和事件处理。

二 Activity 组成剖析

是时候来揭开 Activity 的庐山真面目了。一图胜千言!先来看一张图。

上图描述了 Activity 从外到内大概的一个层级结构

2.1 DecorView

角色

顶层 View
当前 Activity 所有 View 的祖先, 即当前 Activity 视图树根节点。

作用

  1. 显示 & 加载布局。但其本身并不做任何 View 呈现。

  2. 分发 View 层事件,View 层事件由 ViewRoot 分发至 DecorView,再由 DecorView 分发给具体 View。

简介

  1. DecorView 本质上是一个 FrameLayout ( DecorView 类继承自 FrameLayout )。

  2. 其内部包含一个竖直布局的 LinearLayout ,分为2部分:

    id 为 title_container 的 TitleBar 。

    id 为 content 的 Content 。在Activity中通过 setContentView()所设置的布局文件最终就是被添加到此处的 Content 中。

  3. Content 内部的视图树对应的就是 Activity 的布局文件。

2.2 PhoneWindow

角色

视图承载器

作用

承载视图 View 的显示。

简介

PhoneWindow 为 Window 的实现类。每个 Activity 均会创建一个 Window 用以承载 View ,是视图真正的控制者。

2.3 ViewRoot

角色

连接器

作用

  1. 连接 WindowManager 和 DecorView 的纽带。

  2. 完成 View 绘制的三大流程:measurelayoutdraw

  3. 向 DecorView 分发用户发起的 View 层事件,如 KeyEvent,TouchEvent。

简介

对应实现类为 ViewRootImpl ,实现了 ViewParent 接口。作为 WindowManagerGlobal 大部分内部实现的实际实现者,需负责与 WindowManagerService 交互通信,以调整窗口的位置大小,以及对来自 WindowManagerService 的事件(如窗口尺寸改变等)作出相应的处理。

  1. WindowManagerGlobal 方法实际实现如:

 public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {......ViewRootImpl root;......root = new ViewRootImpl(view.getContext(), display);try {//最终实现,调用 ViewRootImpl setView 方法。root.setView(view, wparams, panelParentView);} catch (RuntimeException e) {......}}
  1. 与 WindowManagerService 交互通信:

 public ViewRootImpl(Context context, Display display) {......mWindowSession = WindowManagerGlobal.getWindowSession();......}//与 WindowManagerService 建立远程连接public static IWindowSession getWindowSession() {synchronized (WindowManagerGlobal.class) {if (sWindowSession == null) {try {InputMethodManager imm = InputMethodManager.getInstance();IWindowManager windowManager = getWindowManagerService();sWindowSession = windowManager.openSession(new IWindowSessionCallback.Stub() {@Overridepublic void onAnimatorScaleChanged(float scale) {ValueAnimator.setDurationScale(scale);}},imm.getClient(), imm.getInputContext());} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}return sWindowSession;}
}

三 Activity 显示

在简单分析了 Activity 各个组成元件之后,接下来就是看这些元件是如何完成完整的 Activity 组装并将 View 呈现到用户面前。

一图胜千言:

下面跟随上图的流程走一遍源码(为避免篇幅过长只展示重点代码)。

3.1 Activity 启动

这部分主要做的是一些初始化设置的工作,为 Actvity 最终呈现在用户面前做准备。

1.初始化 Looper 创建消息循环

做为 Android 应用程序的入口类,先来看下 ActivityTread 做了什么工作。

源码路径:ActivityTread 类 main 方法。

public static void main(String[] args) {.......Looper.prepareMainLooper();.......ActivityThread thread = new ActivityThread();thread.attach(false, startSeq);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}.......Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");
}

方法解析:

ActivityTread 的 main 方法初始化了一个 Looper消息循环用以接收主线程的操作消息。

关于Looper消息循环详细介绍可以查看 Android Handler浅析一文,这里就不再展开。

2.接收 LAUNCH_ACTIVITY 消息

Application 启动后,主线程会收到 LAUNCH_ACTIVITY 消息,该消息由 ActivityTread 内部类 H (继承自 Handler) 接收。

源码路径:ActivityTread/H 类 handleMessage 方法。

public void handleMessage(Message msg) {switch (msg.what) {case LAUNCH_ACTIVITY: {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");final ActivityClientRecord r = (ActivityClientRecord) msg.obj;r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);//重点看这个方法handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);} break;
}

接收消息后会调用 handleLaunchActivity() 方法,下面重点都在handleLaunchActivity() 方法里面了。

源码路径:ActivityTread 类 handleLaunchActivity 方法。

  private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {.......Activity a = performLaunchActivity(r, customIntent);if (a != null) {......handleResumeActivity(r.token, false, r.isForward,!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);} ......}

方法解析:

次方法内部重点看 performLaunchActivity() 和 handleResumeActivity() 方法的调用。

先来看 performLaunchActivity() 方法,此方法就开始了我们上图的第 3~9步的流程,直到流程走完,方法调用栈会回退至 handleLaunchActivity() 中继续往下执行,调用 handleResumeActivity()

3. 创建 Activity 对象

源码路径:ActivityTread 类 performLaunchActivity 方法。

 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {.......Activity activity = null;try {//通过 Activity 类名构建 Actvity 对象java.lang.ClassLoader cl = appContext.getClassLoader();activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);......} catch (Exception e) {.......}try {......if (activity != null) {......//为当前 Activity 初始一个 Window 并设置 WindowManageractivity.attach(appContext, this, getInstrumentation(), r.token,r.ident, app, r.intent, r.activityInfo, title, r.parent,r.embeddedID, r.lastNonConfigurationInstances, config,r.referrer, r.voiceInteractor, window, r.configCallback);......//通过 Instrumentation 调用 Activity 的 onCreate 方法if (r.isPersistable()) {mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);} else {mInstrumentation.callActivityOnCreate(activity, r.state);}......} catch (SuperNotCalledException e) {throw e;} catch (Exception e) {......}return activity;}

方法解析:

performLaunchActivity 方法做了三件重要的事:

  1. 通过当前 Activity 的类名为当前的 Activity 创建一个对象。

  2. 调用 activity.attach 方法为当前 Activity 初始化一个 Window 对象。

  3. 通过 Instrumentation 调用 Activity 的 onCreate 方法。

5.创建 PhoneWindow对象

performLaunchActivity 方法中调用 activity.attach 方法为当前 Activity 初始化一个 Window 对象,我们来看下 activity.attach 方法的具体实现。

源码路径:Activity 类 attach 方法。

 final void attach(Context context, ActivityThread aThread,Instrumentation instr, IBinder token, int ident,Application application, Intent intent, ActivityInfo info,CharSequence title, Activity parent, String id,NonConfigurationInstances lastNonConfigurationInstances,Configuration config, String referrer, IVoiceInteractor voiceInteractor,Window window, ActivityConfigCallback activityConfigCallback) {......//为 mWindow 变量赋值,实例化一个 PhoneWindow mWindow = new PhoneWindow(this, window, activityConfigCallback);......//为 mWindow 设置 WindowManager mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);......//为 mWindowManager 变量赋值mWindowManager = mWindow.getWindowManager();.......}

attach 方法中为 Activity 的 mWindow 变量实例化了一个 PhoneWindow 对象,PhoneWindow 是 Window 的一个具体实现,这在上文中已经说过,就不再赘述了。

6. 执行 Activity onCreate()

performLaunchActivity 方法中通过 Instrumentation 调用 Activity 的 onCreate 方法,该方法的具体实现在 Activity 中。一般来说我们都会重写 Activity 的 onCreate 方法并在里面调用 setContentView。下面来看下我们熟悉的 setContentView 里面做了些什么。

7. 调用 setContentView()

源码路径:Activity 类 setContentView 方法。

public void setContentView(@LayoutRes int layoutResID) {//调用 PhoneWindow 的 setContentViewgetWindow().setContentView(layoutResID);initWindowDecorActionBar();
}

Activity 的 setContentView 实际的实现来自 PhoneWindow,可以看下getWindow() 方法返回的其实就是步骤5里 attach 方法赋值的 mWindow 对象。

源码路径:Activity 类 getWindow 方法。

 public Window getWindow() {return mWindow;}

既然这样我们不妨跟进 PhoneWindow 中一探究竟:

源码路径:PhoneWindow 类 setContentView 方法。

@Override
public void setContentView(int layoutResID) {// 如mContentParent为空,创建一个DecroViewif (mContentParent == null) {//初始化 DecroViewinstallDecor();} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {mContentParent.removeAllViews();}if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,getContext());transitionTo(newScene);} else {//为 mContentParent 添加布局文件mLayoutInflater.inflate(layoutResID, mContentParent);}mContentParent.requestApplyInsets();final Window.Callback cb = getCallback();if (cb != null && !isDestroyed()) {//内容改变回调cb.onContentChanged();}mContentParentExplicitlySet = true;
}

方法解析:

  1. mContentParent 这个变量就是我们上文在介绍 DecroView 时提到的 FrameLayout 对应的布局部分。

  2. installDecor() 方法为 PhoneWindow 初始化一个 DecroView 对象。

  3. 一番骚操作之后将当前 Activity 的布局添加至 mContentParent 中。

8. 初始化 DecorView

PhoneWindow 的 setContentView 方法会为当前的 Window 初始化一个 DecorView。

源码路径:PhoneWindow 类 installDecor 方法。

private void installDecor() {if (mDecor == null) {// 创建 DecorView 对象mDecor = generateDecor(-1);mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);mDecor.setIsRootNamespace(true);if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);}} else {mDecor.setWindow(this);}if (mContentParent == null) {// 为 DecorView 的 content 设置布局格式mContentParent = generateLayout(mDecor);......}
}

方法解析:

1.调用 generateDecor 方法创建 DecorView 对象,generateDecor 实现很简单直接。

源码路径:PhoneWindow 类 generateDecor 方法。

protected DecorView generateDecor(int featureId) {......return new DecorView(context, featureId, this, getAttributes());
}
  1. 调用 generateLayout 方法为 DecorView 的 content 设置布局格式。

源码路径:PhoneWindow 类 generateLayout 方法。

protected ViewGroup generateLayout(DecorView decor) {//获取窗口样式信息TypedArray a = getWindowStyle();......// Inflate the window decor.//根据主题样式,加载窗口布局int layoutResource;int features = getLocalFeatures();......mDecor.startChanging();mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);// 获取 Decorview 对应的 content 的 FrameLayoutViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);if (contentParent == null) {throw new RuntimeException("Window couldn't find content container view");}......mDecor.finishChanging();return contentParent;
}

9. 添加布局到 DecorVIew Content,设置布局格式

这个步骤的源码步骤7中已经贴出来了,在 setContentView 方法中:

//为 mContentParent 添加布局文件
mLayoutInflater.inflate(layoutResID, mContentParent);

到这里 handleLaunchActivity 方法中的 performLaunchActivity 方法这条线就差不多走完了,现在回到 handleLaunchActivity 方法中继续往下执行,下面的关键方法是 handleResumeActivity

3.2 Activity 显示

先看 handleResumeActivity 方法的重点代码。

源码路径:ActivityThread 类 handleResumeActivity 方法。

 final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {......// TODO Push resumeArgs into the activity for consideration// 调用Activity的onResume()r = performResumeActivity(token, clearHide, reason);if (r != null) {final Activity a = r.activity;......if (r.window == null && !a.mFinished && willBeVisible) {.......if (a.mVisibleFromClient) {if (!a.mWindowAdded) {a.mWindowAdded = true;//添加 DecorView 到 Windowwm.addView(decor, l);} ......}......}
}

10. 执行 Activity onResume()

handleResumeActivity 方法中通过调用 performResumeActivity 方法来执行 Activity onResume() 方法。具体实现有兴趣的可以自己跟进源码进去看下,这里就不再贴源码了,文章篇幅已经长的头皮发麻了。

11. 添加 DecorView 到 WindowManager

handleResumeActivity 方法中通过调用 WindowManager 的 addView 方法将之前流程中已赋值的 DecorView 对象添加至当前 Window。

12. 初始化创建 ViewRootImpl

初始化 ViewRootImpl 工作就是在 WindowManager 的 addView 方法中进行的。而 WindowManager 是一个接口,对应的实现类是 WindowManagerImpl,那就去 WindowManagerImpl 中找 addView 的具体实现咯。

源码路径:WindowManagerImpl 类 addView 方法。

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

方法解析:

该方法内部直接调用 WindowManagerGlobal 的 addView 方法,WindowManager 的 addView 方法由 WindowManagerGlobal 代理了。

源码路径:WindowManagerGlobal 类 addView 方法。

public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {.......ViewRootImpl root;View panelParentView = null;synchronized (mLock) {......//创建ViewRootImpl对象root = new ViewRootImpl(view.getContext(), display);view.setLayoutParams(wparams);mViews.add(view);mRoots.add(root);mParams.add(wparams);try {//WindowManager将DecorView实例对象交给ViewRootImpl 绘制Viewroot.setView(view, wparams, panelParentView);} catch (RuntimeException e) {......throw e;}}
}

13. DecorView 对象传给 ViewRootImpl

WindowManagerGlobal 类 addView 方法在创建 ViewRootImpl 对象后调用其setView 方法将将 DecorView 实例对象交给 ViewRootImpl 用以绘制 View 。

14. 开始 View 绘制流程,呈现布局

所有一切准备就绪之后我们终于开始对我们 Activity 的布局进行绘制了。最终将调用 ViewRootImpl 的 performTraversals方法(此方法贼长)开始 View 绘制的三大流程,measure、layout、draw

源码路径:ViewRootImpl 类 performTraversals 方法。

private void performTraversals() {...int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);...//执行测量流程performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);...//执行布局流程performLayout(lp, desiredWindowWidth, desiredWindowHeight);...//执行绘制流程performDraw();
}

OK到此 Activity 的部件组装以及显示流程就梳理了一个大概,文章篇幅有点长,但是内容不算多,贴了很多代码,有兴趣的可以跟着我的流程自己跟进源码看下加深印象。

不怕面试再问 Activity,一次彻底地梳理(原理+生命周期)相关推荐

  1. 简述ip地址的abc类如何划分_面试官问:讲讲IP地址的分配原理

    网络模型介绍 在计算机网络中有著名的OSI七层协议体系结构,概念清楚,理论完整,但是它既复杂又不实用.TCP/IP体系结构则不同,得到了广泛的应用.最终结合OSI和TCP/IP的优点,采用了一种只有五 ...

  2. 【192期】面试官问:说说 Tomcat 组成与工作原理?

    点击上方"Java精选",选择"设为星标" 别问别人为什么,多问自己凭什么! 下方有惊喜,留言必回,有问必答! 每天 08:15 更新文章,每天进步一点点... ...

  3. 【067期】面试官问:说说常见的加密算法、原理、优缺点及用途?

    >>号外:关注"Java精选"公众号,回复"面试资料",免费领取资料!"Java精选面试题"小程序,3000+ 道面试题在线刷, ...

  4. 去字节面试,直接让人出门左拐:Bean 生命周期都不知道!

    Spring Bean 的生命周期,面试时非常容易问,这不,前段时间就有个粉丝去字节面试,因为不会回答这个问题,一面都没有过. 如果只讲基础知识,感觉和网上大多数文章没有区别,但是我又想写得稍微深入一 ...

  5. 面试再问ThreadLocal,别说你不会!

    点击上方"后端技术精选",选择"置顶公众号" 技术文章第一时间送达! 作者:坚持就是胜利 juejin.im/post/5d427f306fb9a06b122f ...

  6. 面试再问值传递与引用传递,把这篇文章砸给他!

    java的值传递和引用传递在面试中一般都会都被涉及到,今天我们就来聊聊这个问题,首先我们必须认识到这个问题一般是相对函数而言的,也就是java中的方法参数,那么我们先来回顾一下在程序设计语言中有关参数 ...

  7. mysql 动态传入表名 存储过程_面试再问MySQL存储过程和触发器就把这篇文章给他...

    Mysql存储过程及触发器trigger 存储过程 一.一个简单的存储过程 1,一个简单的存储过程 delimiter $$create procedure testa()begin Select * ...

  8. python 堆_面试再问你什么是堆和栈,你就把这篇文章甩给他

    栈:管程序如何运行的,程序如何执行,如何处理数据.(局部变量其实也是存在栈中的,引用数据类型在栈中存的是地址引用)(栈的空间就不需要那么大了) 堆:管数据存储的.(引用数据类型的存放,所以堆的空间是比 ...

  9. vue 多个回调_Vue 进阶面试必问,异步更新机制和 nextTick 原理

    vue已是目前国内前端web端三分天下之一,同时也作为本人主要技术栈之一,在日常使用中知其然也好奇着所以然,另外最近的社区涌现了一大票vue源码阅读类的文章,在下借这个机会从大家的文章和讨论中汲取了一 ...

最新文章

  1. 云答题微信小程序 实现 前端加后台管理
  2. 排名前 16 的 Java 工具类
  3. JavaScript 小知识点
  4. access开发精要(2)-参照完整性
  5. Android ANR视角InputDispatcher
  6. 卷积神经网络中的池化是什么意思
  7. Linux 操作系统开篇!
  8. 麦克纳姆轮辊子滚动速度分析
  9. X86-64和ARM64用户栈的结构 (1) ---背景介绍
  10. 64位计算机可以安装xp,64位xp系统如何安装【图解】
  11. u深度重装系统详细教程_U深度U盘安装原版win7系统的图文教程
  12. 如何从macOS Catalina向iPhone添加自定义铃声
  13. bugku之凯撒部长的奖励
  14. excel2021 打印圆不圆
  15. iOS swift MD5加密
  16. python调用bitly api出错
  17. SCRDet:Towards More Robust Detection for Small, Cluttered and Rotated Objects(摇杆旋转目标检测方法)
  18. 送快递到程序员,自学3年终于转行成功,薪资从5K涨到了12K
  19. 使用Libxml2操作XML文档
  20. 微信小程序如何使用解析 nbsp; lt; gt; amp; apos; ensp; emsp;等字符?

热门文章

  1. 2、计算机图形学——3D变换
  2. 计算相机采集帧率C实现
  3. php 模块指令,php artisan module常用命令
  4. sinee303a变频器说明书_SINEE--EM303A变频器用户手册.pdf
  5. Linux日常运维管理技巧
  6. linux下安装PHP的redis扩展
  7. UVALive5389 UVA414 POJ1493 ZOJ1339 Machined Surfaces
  8. cordova编译报错:Execution failed for task ':processDebugResources'
  9. linux bash基础
  10. kafka之四:Kafka集群搭建