安卓页面绘制流程(3)Window注册

2023-08-09 19:57:37

前言:

本文属于安卓页面绘制流程的第3篇,主要介绍应用是如何把APP侧的Window向system进行注册的。

主要分为2大块:

第一块,APP侧在resume周期时向系统侧申请绑定。

第二块,系统侧收到请求后处理绑定的流程。

一.APP侧Window注册

在上一篇文章中,我们已经讲过,在Activity的create周期内,其所对应的window及其中的布局创建完成。接下来,就是需要把这个window和系统侧做一个绑定。

1.1 Activity和ActivityClientRecord中成员变量赋值

首先,我们仍然看一下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来处理,而WindowManagerGlobal就是一个单例类,一个进程只会存在一个mGlobal对象。相关代码如下:

//WindowManagerImpl.java
mGlobal
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

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

1.2 WindowManagerGlobal装载Window

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

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

主要执行了以下的逻辑:

1.对最开始的WindowManager.LayoutParams进行一定的修正,补充一些必要的参数,具体内容我们2.3来讲。

2.创建ViewRootImpl,ViewRootImpl是一个很重要的角色,它负责维护window和系统侧的沟通,并且还是页面刷新显示流程的执行者。

3.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>();

4.上面说到,ViewRootImpl是流程的具体执行者,那么window的绑定自然也是交给其来处理。负责这个绑定任务的就是ViewRootImpl中的setView方法,我们2.4中来讲。

1.3 修复window的LayoutParams

这里的adjustLayoutParamsForSubWindow方法,是Window类提供的,我们看一下代码:

void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
    if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW ...){
        ...
    } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW){
        
    } else {
        wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
        wp.setTitle(mAppName);
        wp.packageName = mContext.getPackageName();
        if (mHardwareAccelerated || (mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED) != 0) {
            wp.flags |= FLAG_HARDWARE_ACCELERATED;
        }
    }
}

其实主要就是给token和title赋值,title好理解。而token,其实是系统在Activity创建给分配Activity的那个token。

1.4 获取WindowSession

介绍setView之前,我们先来了解下WindowManagerGlobal中的WindowSession对象,它是一个负责和系统通信的binder引用。

我们直接看其中的getWindowSession()方法:

WindowManagerGlobal.java
public final class WindowManagerGlobal {
    private static IWindowSession sWindowSession;
    
    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            });
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }
}

通过WindowManagerService的openSession方法获取的,我们再看一下系统侧的代码:

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

具体Session的代码我们就不看了,它就是一个binder的server端,负责接收APP过来的请求并进行处理,下一章要讲的addToDisplayAsUser方法就是它提供的。

至此,APP端负责和系统通讯的WindowSession已经成功获取了,并且把它传递给ViewRootImpl,下面的流程中,就会用到这个对象。

1.5 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());
}

相关代码进行了删减,只保留最核心的部分,这样看起来会比较清晰。

1.首先,在方法中通过requestLayout方法请求执行首次的View绘制流程。requestLayout中会生成一个任务放到主线程中去执行。首次绘制是十分关键的,window相关的内容都是首次绘制的时候传递到SF的,这个我们就不展开了,绘制流程我们系列第5篇会讲,首次通知SF我们会在第5篇介绍。

2.生成InputChannel对象,这个对象类似于一个回调,通过后面的binder方法传递给系统侧并注册。window上的点击事件,就会通过InputChannel回调通知到应用侧。生成WindowInputEventReceiver就用到了inputChannel,它会接收系统的通知然后通知到ViewRootImpl,最终通知到Activity和DecorView。

3.这里使用到2.3中获取的mWindowSession引用,把相关的对象传递给系统侧,进行相关的注册。传递的内容如下:

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

APP提供的binder引用,server端是ViewRootImpl.W

WindowManager.LayoutParams

attrs

window的显示属性

int

viewVisibility

显示状态,首次的话是View.INVISIBLE

