View绘制流程-Window创建

news2024/11/24 16:37:33

前言:

View绘制流程中,主要流程是这样的:

1.用户进入页面,首先创建和绑定Window;

2.首次创建以及后续vsync信号来临时,会请求执行刷新流程;

3.刷新流程完成后,会通知SurfaceFlinger读取数据以及刷新页面。

本篇就是大流程中的第一个环节,重点讲解进入页面后,Window是如何创建以及绑定到系统侧的。

本文的流程主要分为以下三大块:

1.APP侧window和布局的创建流程;

2.APP侧window是如何绑定ViewRootImpl以及注册到系统侧的;

3.系统侧接收到window后,是如何处理的。

一.APP侧Window和View创建

1.1 创建Window

Activity启动时,会经历performLaunchActivity和handleResumeActivity的流程,而window的创建以及decorView的创建,就是在launch的过程中。

我们首先看一下ActivityThread.performLaunchActivity中的代码:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    //Activity的创建
    Activity activity = null;
    activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
    //Activity的关联
    activity.attach();
    //执行Activity的onCreate流程
    mInstrumentation.callActivityOnCreate()
    ...
}

我们看一下activity.attach中实现的相关内容:

//android.app.Activity
final void attach(){
    //1
    mWindow = new PhoneWindow();
    //3
    mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),...);
    mWindow.setColorMode(info.colorMode);
}


//com.android.internal.policy.PhoneWindow.java
public PhoneWindow(@UiContext Context context) {
    public PhoneWindow(@UiContext Context context) {
        super(context);
        mLayoutInflater = LayoutInflater.from(context);
    }
}

主要执行了以下的逻辑:

1.创建了Activity所绑定的Window,成员名为mWindow,类型为PhoneWindow。

2.在PhoneWindow中,mLayoutInflater赋值。我们的布局就是通过mLayoutInflater对象去解析的。

3.给Window对象绑定WindowManager,这个WindowManager实际上是WindowManagerImpl。

所以此时,Activity中的mWindow,以及PhoneWindow中的mWindowManager和mLayoutInflater都已经有值了。

1.2 DecorView和ContentParent创建

接下来,我们看下callActivityOnCreate的流程。Activity.onCreate流程没有什么有关window的逻辑,但是一般我们都会在onCreate中调用setContentView,这个方法中却大有玄机,我们一起看一下:

//android.app.Window.java
public void setContentView(@LayoutRes int layoutResID) {
    //1
    getWindow().setContentView(layoutResID);
}

//com.android.internal.policy.PhoneWindow
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        //2
        installDecor();
    }
    ...
    //3
    mLayoutInflater.inflate(layoutResID, mContentParent);
}

private void installDecor() {
    if(mDecor == null){
        mDecor = generateDecor(-1);
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);
    }
}

主要执行了以下的逻辑:

1.调用Activity中所持有window去加载layout。

2.首次的时候,通过installDecor方法去创建根布局DecorView以及容器布局mContentParent。mContentParent是Activity上所有的View的父容器。

3.通过mLayoutInflater对象去解析生成布局对象,并且关联到mContentParent上。

具体的解析逻辑不是本文的核心,这里就不去细讲了。

1.3 小结

至此,Activity的创建和其onCreate的流程已经结束,此时Activity中的成员变量mWindowManager和mWindow对象已经完成了赋值,总结一下,如下图所示:

 

二.APP侧Window注册

2.1 Activity和ActivityClientRecord中成员变量赋值

在第一章中,Activity所对应的window及其中的布局创建完成了,所以下一步,就是需要把这个window向系统做一个绑定,这个流程,主要是在Activity的onResume周期中执行的。

首先,我们仍然看一下resume周期所对应的代码,如下:

//ActivityThread.java
public void handleResumeActivity(ActivityClientRecord r, ...) {
    final Activity a = r.activity;
    ...
    if (r.window == null && !a.mFinished && willBeVisible) {
        r.window = r.activity.getWindow();
        ...
        //1
        a.mDecor = decor;
        //2
        l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
        //3
        wm.addView(decor, l);
    }
}

主要执行了以下的逻辑:

1.把window中的decor赋值给Activity中的mDecor;

2.设置Window.LayoutParams的type类型为WindowManager.LayoutParams.TYPE_BASE_APPLICATION;在安卓中,type决定window图层优先级,值越大优先级越高,部分图层优先级如下:

