Android Activity 启动流程 二:setContentView

关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。
专注于分享各领域原创系列文章 ,擅长java后端、移动开发、商业变现、人工智能等,希望大家多多支持。

目录

  • 一、概览
  • 二、setContentView()
  • 三、inflate
  • 四、view的绘制展示
    • 4.1 Activity.onResume
    • 4.2 WindowManager addView
    • 4.3 ViewRootImpl
    • 4.4 addWindow & makeVisible
  • 五、 推荐阅读

在这里插入图片描述

接 - > 上 篇,Activity创建后,还只是调用了onCreate方法,页面并没有展示出来,还需要调用setContentView方法,加载页面布局,并进行渲染,最后展示。

一、概览

本源码基于Android 12
看代码前,我们先上一张Activity,Window, DecorView三者之间的关系图
在这里插入图片描述

DecorView是整个ViewTree的最顶层View,它是一个FrameLayout布局,代表了整个应用的界面。
在该布局下面,有标题view和内容view两个子元素。

Activity setContentView 核心就是PhoneWindow的setContentView方法,其主要干了两件事:
1.完成DecorView的创建与加载,这个DecorView会在后面onresume后添加到window中
2.将MainActivity的布局加载到DecorView内的一个ViewGroup中

创建DecorView,即installDecor方法,其内部用到了两个核心的方法:
1.generateDecor方法创建出DecorView对象
2.generateLayout方法完成这个DecorView对象的布局加载,并完成了MainActivity的父容器的赋值(即contentParent变量)

先上一张流程图
在这里插入图片描述

二、setContentView()

我们跟踪一下源码,看看这个方法是怎么做的

    public void setContentView(View view) {getWindow().setContentView(view);initWindowDecorActionBar();}

这里window即为 PhoneWindow,
window的初始化是在 Acticity 创建的时候初始化, 在Acticity对象创建后,会调用attach方法

@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)final void attach(Context context, ActivityThread aThread, ...) {attachBaseContext(context);mFragments.attachHost(null /*parent*/);mWindow = new PhoneWindow(this, window, activityConfigCallback);mWindow.setWindowControllerCallback(mWindowControllerCallback);mWindow.setCallback(this);mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);}

PhoneWindow.java

@Overridepublic void setContentView(int layoutResID) {根view 为空,则初始 mDecor viewif (mContentParent == null) {installDecor();} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {mContentParent.removeAllViews();}if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,getContext());transitionTo(newScene);} else {// 将布局文件添加到 mContentParentmLayoutInflater.inflate(layoutResID, mContentParent);}mContentParent.requestApplyInsets();final Callback cb = getCallback();if (cb != null && !isDestroyed()) {cb.onContentChanged();}mContentParentExplicitlySet = true;}调用installDecor()进行DecorView的初始化private void installDecor() {mForceDecorInstall = false;if (mDecor == null) {// 创建出一个DecorView并返回mDecor = generateDecor(-1);} else {mDecor.setWindow(this);}if (mContentParent == null) {//对mContentParent进行赋值,作为Activity布局的父容器,mContentParent = generateLayout(mDecor);}}

首先判断了mContentParent是否为null,如果为空则执行installDecor()方法,同时初始化一个mContentParent,这个就是Activity布局的父容器

三、inflate

LayoutInflater.java

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {final Resources res = getContext().getResources();View view = tryInflatePrecompiled(resource, res, root, attachToRoot);if (view != null) {return view;}XmlResourceParser parser = res.getLayout(resource);try {return inflate(parser, root, attachToRoot);} finally {}}
private @NullableView tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root, boolean attachToRoot) {try {Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader);Method inflater = clazz.getMethod(layout, Context.class, int.class);View view = (View) inflater.invoke(null, mContext, resource);if (view != null && root != null) {XmlResourceParser parser = res.getLayout(resource);try {AttributeSet attrs = Xml.asAttributeSet(parser);advanceToRootNode(parser);ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);if (attachToRoot) {root.addView(view, params);} else {view.setLayoutParams(params);}} finally {parser.close();}}return view;} catch (Throwable e) {} finally {}return null;}

布局就是这么添加进mContentParent中的。
在这里插入图片描述

但是,view还是没有显示出来的,此时代码所做的事情仅仅只是加载了布局,并没有开始view的测量、布局、绘制工作。
对应方法是onMeasure, onLayout, onDraw,这些操作在后面

