Android Activity 启动流程 二:setContentView

news2024/11/14 15:13:56

关于作者: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


    @Override
    public void setContentView(int layoutResID) {
        根view 为空,则初始 mDecor view
        if (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 {
            
            // 将布局文件添加到 mContentParent
            mLayoutInflater.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 @Nullable
    View 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启动文章.


    @Override
    public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
        boolean isForward, String reason) {


        // 这个方法会调用 activity的 onResume 方法
        if (!performResumeActivity(r, finalStateRequest, reason)) {
            return;
        }
        
        final Activity a = r.activity;

        // window 未被添加进 windowmanager
        if (r.window == null && !a.mFinished && willBeVisible) {

            // window
            r.window = r.activity.getWindow();
    
            // decorView
            View 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 添加到 window
                wm.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();
    ...
    @Override
    public 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 things
            try {
				// //调用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

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    
    }
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

    final class TraversalRunnable implements Runnable {
        @Override
        public 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


    @Override
    public 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.coloradmin.cn/o/959660.html

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

相关文章

Docker切换文件系统为VFS

一、介绍 Docker支持AUFS、Btrfs、Device Mapper、OverlayFS、VFS、ZFS六种不同的存储驱动。 1. AUFS AUFS是一种常见的存储驱动程序,它也使用了Linux内核的AUFS文件系统。它的优点是支持所有的Linux发行版,可以在不同的容器之间共享文件系统&#xf…

重大工程建造云服务平台源码 SpringCloud+Vue

技术架构: 微服务JavaSpring Cloud VueUniApp MySql 开发语言:Java 开发工具:Idea 前端框架:Vue 后端框架:Spring Cloud 数 据 库:MySql 移 动 端:UniApp 系统端口:PC端&…

Vue2 集成 CodeMirror 实现公式编辑、块状文本编辑,TAG标签功能

效果图 安装codemirror依赖 本示例为Vue2项目&#xff0c;安装低版本的依赖 npm i codemirror5.65.12 npm i vue-codemirror4.0.6 实现 实现代码如下&#xff0c;里边涉及到的变量和函数自行替换即可&#xff0c;没有其他复杂逻辑。 <template><div class"p…

排盘程序算法探寻举例(陆先生八字)

算法实现&#xff1a; 1.庚生未月&#xff0c;燥土不能生金&#xff0c;日支申金为日主墙根&#xff0c;月干辛金比劫透出傍身&#xff0c;月干强。年干甲木自做寅木强根&#xff0c;又得月支乙木中气&#xff0c;甲木强旺有力&#xff0c;时干丙火七杀得未土余气&#xff0c;…

VMware Aria Operations SSH 身份验证绕过漏洞 (CVE-2023-34039)

zhi.oscs1024.com​​​​​ 漏洞类型身份验证不当发现时间2023-08-30漏洞等级严重MPS编号MPS-d9wr-56qmCVE编号CVE-2023-34039漏洞影响广度广 漏洞危害 OSCS 描述VMware Aria Operations for Networks 是 VMware 公司提供的一款网络可视性和分析工具&#xff0c;用于优化网络…

链表形式队列

&#x1f308;队列相关概念 1.储存了数据的节点从一端&#xff08;队尾&#xff09;进入队列&#xff08;入队尾插&#xff09;&#xff0c;从另一端&#xff08;队头&#xff09;出队列&#xff08;出队头删&#xff09;&#xff0c;先进先出。进行插入操作的一端称为队尾&am…

软件测试Day4|软件测试理论02

目录 6. 测试用例基础6.1 测试用例的定义6.2 测试用例要素6.3 测试用例设计和编写的作用 7. 黑盒测试用例设计方法7.1 用例设计方法分类7.2 测试数据选择7.2.1 等价类划分&#xff08;1&#xff09;等价类划分原理&#xff08;2&#xff09;确定等价类的原则&#xff08;3&…

群晖DS923+扩展ECC 64G内存

1 有必要上64G吗&#xff1f; 如果你不运行大型应用以及安装的套件不多&#xff0c;并且不使用虚拟机&#xff0c;确实没有太大必要。 但是大内存除了这些用处&#xff0c;还会被系统作为缓存使用。在资源监控中查看内存结构&#xff0c;虽然内存利用率只有4%&#xff0c;但缓存…

企业供应链数字化怎么做?企业数字化供应链流程落地方式

什么是供应链&#xff1f;简单来说&#xff0c;供应链是围绕客户需求&#xff0c;以提高产品流通各个环节的效率为目标&#xff0c;通过资源整合的方式来实现产品从设计、生产到销售、服务整个环节的组织形态。如同人工智能、区块链、5G等技术的发展带来的各种行业变化&#xf…

mov怎么改成mp4?跟我一起操作吧

mov怎么改成mp4&#xff1f;mov因为并不是一种常见的视频文件格式&#xff0c;因此大家对这种视频文件可能知道的并不多&#xff0c;但如果你是用的是苹果手机&#xff0c;那么你会发现苹果手机拍摄的视频转移到电脑上后就是mov格式的&#xff0c;因为mov格式的视频并没有受到大…

运算放大器典型应用(一)

这里写目录标题 一、反向比例运算电路怎么优化&#xff1f;平衡电阻的讨论 二、同向比例运算电路三、电压跟随器重要事项 四、加法运算电路反向加法同向加法 五、减法运算电路专用减法器 六、积分电路微分电路七、对数指数运算电路八、测量放大电路&#xff08;仪表放大电路&am…

低代码概念——初步认识低代码

随着数字化转型和软件需求的不断增长&#xff0c;传统的手写代码开发方式已经无法满足迅速推出应用程序的需求。为了加快软件开发的速度并降低技术门槛&#xff0c;低代码开发模式应运而生。 一、低代码的定义 低代码是一种软件开发方法&#xff0c;通过使用可视化编程工具和少…

webpack(三)loader

定义 loader用于对模块的源代码进行转换&#xff0c;在imporrt或加载模块时预处理文件 webpack做的事情&#xff0c;仅仅是分析出各种模块的依赖关系&#xff0c;然后形成资源列表&#xff0c;最终打包生成到指定文件中。 在webpack内部&#xff0c;任何文件都是模块&#x…

堆的 shift up(Java 实例代码)

目录 堆的 shift up Java 实例代码 src/runoob/heap/HeapShiftUp.java 文件代码&#xff1a; 堆的 shift up 本小节介绍如何向一个最大堆中添加元素&#xff0c;称为 shift up。 假设我们对下面的最大堆新加入一个元素52&#xff0c;放在数组的最后一位&#xff0c;52大于父…

蓝桥杯 2240. 买钢笔和铅笔的方案数c++解法

最近才回学校。在家学习的计划不翼而飞。但是回到学校了&#xff0c;还是没有找回状态。 现在是大三了&#xff0c;之前和同学聊天&#xff0c;说才大三无论是干什么&#xff0c;考研&#xff0c;找工作&#xff0c;考公&#xff0c;考证书 还都是来的及的。 但是心里面…

深度刨析数据在内存中的存储

✨博客主页&#xff1a;小钱编程成长记 &#x1f388;博客专栏&#xff1a;进阶C语言 深度刨析数据在内存中的存储 1.数据类型介绍1.1 类型的基本归类 2.整形在内存中的存储2.1 原码、反码、补码2.2 大小端介绍 3.浮点型在内存中的存储3.1 一个例子3.2 浮点数的存储规则3.3指数…

树和二叉树基础

引言&#xff1a; 树是一种非线性的结构&#xff0c;也是由一个一个的结点构成。 树的一些基本概念&#xff1a; 节点的度&#xff1a;一个节点含有的子树的个数称为该节点的度&#xff1b;如上图&#xff1a;A的度为6 叶节点或终端节点&#xff1a;度为0的节点称为叶节点。…

聊天机器人将取代人工客服?电商界的超级“贵人”

聊天机器人在全球范围内取得了成功&#xff0c;目前有58%的 B2B公司和42%的 B2C公司使用聊天机器人&#xff0c;而且这个数字预计还会继续增长。原因有很多&#xff0c;聊天机器人能够模拟人类交互并每天 24小时提供客户服务。当客户有疑问时&#xff0c;不用等上几小时才能得到…

【RabbitMQ】RabbitMQ 服务无法启动。系统出错。发生系统错误 1067。进程意外终止。

问题描述 RabbitMQ 服务无法启动。 rabbitmq-service.bat startRabbitMQ 服务正在启动 . RabbitMQ 服务无法启动。系统出错。发生系统错误 1067。进程意外终止。原因分析 RabbitMQ和Erlang版本不匹配。 解决方案 查询并安装RabbitMQ版本对应Erlang版本 https://www.rabbitm…

[ROS]yolov8部署ROS

Yolov8是一种基于PyTorch深度学习框架的轻量级目标检测算法&#xff0c;具有高效、准确和快速的特点&#xff0c;因此在机器人领域得到了广泛的应用。而ROS&#xff08;Robot Operating System&#xff09;是一个用于机器人软件开发的框架&#xff0c;提供了各种工具和库&#…