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


    前言:

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

    主要分为2大块:

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

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

    一.APP侧Window注册

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

    1.1 Activity和ActivityClientRecord中成员变量赋值

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

    1. //ActivityThread.java
    2. public void handleResumeActivity(ActivityClientRecord r, ...) {
    3. final Activity a = r.activity;
    4. ...
    5. if (r.window == null && !a.mFinished && willBeVisible) {
    6. r.window = r.activity.getWindow();
    7. ...
    8. //1
    9. a.mDecor = decor;
    10. //2
    11. l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
    12. //3
    13. wm.addView(decor, l);
    14. }
    15. }

    主要执行了以下的逻辑:

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

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

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

    3.通过windowManager添加decor。这里wm的对象,实际上是WindowManagerImpl,而其中的addView方法中,又交给了WindowManagerGlobal来处理,而WindowManagerGlobal就是一个单例类,一个进程只会存在一个mGlobal对象。相关代码如下:

    1. //WindowManagerImpl.java
    2. mGlobal
    3. private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    4. public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    5. mGlobal.addView(view, params,...);
    6. }

    1.2 WindowManagerGlobal装载Window

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

    1. public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId) {
    2. final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    3. //1
    4. if (parentWindow != null) {
    5. parentWindow.adjustLayoutParamsForSubWindow(wparams);
    6. }
    7. ViewRootImpl root;
    8. View panelParentView = null;
    9. ...
    10. //2
    11. if (windowlessSession == null) {
    12. root = new ViewRootImpl(view.getContext(), display);
    13. } else {
    14. root = new ViewRootImpl(view.getContext(), display, windowlessSession);
    15. }
    16. view.setLayoutParams(wparams);
    17. //3
    18. mViews.add(view);
    19. mRoots.add(root);
    20. mParams.add(wparams);
    21. //4
    22. root.setView(view, wparams, panelParentView, userId);
    23. }

    主要执行了以下的逻辑:

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

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

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

    1. @UnsupportedAppUsage
    2. private final ArrayList<View> mViews = new ArrayList<View>();
    3. @UnsupportedAppUsage
    4. private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    5. @UnsupportedAppUsage
    6. private final ArrayList<WindowManager.LayoutParams> mParams =
    7. new ArrayList<WindowManager.LayoutParams>();

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

    1.3 修复window的LayoutParams

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

    1. void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
    2. if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW ...){
    3. ...
    4. } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW){
    5. } else {
    6. wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
    7. wp.setTitle(mAppName);
    8. wp.packageName = mContext.getPackageName();
    9. if (mHardwareAccelerated || (mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED) != 0) {
    10. wp.flags |= FLAG_HARDWARE_ACCELERATED;
    11. }
    12. }
    13. }

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

    1.4 获取WindowSession

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

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

    1. WindowManagerGlobal.java
    2. public final class WindowManagerGlobal {
    3. private static IWindowSession sWindowSession;
    4. public static IWindowSession getWindowSession() {
    5. synchronized (WindowManagerGlobal.class) {
    6. if (sWindowSession == null) {
    7. try {
    8. InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
    9. IWindowManager windowManager = getWindowManagerService();
    10. sWindowSession = windowManager.openSession(
    11. new IWindowSessionCallback.Stub() {
    12. @Override
    13. public void onAnimatorScaleChanged(float scale) {
    14. ValueAnimator.setDurationScale(scale);
    15. }
    16. });
    17. } catch (RemoteException e) {
    18. throw e.rethrowFromSystemServer();
    19. }
    20. }
    21. return sWindowSession;
    22. }
    23. }
    24. }

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

    1. public class WindowManagerService{
    2. @Override
    3. public IWindowSession openSession(IWindowSessionCallback callback) {
    4. return new Session(this, callback);
    5. }
    6. }

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

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

    1.5 ViewRootImpl负责视图绑定

    我们再看一下setView()方法:

    1. ViewRootImpl.java
    2. public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {
    3. ...
    4. if (mView == null) {
    5. mView = view;
    6. ...
    7. //1
    8. requestLayout();
    9. //2
    10. InputChannel inputChannel = null;
    11. if ((mWindowAttributes.inputFeatures
    12. & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
    13. inputChannel = new InputChannel();
    14. }
    15. //3
    16. res = mWindowSession.addToDisplayAsUser(...);
    17. }
    18. //
    19. mInputEventReceiver = new WindowInputEventReceiver(inputChannel,Looper.myLooper());
    20. }

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

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

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

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

    1. int addToDisplayAsUser(IWindow window, in WindowManager.LayoutParams attrs,
    2. in int viewVisibility, in int layerStackId, in int userId,
    3. in InsetsVisibilities requestedVisibilities, out InputChannel outInputChannel,
    4. 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向系统侧注册。我们看一下其在系统侧的实现:

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

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

    1. //WindowManagerService.java
    2. public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,...) {
    3. WindowState parentWindow = null;
    4. ...
    5. //1
    6. final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);
    7. ...
    8. //2
    9. WindowToken token = displayContent.getWindowToken(hasParent ? parentWindow.mAttrs.token : attrs.token);
    10. ...
    11. if (token == null) {
    12. if( hasParent ){
    13. token = parentWindow.mToken;
    14. } else if (){
    15. token = new WindowToken.Builder(this, binder, type)
    16. } else {
    17. token = new WindowToken.Builder(this, binder, type)
    18. }
    19. ...
    20. }
    21. ...
    22. //3
    23. final WindowState win = new WindowState(this, session, , client, token, parentWindow,appOp[0], attrs, viewVisibility, session.mUid, userId, session.mCanAddInternalSystemWindow);
    24. ...
    25. if (openInputChannels) {
    26. win.openInputChannel(outInputChannel);
    27. }
    28. ...
    29. //4
    30. win.openInputChannel(outInputChannel);
    31. ...
    32. //5
    33. win.attach();
    34. win.initAppOpsState();
    35. ...
    36. win.mToken.addWindow(win);
    37. }

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

    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()这个方法:

    1. //WindowState.java
    2. void attach() {
    3. if (DEBUG) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
    4. mSession.windowAddedLocked();
    5. }
    6. //Session.java
    7. void windowAddedLocked() {
    8. if (mPackageName == null) {
    9. mPackageName = wpc.mInfo.packageName;
    10. }
    11. if (mSurfaceSession == null) {
    12. mSurfaceSession = new SurfaceSession();
    13. ...
    14. mService.mSessions.add(this);
    15. }
    16. mNumWindow++;
    17. }

    简单来说,一个应用首次完成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的创建。

     

  • 相关阅读:
    如何改变胆小怕事的性格?
    React之组件定义和事件处理
    Config
    JNPF低代码开发
    2023年中国调音台产业链、产量及市场规模分析[图]
    ADI 阻抗测量开发板AD5940调试
    CSS高频面试题
    3857墨卡托坐标系转换为4326 (WGS84)经纬度坐标
    【分享】“飞鹅打印机“ 在集简云平台集成应用的常见问题与解决方案
    内部类杂记
  • 原文地址:https://blog.csdn.net/AA5279AA/article/details/132196264