public static final int TYPE_BASE_APPLICATION   = 1;//默认Activity对应的图层
public static final int FIRST_SYSTEM_WINDOW     = 2000;//系统弹窗的图层
public static final int TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5;//Toast的图层
public static final int TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6;//悬浮窗的图层等级
public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;//同上,用于替代上面那个

3.通过windowManager添加decor。这里wm的对象,实际上是WindowManagerImpl,而其中的addView方法中,又交给了WindowManagerGlobal来处理,相关代码如下:

//WindowManagerImpl.java
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    mGlobal.addView(view, params,...);
}

2.2 WindowManagerGlobal装载Window

接下来,我们看一下WindowManagerGlobal.addView()中的逻辑。

public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId) {
    ViewRootImpl root;
    View panelParentView = null;
    ...
    //1
    if (windowlessSession == null) {
        root = new ViewRootImpl(view.getContext(), display);
    } else {
        root = new ViewRootImpl(view.getContext(), display, windowlessSession);
    }
    view.setLayoutParams(wparams);
    //2
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);
    //3
    root.setView(view, wparams, panelParentView, userId);   
}

主要执行了以下的逻辑:

1.创建ViewRootImpl,ViewRootImpl的角色是页面刷新显示流程的执行者。

2.WindowManagerGlobal的角色是维护客户端所有的页面的,所以自然而然的,其中就维护了很多集合。比如存储所有根布局的mView对象等等,这里就是往集合中注册的。

@UnsupportedAppUsage
private final ArrayList<View> mViews = new ArrayList<View>();
@UnsupportedAppUsage
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
@UnsupportedAppUsage
private final ArrayList<WindowManager.LayoutParams> mParams =
        new ArrayList<WindowManager.LayoutParams>();

3.上面说到,ViewRootImpl是流程的具体执行者,那么window的绑定自然也是交给其来处理。所以这里通过ViewRootImpl.setView方法来负责。

2.3 ViewRootImpl负责视图的绑定

