View绘制流程-Window创建

前言:

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对象已经完成了赋值,总结一下,如下图所示:

View绘制流程-Window创建_第1张图片

 

二.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 mViews = new ArrayList();
@UnsupportedAppUsage
private final ArrayList mRoots = new ArrayList();
@UnsupportedAppUsage
private final ArrayList mParams =
        new ArrayList();

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);

整个流程如下图所示:

View绘制流程-Window创建_第2张图片

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

对象类型

成员变量名

解释

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的关系。流程图如下:

View绘制流程-Window创建_第3张图片

 

四.总结

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

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

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

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

整体流程图如下:

View绘制流程-Window创建_第4张图片

 

五.扩展性问题

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

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

你可能感兴趣的:(#,安卓-源码分析,gitee)