四、view的绘制展示

每一个Activity组件都有一个关联的Window对象,用来描述一个应用程序窗口。每一个应用程序窗口内部又包含一个View对象,用来描述应用程序窗口的视图。
我们再看下图:
在这里插入图片描述

Activity#onResume()之后才是布局由不可见变为可见的,我们看源码

4.1 Activity.onResume

ActivityThread.java
下面这个方法是在Activity onCreate创建后调用的,handleResumeActivity,不清楚的可以看前面app启动文章.

@Overridepublic void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,boolean isForward, String reason) {// 这个方法会调用 activity的 onResume 方法if (!performResumeActivity(r, finalStateRequest, reason)) {return;}final Activity a = r.activity;// window 未被添加进 windowmanagerif (r.window == null && !a.mFinished && willBeVisible) {// windowr.window = r.activity.getWindow();// decorViewView decor = r.window.getDecorView();decor.setVisibility(View.INVISIBLE);ViewManager wm = a.getWindowManager();WindowManager.LayoutParams l = r.window.getAttributes();a.mDecor = decor;} else if (!willBeVisible){}if (a.mVisibleFromClient) {if (!a.mWindowAdded) {// DecorView 添加到 windowwm.addView(decor, l);} else {a.onWindowAttributesChanged(l);}}if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {使布局可见if (r.activity.mVisibleFromClient) {r.activity.makeVisible();}}r.nextIdle = mNewActivities;}

在上面的代码中,会先调用Activity的onResume, 然后再是view的绘制,最后将DecorView 设置 可见;

4.2 WindowManager addView

WindowManagerImpl.java

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();...@Overridepublic void addView(View view, ViewGroup.LayoutParams params) {mGlobal.addView(view, params, mDisplay, mParentWindow);}

这里也是一个空壳代码,调用WindowManagerGlobal

WindowManagerGlobal.java

    public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId) {final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;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);// do this last because it fires off messages to start doing thingstry {// //调用ViewRootImpl.setView方法,把DecorView作为参数传递进去root.setView(view, wparams, panelParentView, userId);} catch (RuntimeException e) {// BadTokenException or InvalidDisplayException, clean up.if (index >= 0) {removeViewLocked(index, true);}throw e;}}}

在方法内部,会通过跨进程方式向WMS(WindowManagerService)发起一个调用,从而将DecorView最终加到Window上,在这个过程中,ViewRootImpl、DecorView和WMS会彼此关联。
最后,WMS调用ViewRootImpl.performTraversals 方法开始View的测量、布局、绘制。

4.3 ViewRootImpl

一个 Window 对应着一个 ViewRootImpl 和 一个 VIew。这个 View 就是被 ViewRootImpl 操作的.

从上面代码,我们可以看到,ViewRootImpl的初始化是在WindowManagerGlobal的addView中

ViewRootImpl.java