接下来,我们就看下ViewRootImpl的setView()方法。

ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {
    ...
    if (mView == null) {
        mView = view;
        ...
        //1
        requestLayout();
        //2
        InputChannel inputChannel = null;
        if ((mWindowAttributes.inputFeatures
                & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
            inputChannel = new InputChannel();
        }
        //3
        res = mWindowSession.addToDisplayAsUser(...);
    }
    //
    mInputEventReceiver = new WindowInputEventReceiver(inputChannel,Looper.myLooper());
}

这一块的逻辑也是较为清晰的:

首先,通过requestLayout方法尝试进行首次View绘制的完整流程,虽然这时window还没有绑定上,但是并不影响View流程的开始,毕竟View流程中,只有最后的绘制流程才需要和SurfaceFlinger进行交互。

然后,生成InputChannel对象,这个对象类似于一个回调,通过后面的binder接口传递给系统侧。后面window上的点击事件,就会通过InputChannel回调通知到应用侧。后面把inputChannel绑定到WindowInputEventReceiver中,所以APP侧点击事件的来源,就是其中的onInputEvent方法。

最后,把相关的对象传递给系统侧,完成注册。传递的内容如下:

int addToDisplayAsUser(IWindow window, in WindowManager.LayoutParams attrs,
        in int viewVisibility, in int layerStackId, in int userId,
        in InsetsVisibilities requestedVisibilities, out InputChannel outInputChannel,
        out InsetsState insetsState, out InsetsSourceControl[] activeControls);

int addToDisplayAsUser(IWindow window, in WindowManager.LayoutParams attrs, in int viewVisibility, in int layerStackId, in int userId, in InsetsVisibilities requestedVisibilities, out InputChannel outInputChannel, out InsetsState insetsState, out InsetsSourceControl[] activeControls);

整个流程如下图所示:

给系统侧传递的数据列表如下:

对象类型

成员变量名

解释

IWindow

window

对应ViewRootImpl中的IWindow.Stub,传递的一个binder对象

WindowManager.LayoutParams

attrs

window对应的layoutParams属性

int

viewVisibility

根布局的显示状态

int

layerStackId

displayId,显示区域的唯一ID

int

userId

应用的userId

InsetsVisibilities

requestedVisibilities

InputChannel

outInputChannel

事件分发流程中,传递的通道

InsetsState

insetsState

InsetsSourceControl

activeControls

三.系统侧Window绑定

介绍系统侧的流程前,我们先对系统侧的几个核心类简单介绍下,因为大多数的读者对于系统侧的了解较少。

3.1 核心类介绍

类名

功能介绍

com.android.server.wm.Session

一个session对应一个应用进程,负责应用和系统之间的窗口注册/移除,SurfaceSession注册等等。

SurfaceSession

这个类注册是native的实现。负责维护应用和surfaceFlinger之间的连接。所以,APP刷新时是直接通知SF,并不需要经过system_server。

WindowManagerService

顾名思义,用于所有应用的窗口管理。这里只是维护窗口的关系,并负责具体的渲染流程。

3.2 应用进程绑定唯一的IWindowSession

上一章有讲到,一个应用会有一个维护所有的视图的容器WindowManagerGlobal,那么它其中,一定有一个负责和系统侧通信的对象,这个对象就是IWindowSession。相关代码如下:

//WindowManagerGlobal.java
public static IWindowSession getWindowSession() {
    if (sWindowSession == null) {
        IWindowManager windowManager = getWindowManagerService();
        sWindowSession = windowManager.openSession(...);
    }
}

//WindowManagerService.java
@Override
public IWindowSession openSession(IWindowSessionCallback callback) {
    return new Session(this, callback);
}

也就是说,WindowManagerGlobal中只会持有一个sWindowSession对象,而WindowManagerGlobal对应一个应用的进程,所以IWindowSession是绑定唯一一个应用进程的。IWindowSession是一个binder的引用,其在系统侧的具体实现是Session。上面的addToDisplayAsUser方法,就是通过IWindowSession中提供的binder方法。

3.3 把window注册到系统侧

接下来我们就看一下第二中讲到的addToDisplayAsUser()方法,它负责把应用侧的Window向系统侧注册。我们看一下其在系统侧的实现:

//Session.java
class Session extends IWindowSession.Stub{
    public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {
        return mService.addWindow(this, ...);
    }
}

逻辑很简单,直接交给WindowServiceManger的addWindow方法去处理,接下来我们就看下这个方法:

//WindowManagerService.java
public int addWindow(Session session, ...) {
    WindowState parentWindow = null;
    ...
    //1
    final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);
    ...
    //2
    WindowToken token = displayContent.getWindowToken(hasParent ? parentWindow.mAttrs.token : attrs.token);
    ...
    if (token == null) {
        if( hasParent ){
            token = parentWindow.mToken;
        } else if (){
            token = new WindowToken.Builder(this, binder, type)
        } else {
            token = new WindowToken.Builder(this, binder, type)
        }
        ...
    }
    ...
    //3
    final WindowState win = new WindowState(this, session, );
    ...
    if  (openInputChannels) {
        win.openInputChannel(outInputChannel);
    }
    ...
    //4
    win.openInputChannel(outInputChannel);
    ...
    //5
    win.attach();
    win.initAppOpsState();
    ...
    win.mToken.addWindow(win);
}

首先,根据displayId找到归属的DisplayContent,DisplayContent的作用是用于跟踪一系列的WindowState;

然后,如果当前的window存在parent,则去查询其parent的WindowToken。WindowToken顾名思义,用于识别WindowState;

接下来,生成WindowState,这里的WindowState和APP侧的Window是对应的,WindowState就是在系统侧window的描述并负责和window进行通讯;

然后,绑定事件输入,这里的outInputChannel就是APP侧传递过来的。

最后,通过attch()方法完成绑定,我们重点看一下这个方法:

//WindowState.java
void attach() {
    if (DEBUG) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
    mSession.windowAddedLocked();
}

//Session.java
void windowAddedLocked() {
    if (mPackageName == null) {
        mPackageName = wpc.mInfo.packageName;
    }
    if (mSurfaceSession == null) {
        mSurfaceSession = new SurfaceSession();
        ...
        mService.mSessions.add(this);
    }
    mNumWindow++;
}

简单来说,一个应用首次完成window.attch()的时候,初始化mPackageName和mSurfaceSession()。

而mSurfaceSession对应的就是显示在前台的区域,它初始化后,对应的就是native创建surface以及后续和surfaceFlinger交互的流程了,这个我们后面的文章来讲解。

最后使用mNumWindow记录Window的数量。

3.4 小结

我们仍然做一个小的总结,window注册在系统侧的实现。其实就是接受一个客户端传递过来的binder引用对象IWindow,然后生成一个唯一的对应对象WindowState。并且在应用进程级别生成一个SurfaceSession去维护应用的ViewRootImpl和surfaceFlinger的关系。流程图如下:

 