int

layerStackId

displayId,首次的时候值为0

int

userId

用户ID

InputChannel

inputChannel

事件通道,用于点击等事件的传递

第三章中,我们会详细的讲一下这些参数是如何使用的。

1.6 流程小节

整个流程如下图所示:

APP侧,首先构建WindowManagerGlobal维护所有的视图,并且为每个window都创建一个ViewRootImpl用于处理展示流程,并且首次的时候还会创建mWindowSession对象用于和系统通讯。最后,通过mWindowSession的addToDisplayAsUser方法,把相关的内容向系统侧进行注册。 

二.系统侧Window绑定

为了方便一些对这块了解不多的读者,所以我们在介绍系统侧注册window的流程前,我们先对这一块的几个核心类和主要流程先做一个简单介绍。

2.1 核心类介绍

Window的注册,其实并不是把客户端的window对象注册到了系统,而是把ViewRootImpl中的内部类W(IWindow类型)的这个binder引用,提供给系统,让系统有了一个和应用交互的桥梁。

类名

功能介绍

com.android.server.wm.Session

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

WindowManagerService

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

ViewRootImpl.W

ViewRootImpl提供的binder引用,负责和系统侧进行沟通。

SurfaceSession

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

DisplayContent

确定唯一的一块显示区域,由RootWindowContainer维护所有的视图区域。

2.2 把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, IWindow client, LayoutParams attrs, int viewVisibility,...) {
    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, , client, token, parentWindow,appOp[0], attrs, viewVisibility, session.mUid, userId, session.mCanAddInternalSystemWindow);
    ...
    if  (openInputChannels) {
        win.openInputChannel(outInputChannel);
    }
    ...
    //4
    win.openInputChannel(outInputChannel);
    ...
    //5
    win.attach();
    win.initAppOpsState();
    ...
    win.mToken.addWindow(win);
}

我们先看一下方法的入参:

Session就是系统侧负责管理和应用视图交互的对象;

IWindow是应用侧ViewRootImpl传递过来的W对象;

attrs就是Window的显示参数;

viewVisibility是显示状态;

requestUserId是应用的UID;

outInputChannel则是传递过来的事件传递的通道。

然后我们再来看一下逻辑:

首先,根据displayId找到归属的DisplayContent,如果是首次,则创建,正常来说不会走创建流程。DisplayContent的作用确定唯一的显示区域,其实就是确定一块显示屏,用于跟踪一系列的WindowState;

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

第三步,生成WindowState,这里的WindowState和APP侧的Window是对应的,WindowState就是在系统侧window的描述并负责和window进行通讯。

WindowState中使用到了很多的参数,我们做了一个来源整理:

 

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

最后,通过attch()方法完成绑定。

我们重点看一下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的数量。

2.3 流程小节

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


三.总结

所以整个window的注册流程主要分为三块大块:

1.在Activity的resume流程中,会使用WindowManagerGlobal维护应用侧所有的视图。

2.WindowManagerGlobal添加Window时,会为其创建一个ViewRootImpl维护window,系统和SF的关系,并且负责向系统侧注册。注册时会传递一些必要的参数和通道对象,方便后续和系统的沟通。

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

整体流程图如下:

到这里为止,只是完成了APP向system_server的注册,但是也页面的绘制显示并不是system_server负责的,而是surfaceFlinger。所以,system_server会帮助APP向SF发起相关的注册申请,而这一块,就属于我们下一篇要讲的SurfaceSession的创建。

 

更多推荐

Docker概念通讲

目录什么是Docker?Docker的应用场景有哪些?Docker的优点有哪些?Docker与虚拟机的区别是什么?Docker的三大核心是什么?如何快速安装Docker?如何修改Docker的存储位置?Docker镜像常用管理有哪些?如何创建Docker容器?Docker在后台的标准运行过程是什么?Docker网络模式

Apollo