/*** We have one child*/public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {synchronized (this) {if (mView == null) {mView = view;mAdded = true;int res; /* = WindowManagerImpl.ADD_OKAY; */// 刷新布局的操作,触发view的measure -> layout -> draw 操作requestLayout();try {//将 View 添加到 WMS 中res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes, ...);} catch (RemoteException e) {} finally {}// Set up the input pipeline. 设置了一系列的输入通道CharSequence counterSuffix = attrs.getTitle();mSyntheticInputStage = new SyntheticInputStage();InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);}}}

首先会调用requestLayout方法来刷新布局,然后将 View 添加到 WMS 中,最后是view事件的处理;
view事件的处理,最后还是会回到了 PhoneWindow 中的 DecorView 来处理,剩下的就是从 DecorView 开始将事件层层传递给内部的子 View 中了
这里就不展开

ViewGroup.java@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {}
    @Overridepublic void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {checkThread();mLayoutRequested = true;scheduleTraversals();}}final class TraversalRunnable implements Runnable {@Overridepublic void run() {doTraversal();}}void doTraversal() {if (mTraversalScheduled) {performTraversals();}}

requestLayout()最终会调用到performTraversals,在这个方法中会调用 View 的 measure() ,layout() ,draw() 方法。
我们看下面源码


private void performTraversals() {final View host = mView;if (mFirst || windowShouldResize || viewVisibilityChanged || params != null|| mForceNextWindowRelayout) {try {if (!mPendingMergedConfiguration.equals(mLastReportedMergedConfiguration)) {performConfigurationChange(new MergedConfiguration(mPendingMergedConfiguration),!mFirst, INVALID_DISPLAY /* same display */);}} catch (RemoteException e) {}if (!mStopped || wasReportNextDraw) {//View 的测量performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);if (measureAgain) {//View 的测量performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);}layoutRequested = true;}}} else {}if (didLayout) {// View 的布局performLayout(lp, mWidth, mHeight);}if (!cancelDraw) {// View 的绘制performDraw();} else {}mIsInTraversal = false;
}

4.4 addWindow & makeVisible

com.android.server.wm.Session.java

@Overridepublic int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities,InputChannel outInputChannel, InsetsState outInsetsState,InsetsSourceControl[] outActiveControls) {return mService.addWindow(this, window, attrs, viewVisibility, displayId,UserHandle.getUserId(mUid), requestedVisibilities, outInputChannel, outInsetsState,outActiveControls);}

Activity.java

DecorView的状态设置为可见,那么布局也就可见了void makeVisible() {if (!mWindowAdded) {ViewManager wm = getWindowManager();wm.addView(mDecor, getWindow().getAttributes());mWindowAdded = true;}mDecor.setVisibility(View.VISIBLE);}

五、 推荐阅读

Java 专栏

SQL 专栏

数据结构与算法

Android学习专栏

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/62341.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

阿桂天山的技术小结:Flask+UEditor实现图片文件上传富文本编辑

话不多说,有图有源码 先看效果: 1.前端html页面index.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><script src"{{ url_for(static,filenameueditor/ueditor.config.js) }}"></script…

数学建模:灰色关联分析

&#x1f506; 文章首发于我的个人博客&#xff1a;欢迎大佬们来逛逛 灰色关联分析法 算法流程 建立一个m行 n列的矩阵 X X X &#xff0c;其中 m 表示评价对象&#xff0c; n表示评价指标首先进行矩阵的归一化&#xff0c;得到归一化后的矩阵 d a t a data data获取参考向…

说说Flink中的State

分析&回答 基本类型划分 在Flink中&#xff0c;按照基本类型&#xff0c;对State做了以下两类的划分&#xff1a; Keyed State&#xff0c;和Key有关的状态类型&#xff0c;它只能被基于KeyedStream之上的操作&#xff0c;方法所使用。我们可以从逻辑上理解这种状态是一…

[第七届蓝帽杯全国大学生网络安全技能大赛 蓝帽杯 2023]——Web方向部分题 详细Writeup

Web LovePHP 你真的熟悉PHP吗&#xff1f; 源码如下 <?php class Saferman{public $check True;public function __destruct(){if($this->check True){file($_GET[secret]);}}public function __wakeup(){$this->checkFalse;} } if(isset($_GET[my_secret.flag]…

【Java Web】敏感词过滤

一、前缀树 假设有敏感词&#xff1a;b&#xff0c;abc&#xff0c;abd&#xff0c;bcd&#xff0c;abcd&#xff0c;efg&#xff0c;hii 那么前缀树可以构造为&#xff1a; 二、敏感词过滤器 package com.nowcoder.community.util;import org.apache.commons.lang3.CharUt…

全网首发!大众宝来高尔夫polo领驭迈腾帕萨特奥迪A4A6B6B7等老车机增加带蓝牙控制的AUX解码模块,支持小程序原车按钮控制,支持外接高品质蓝牙模块

文章目录 前言1、设计指标2、PCB设计3、程序设计4、调试4.1蓝牙控制AUX解码板4.2自定义车机按钮控制其他高品质蓝牙音频模块4.3小程序使用 5、模块与车机连接方法6、结语 前言 ​ 之前写过四篇关于车机增加音频输入的方法。 1、07宝来经典车机CD收音机&#xff08;RC668&…

【Python】python使用docxtpl生成word模板

python使用docxtpl生成word模板 python-docxtpl包简单使用和实战&#xff0c;Python处理word&#xff0c;docx文件。 最近需要处理一些爬虫得到的数据来进行一些自动化报告的操作&#xff0c;因为需要生成的是word的报告&#xff0c;所以估选用docxtpl库来直接生成模板 docxt…

前端需要理解的数据治理与异常监控知识

服务监控包括错误监控、性能监控和行为监控。数据埋点是对服务监控中收集用户信息的技术实现&#xff0c;分为侵入式和非侵入式。 1 数据治理 前端数据治理的重要指标是准确性和数据&#xff0c;一个数据对象包括数据值和其他元数据。 2 数据上报方式 2.1 Image 通过将采集…

nodejs发布静态https服务器

1、先用 npm init 创建一个package.json&#xff0c;然后添加依赖 node-static &#xff0c;package.json 如下&#xff1a; {"name": "freeswitch","version": "1.0.0","description": "test freeswitch for webrtc&…

20种数据相似性计算方法

不同的相似性计算方法适用于不同类型的数据和问题。在选择相似性计算方法时&#xff0c;需根据数据的特性、问题的定义以及所关注的数据特点来做出合适的选择。 本文归纳了20种数据相似性计算方法以及它们的特点和适用场景&#xff0c;并给出了参考python实现。 相似性计算方法…

windows10默认浏览器总是自动更改为Edge浏览器

在设置的默认应用设置中把默认浏览器改为chrome或其他之后他自动又会改回Edge。不得不说*软真的狗。 解决办法&#xff1a; 后来发现在Edge浏览器的设置中有这么一个选项&#xff0c;会很无耻的默认是Edge。把它关掉后重新设置就行了。

NPM 常用命令(一)

目录 1、npm 1.1 简介 1.2 依赖性 1.3 安装方式 2、npm access 2.1 命令描述 2.2 详情 3、npm adduser 3.1 描述 4、npm audit 4.1 简介 4.2 审计签名 4.3 操作示例 4.4 配置 audit-level dry-run force json package-lock-only omit foreground-scripts …

软考-中级-软件设计师之路

前言&#xff1a;两个月时间复习软考基本知识&#xff0c;下面做知识总结。 1. 计算机概论 第1章主要介绍计算机系统基础知识、计算机体系结构以及安全性、可靠性和系统性能评测基础。 2. 程序设计语言基础知识 第2章主要介绍程序设计语言的基本概念与基本成分&#xff0c;…

从项目中突显技能:在面试中讲述你的编程故事

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

mongodb建用户

玛德折腾了2个小时&#xff0c;、mongodb 建用户。艹 [rootk8-master mongodb]# cat docker-compose.yaml version: 2 services: mongodb: container_name: mongodb_2.0 image: mongo:4.4 restart: always environment: TZ: Asia/Shanghai MONGO_INITDB_ROOT_USERNAME: admin M…

【飞书ChatGPT机器人】飞书接入ChatGPT,打造智能问答助手

文章目录 前言环境列表1.飞书设置2.克隆feishu-chatgpt项目3.配置config.yaml文件4.运行feishu-chatgpt项目5.安装cpolar内网穿透6.固定公网地址7.机器人权限配置8.创建版本9.创建测试企业10. 机器人测试 前言 在飞书中创建chatGPT机器人并且对话&#xff0c;在下面操作步骤中…

reduxreact-redux

redux redux组成部分&#xff1a;state,action,reducer,store store主要职责&#xff1a; 维持应用的state 提供getState()方法获取state 提供dispatch()方法发送action 通过subscribe()来注册监听 通过subscribe()返回值来注销监听 用法&#xff1a; action:必须要有return返…

TDengine函数大全-字符串函数

以下内容来自 TDengine 官方文档 及 GitHub 内容 。 以下所有示例基于 TDengine 3.1.0.3 TDengine函数大全 1.数学函数 2.字符串函数 3.转换函数 4.时间和日期函数 5.聚合函数 6.选择函数 7.时序数据库特有函数 8.系统函数 字符串函数 TDengine函数大全CHAR_LENGTHCONCATCONCA…

结构体的简单介绍

目录 概念&#xff1a; 与数组类比&#xff1a; 结构体声明&#xff1a; 注意&#xff1a; 结构体变量、全局变量、局部变量&#xff1a; 结构体声明中包含其他结构体变量&#xff1a; 结构体变量的初始化&#xff1a; 包含了其他结构体变量的初始化&#xff1a; 结构体…

SPI2外设驱动-W25Q64 SPI接口初始化

前言 &#xff08;1&#xff09;本系列是基于STM32的项目笔记&#xff0c;内容涵盖了STM32各种外设的使用&#xff0c;由浅入深。 &#xff08;2&#xff09;小编使用的单片机是STM32F105RCT6&#xff0c;项目笔记基于小编的实际项目&#xff0c;但是博客中的内容适用于各种单片…