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

前言:

本文属于安卓页面绘制流程的第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的创建。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失落夏天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值