四.总结

最后,我们做一下总结,整个window的注册流程主要分为三块大块:

1.create流程主要是各种对象的初始化。流程中完成客户端window的创建以及mDecor,mContentParent等相关成员变量的初始化;

2.resume流程主要是window关系的维护。所以创建视图处理类ViewRootImpl,并且使用其把window向系统侧申请绑定;

3.系统收到后主要是生成window在系统侧的对象并记录。所以分别创建Window组的对象WindowToken和Window的系统侧对象WindowState并保存。

整体流程图如下:

 

五.扩展性问题

1.如果onCreate中不调用setContentView,那么会执行后面的流程吗?

答:会的,即使不调用setContentView,只是不会有ContentView,但是DecorView仍然会创建和绑定的,只不过这时候展示的会是黑屏。

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

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

相关文章

React源码解析18(1)------ React.createElement 和 jsx

1.React.createElement 我们知道在React17版本之前&#xff0c;我们在项目中是一定需要引入react的。 import React from “react” 即便我们有时候没有使用到React&#xff0c;也需要引入。原因是什么呢&#xff1f; 在React项目中&#xff0c;如果我们使用了模板语法JSX&am…

计算机网络实验1:网络命令学习

文章目录 1. 主要教学内容2. ping命令2.1 发送ping测试报文2.2 检测TCP/IP 3. tracert命令4. ipconfig命令5. netstat命令6. arp命令7. net命令8. netsh命令 1. 主要教学内容 实验内容&#xff1a;学习ping、tracert、ipconfig等常用的网络命令。所需学时&#xff1a;1。重难点…

数据结构-栈(C语言简单实现)

简介 栈是一种数据结构栈可以用来存放数字一次只能向栈里加入一个数字&#xff0c;一次也只能从栈里获得一个数字栈里到的数字有前后顺序&#xff0c;先进入到的数字在前&#xff0c;后进入的数字在后每次从栈里获取的数字一定是最后面的数字&#xff0c;最后获取的数字一定是…

使用svg生成图像

使用svg生成图像 每个HTML开发人员都应该对可伸缩的向量图形有一个基本的理解。本文会通过使用svg创建一个雨伞图像来介绍一下svg的基本知识。 svg介绍 SVG 意为可缩放矢量图形&#xff08;Scalable Vector Graphics&#xff09;。是一种可以在HTML中创建图像的方式。 我们…

Java # Spring(2)

一、Spring事物 一、分类 编程式事物&#xff1a;代码中硬编码&#xff08;不推荐使用&#xff09; 声明式事物&#xff1a;配置文件中配置&#xff08;推荐使用&#xff09; 分类&#xff1a; 基于xml的声明式事物基于注解的声明式事物 二、隔离级别 ISOLATION_DEFAULT&…

navicat连接postgresql报错

navicat连接postgresql报错 navicat连接postgresql报错 现象 有小伙伴告诉我 安装了新的postgresql 使用navicat连接&#xff0c;报错 ERROR: column "datlastsysoid" does not existLINE 1: SELECT DISTINCT datlastsysoid FROM pg database column “datlastsy…

电源控制--条件稳定

控制系统的条件稳定是指系统在一定条件下能够保持稳定性的特性。稳定性是控制系统设计中非常重要的概念&#xff0c;它涉及系统的输出在时间上是否趋向于有限值或者周期性变化&#xff0c;而不是无限增长或发散。 在控制系统中&#xff0c;条件稳定的要求通常涉及到以下几个方…

可靠传输-选择重传协议SR

一个数据分组的误码就会导致其后续多个数据分组不能被接收方按序接收而丢弃 (尽管它们无乱序和误码)。这必然会造成发送方对这些数据分组的超时重传&#xff0c;显然这是对通信资源的极大费。 为了进一步提高性能&#xff0c;可设法只重传出现误码的数据分组。因此&#xff0c;…

陈述式资源管理方法

陈述式资源管理方法&#xff1a; 1.kubernetes 集群管理集群资源的唯一入口是通过相应的方法调用 apiserver 的接口 2.kubectl 是官方的CLI命令行工具&#xff0c;用于与 apiserver 进行通信&#xff0c;将用户在命令行输入的命令&#xff0c;组织并转化为 apiserver 能识别的…

安卓如何卸载应用