Apollo🍓目前市面上比较多的配置中心🍓Apollo组件🍓Apollo特性🍓Apollo服务端安装🍓部署架构🍓核心概念🍓客户端连接Apollo🍓配置发布原理代码地址:https://gitee.com/xuhx615/apollo-demo.git🍓目前市面上比较多的配置中心⭐Disconf百度开源

Dubbo3基础使用

1、Dubbo概述现在SpringCloudAlibaba比较火,用的比较多是吧,那dubbo是不是过时的呢?并不是的,以前有人把Dubbo和SpringCloud进行对比,其实两者是不同维度的,不能对比,dubbo就是一个rpc框架,SpringCloud是一个生态,里面包括很多组件,并且dubbo3也可以和Spri

【K8S系列】深入解析k8s网络插件—Weave Net

序言做一件事并不难,难的是在于坚持。坚持一下也不难,难的是坚持到底。文章标记颜色说明:黄色:重要标题红色:用来标记结论绿色:用来标记论点蓝色:用来标记论点Kubernetes(k8s)是一个容器编排平台,允许在容器中运行应用程序和服务。今天学习一下k8s网络插件-WeaveNet相关知识希望这篇文章能让你不仅有一定的收

一文总结提示工程框架,除了CoT还有ToT、GoT、AoT、SoT、PoT

夕小瑶科技说原创编译|谢年年大语言模型LLM被视为一个巨大的知识库,它可以根据你提出问题或陈述的方式来提供答案。就像人类可能会根据问题的不同提供不同的答案一样,LLM也可以根据输入的不同给出不同的答案。因此,你的问题或陈述方式就显得非常重要。如何引导大语言模型给出更恰当的答案,是最近研究的热点。经常用到的方法如让大模型

Python基础学习笔记3

深度学习实践深度学习离不开编程深度学习离不开数学分析(高等数学)、线性代数、概率论等知识,更离不开以编程为核心的动手实践。Python编程语言无论是在机器学习还是深度学习中,Python已经成为主导性的编程语言。而且,现在许多主流的深度学习框架都提供Python接口,Python被用于数据预处理、定义网络模型、执行训练

2023华数杯数学建模A题2023华数杯A 题隔热材料的结构优化控制研究

问题1问题1:该问题需要建立一个数学模型来描绘织物整体热导率与单根纤维热导率之间的关系。这个模型需要考虑织物的结构(如纤维的排列、空隙大小和分布等)以及纤维和空隙中的空气对热传导的贡献。此外,我们需要根据织物的整体热导率来逆向推算出单根纤维的热导率。解题思路:使用热传导的基础理论,结合题目给出的纤维和空气的热导率,以及

单片机C语言实例:24、红外通讯

一、红外接收原理程序实例1:#include<reg52.h>//包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义sbitLED=P1^0;//用sbit关键字定义LED到P1.0端口,LED是自己任意定义且容易记忆的符号sbitIR_IN=P3^2;/*-------------------------

NLP(6)--Diffusion Model

目录一、Flow-BasedGeneralModel1、概述2、函数映射关系3、CouplingLayer4、Glow二、DiffusionModel1、概述2、前向过程3、反向过程4、训练获得噪声估计模型5、生成图片三、马尔科夫链一、Flow-BasedGeneralModel1、概述Flow-BasedGenera

浅谈C++|构造.析构函数篇

一对象的初始化和处理1.1构造函数和析构函数C++拥有构造函数和析构函数,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器提供的构造函数和析构函数是空实现。·构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编

socket编程|TCP

一.套接字概念套接字(Socket)是一种用于网络通信的编程接口,它提供了一种机制,使得不同计算机上的应用程序能够通过网络进行通信和交换数据。套接字可以看作是应用程序和网络之间的端点,它定义了应用程序与网络之间的通信规则和数据格式。通过套接字,应用程序可以创建、连接、发送和接收数据,实现与其他计算机上的应用程序进行通信

热文推荐