卸载系统应用 首先需要打开手机的开发者选项&#xff0c;启动usb调试。 第二步需要在电脑上安装adb命令&#xff0c;喜欢的话还可以将它加入系统path。如果不知道怎么安装&#xff0c;可以从这里下载免安装版本。 第三步将手机与电脑用数据线连接&#xff0c;注意是数据线&a…

AcWing算法提高课-1.3.17背包问题求具体方案

宣传一下算法提高课整理 <— CSDN个人主页&#xff1a;更好的阅读体验 <— 本题链接&#xff08;AcWing&#xff09; 点这里 题目描述 有 N N N 件物品和一个容量是 V V V 的背包。每件物品只能使用一次。 第 i i i 件物品的体积是 v i v_i vi​&#xff0c;价值…

Linux配置QT Creator环境:ubuntu中安装QT Creator环境

一、前景 目前市面上很多公司使用QT Creator进行界面开发&#xff0c;基本都会选择在Linux环境进行&#xff0c;优点不仅是市场所需&#xff0c;更是方便后期代码的移植&#xff0c;相较于Windows系统&#xff0c;Linux系统移植性非常好。故此篇文章&#xff0c;介绍如何在Linu…

Android 视频播放器dkplayer

列表播放如图所示&#xff1a; 一、依赖 //添加RecyclerView的依赖包implementation androidx.recyclerview:recyclerview:1.2.1// 异步加载图片依赖implementation com.squareup.picasso:picasso:2.5.2// 上拉刷新、下来加载依赖implementation com.scwang.smartrefresh:Smart…

Linux命令200例:mkdir用于创建目录(常用)

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌。CSDN专家博主&#xff0c;阿里云社区专家博主&#xff0c;2023年6月csdn上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &…

centos linux 安装RDMA Soft-RoCE|虚拟机安装Soft-RoCE

什么是Soft-RoCE softRoCE的目标是在所有支持以太网的设备上都可以部署RDMA传输&#xff0c;可以使不具备RoCE能力的硬件和支持RoCE的硬件间进行基于IB语义的交流。 大白话就是模拟RDMA的软件栈&#xff0c;使得在没有RDMA网卡的环境上&#xff0c;也可以运行基于RDMA写的传输…

jenkins流水线

1.拉取代码 https://gitee.com/Wjc_project/yygh-parent.git2、项目编译 mvn clean package -Dmaven.test.skiptrue ls hospital-manage/target3、构建镜像 ls hospital-manage/target docker build -t hospital-manage:latest -f hospital-manage/Dockerfile ./hospital-ma…

ASL国产CS5213 转VGA信号输出音频 替代AG6200安格芯片 HDMI to VGA(带音频)方案设计原理图

CS5213功能&#xff1a;HDMI转VGA带音频输出&#xff0c;专注于设计HDMI转VGA带音频输出。可替代AG6200 AG6201。 CS5213芯片是一个HDMI&#xff08;高清多媒体接口&#xff09;到VGA桥接芯片。 它将HDMI信号转换为标准VGA信号它可以在适配器、智能电缆等设备中设计。 Capst…

用C语言实现定积分计算(包括无穷积分/可自定义精度)

关于严谨性的声明&#xff1a; 在用C语言进行定积分的计算之前&#xff0c;我需要声明以下几点&#xff1a; 一、我们所进行定积分计算的函数都是应当是黎曼可积的&#xff0c;这保证了我们即使均匀地分割区间也保证了积分的收敛性。 二、我们同时还应该认识到&#xff0c;鉴…

一文详解 DolphinDB SQL 标准化

为了提升用户体验&#xff0c;降低用户学习成本和脚本迁移复杂度&#xff0c;自 1.30.17 / 2.00.5 版本开始&#xff0c;DolphinDB 逐步支持了标准化 SQL 的书写方法&#xff1b;并于 1.30.22 / 2.00.10 版本起&#xff0c;对标准 SQL 的常用语法和关键字实现了兼容。 1. 与标…

06-5_Qt 5.9 C++开发指南_Splash 与登录窗口(MouseEvent鼠标事件;注册表;加密)

文章目录 1. 实例功能概述2. 对话框界面设计和类定义3. QDIgLogin 类功能实现3.1 构造函数里的初始化3.2 应用程序设置的存储3.3 字符串加密3.4 用户名和密码输入判断3.5 窗口拖动功能的实现 4. Splash 登录窗口的使用5. 框架及源码5.1 可视化UI设计5.2 qdlglogin.h5.3 qdlglog…