View体系#View的显示原理

前言

View的显示原理牵涉知识点甚多,想要明白整个流程需要储备的知识点还是很多的,如果按顺序探究过如下相关知识点,那么本篇的探究还是比较舒服点的。

(1)系统启动过程

(2)应用进程启动

(3)根Activity启动过程

这里是重点,留意下Activity生命周期都做了那些事、Context创建、Application创建。

(4)四大组件Context知识体系

(5)Window机制

安卓系统启动完成后开始创建应用进程,然后启动应用进程的根Activity与用户进行交互。那么交互的界面到底是啥呢?其实交互的界面就是你在Activity#setContentView中传入的View。接下来本篇就以setContentView为开篇来探究下View的显示原理。

一、Activity显示UI的原理

1、Activity#onCreate#setContentView

(1) Activity.java

 
    public void setContentView(View view) {
        getWindow().setContentView(view);
        initWindowDecorActionBar();
    }

    public Window getWindow() {
        return mWindow;
    }
    
    private Window mWindow;

(2)Window.java

/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 * 一个管理顶层窗口外观和行为策略的抽象基类,提供了标准的UI策略。
 * 可以使用window manager与Window直接进行交互。
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 * 唯一实现类是android.view.PhoneWindow
 */
public abstract class Window {

...
}

(3)小结

setContentView内部调用的mWindow的setContentView,mWindow是Activity的成员变量,是Window类型的。window 是一个抽象概念,PhoneWindow是其实现类。所以具体的加载就交给了PhoneWindow的setContentView来加载。

(4)拓展

那么PhoneWindow是在合适创建的呢?

其实phoneWindow是在Activity被创建时创建的,具体是在调用activity的attach方法内部被new出来的。attach内部不止创建了PhoneWindow对象,同时还为activity绑定了上下文Context、创建WindowManager、设置windowCallback等操作。看下activity#attach源码:

    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
       
        attachBaseContext(context); //创建Activity的Context对象

        // 创建PhoneWindow对象
        mWindow = new PhoneWindow(this, window, activityConfigCallback);

        //设置windowCallback.Activity自身实现了这个接口
        mWindow.setCallback(this);
        ...
        mUiThread = Thread.currentThread();
        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
        mAssistToken = assistToken;
        mIdent = ident;
        mApplication = application;
        mIntent = intent;
        mReferrer = referrer;
        mComponent = intent.getComponent();
        mActivityInfo = info;
        mTitle = title;
        mParent = parent;
        ...
        //创建WindowManager对象,
        //设置给Window设置WindowManager属性
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        // 获取WindowManager
        mWindowManager = mWindow.getWindowManager();
       ...
    }
2、PhoneWindow

    // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    
    //这是个View容器,使用setContentView加载的view被加载到这个容器中。
    //mContentParent可以是mDecor本身,也可以是mDecor 的子View。
    ViewGroup mContentParent;
    
    @Override
    public void setContentView(int layoutResID) {
        // 1、核心代码:mContentParent为空通过installDecor方式加载布局
        if (mContentParent == null) {
            installDecor();
        //非透明主题的处理
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        // 透明主题的处理
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {// 2、核心代码:走到这里说明mContentParent!=null.
               // 调用LayoutInflater.inflate方法加载布局到mContentParent容器中。
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

大致逻辑如下:

mContentParent = null时,通过installDecor方式加载布局。
mContentParent !=null时,通过LayoutInflater吧布局加载到mContentParent这个容器中。

读完这段代码或许我们会有疑问:

installDecor方式加载布局、LayoutInflater加载布局到mContentParent。这样布局分情况就会被加载到两个地方了吧?Decor和mContentParent有啥关系吗?

(1)Decor和mContentParent的关系

    // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    
    //这是个View容器,使用setContentView加载的view被加载到这个容器中。
    //mContentParent可以是mDecor本身,也可以是mDecor 的子View。
    ViewGroup mContentParent;

看mContentParent的注释:mContentParent要么是 DecorView本身,要么是DecorView的子布局。貌似还有点抽象,如何解释呢?可以这样理解:

在这里插入图片描述

  • 当有titleBar时DecorView就会被分为 titleBar+mContentParent 两部分。
  • 当无titleBar时DecorView就是mContentParent
  • 最终setContentView都是被加载到DecorView上。

(2)总结

可见首次进入PhoneWindow#setContentView时mContentParent 肯定是为空的,这时走installDecor方法,方法内部会创建初始化mDecor,mContentParent 。生成mContentParent 时还会把mDecor传入,这样二者就可以建立联系了。最终setContentView都是被加载到DecorView上。

3、ActivityThread#handleResumeActivity 方法做了什么事情?

看到这个标题,我们心中或许又有疑问啦,怎么突然调到到了这里???emmm… 这里根前面的过程有啥关系吗???

嗯,别慌我梳理下流程:前面的讲解是由Activity的主线开始讲解的:

主线1:Activity对象被创建后会先回调attach方法。这里面有几个重点步骤,创建Window实现类对象(onCreate中使用)、创建Context、创建WindoManager等。
主线2:回调完onAttach方法后会回调Activity的onCreate方法这里主要调用了setContentView,而且委托给PhoneWindow的setContentView进行处理。最终根view是被加载到根容器DecorView上。
主线3:之前了解Activity启动过程onStart貌似也没做和本文相关的事情,那就是继续走生命周期回调onResume方法。嗯就是这里了看看做了啥。

(1)首先梳理下Activity的先关逻辑

根Activity启动时在AMS中最终是这样的:

AMS通过app.thread.scheduleLaunchActivity 跨进程来调用ApplicationThread的scheduleLaunchActivity 进行创建Activity。

scheduleLaunchActivity会给H 这个Handler类发送个消息进行线程切换,这样就从应用程序进程的Binder线程切换到主线程。主线程中收到创建Activity的消息后调用handleLaunchActivity来处理Activity的创建工作

a、通过调用performLaunchActivity 来创建Activity的实例、回调attach方法、设置context、调用onCreate等。
b、通过调用handleResumeActivity 来让activity处理Resume相关逻辑。

(2)handleResumeActivity


public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        //1、内部执行  r.activity.performResume(r.startsNotResumed, reason);
        // 回调Activity的onResume方法
        
        //ActivityClientRecord内部封装了要创建activity的很多重要信息
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
          
            if (r == null) {
               return;
             }
              // 2、获取Activity对象
               final Activity a = r.activity;
               // 3、获取Window对象
                r.window = r.activity.getWindow();
                //4、获取DecorView对象设置给Activity的成员变量mDecor 
                View decor = r.window.getDecorView();
                // 设置decorView的Visibility属性(还不可见)
                decor.setVisibility(View.INVISIBLE);
                
               //WindowManager是ViewManager 实现类
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                 // 5、给设置decorView设置布局参数,并把decorView通过
                 //wm,进行IPC添加到Window中。
                wm.addView(decor, l);
                 // 6、 使Activity页面可见
                 r.activity.makeVisible(); 

             }

可见首先通过performResumeActivity来让activity执行onResume回调。onResume 回调执行之后就是处理DecorView的显示工作了。到这里我们就知道为啥Activity#onResume之后才能看到界面吧!!!因为在onResume之后系统才着手处理界面的显示工作。

4、总结

我们平时在setContentView中写的布局如何被加载显示的大致流程其实讲到这里已经算是
分析完了,这里再次根据主线进行总结下:

主线1:Activity对象被创建后会先回调attach方法。这里面有几个重点步骤,创建Window实现类对象(onCreate中使用)、创建Context、创建WindoManager(onResume回调完使用)等。
主线2:回调完attach方法后会回调Activity的onCreate方法这里主要调用了setContentView,而Activity又委托给PhoneWindow的setContentView进行处理。最终根view是被加载到根容器DecorView上。
主线3:接着回调onResume方法,onResume方法回调完成后开始处理view的加载window、Activity#Visible工作。这样我们就能看到Activity界面啦

其实到了这里可以大致的说出了View的显示过程了,本着打破砂锅问到底的精神我们还需继续深入下。问题来了,DecorView是应用程序页面的根布局,怎样被显示到屏幕让用户看到的呢?

其实安卓提供了Window机制来处理用户界面相关的工作。通过源码我们可以知道Decorview最终交付给ViewManager的addView 来处理。Windiw是如何添加VIew的呢?View图像又是被如何渲染到屏幕的呢?那么我需要了解下Window相关的知识。

二、Window相关

Window是一个抽象的概念,View是其具体的实现,也即Window是以View的形式存在的。View最终是通过WindowManager传递给WMS进行处理。WMS 为窗口分配suface,然后把surface交付给SurfaceFlinger合并处理,传递给CPU的缓冲区最终把图像渲染到手机
屏幕上显示出来。

Window是有分类的:

  • 应用窗口(1-99):可以简单理解为Activity上面的普通VIew

  • 子窗口(1000-1999):可以简单理解为Activity上的Dialog

  • 系统窗口(2000-2999):可以简单理解为Toast

给安卓建立控件坐标系的话,分x、y、z轴。window层级就对应Z轴。每种Window的类型对应Z值范围。范围越大,越显示在手机屏幕的上层。Window 管理view交付WindowManager处理时需要处理不同类型的Widow,最终交付给WMS。WMS不做类型区分一视同仁,统一处理。有了这些概念后我们会发现在WindowManager的子类中是对window分类处理的。

在这里插入图片描述

ViewManager这个东西,看下其源码十分简单,是一个接口,提供了View的添加、删除、更新操作。Windowmanager是其子类。

1、回顾

(1)回顾下Activity#attach

       //注意,这里的mWindow就是PhoneWindow对象。下面会用到。
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

(2)回顾下ActivityThread#handleResumeActivity

   ViewManager wm = a.getWindowManager();
   wm.addView(decor, l);  

可知在Activity#attach时会获取WindowManager的实例,然后在Activity#onResume处理完成后进行addView操作。那么WindowManager对象时如何获取的呢?

2、WindowManager实例的获取

(1)Window#setWindowManager

public abstract class Window {

...
    private WindowManager mWindowManager;
     
    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated;
        //1、如果WindowManager为空通过getSystemService获取WindowManager对象
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        // 2、WindowManager不为空时吧WindowManager强转为WindowManagerImpl
        //调用WindowManagerImpl的createLocalWindowManager来获取mWindowManager 
        
        //这里的this就是PhoneWindow对象。
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }
    
    public WindowManager getWindowManager() {
        return mWindowManager;
    }
    
    ...
}

Context#getSystemService

    registerService(Context.WINDOW_SERVICE, WindowManager.class,
                new CachedServiceFetcher<WindowManager>() {
                
            @Override
            public WindowManager createService(ContextImpl ctx) {
               // 这里传递参数parentWindow为null
                return new WindowManagerImpl(ctx);
            }});

WindowManagerImpl

public final class WindowManagerImpl implements WindowManager {

    private final Window mParentWindow; 
    
    public WindowManagerImpl(Context context) {
        this(context, null);
    }

    private WindowManagerImpl(Context context, Window parentWindow) {
        mContext = context;
        mParentWindow = parentWindow;
    }
    
    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
    // 吧parentWindow这个phoneWindow对象赋值给mParentWindow
        return new WindowManagerImpl(mContext, parentWindow);
    }

}

WindowManager对象在Activity的attach使被创建的:
1、首先是在Activity#attach时,setWindowManager第一个参数通过getSystemService获取然后传递过来,传递过来后发现对象为空那就重新获取下。
2、通过getSystemService方式获取的WindowManager对象时这时WinmanagerImpl的mParentWindow成员变量这为空
3、2中获取到WindowManager对象然后通过调用WindowManagerImpl的createLocalWindowManager 吧Window实现类对象PhoneWindow作为参数传递过来,再次创建WindowManager对象。这时mParentWindow就是PhoneWindow了。

3、收获
  • WindowManager对象在Activity#attach时被创建赋值。

  • WindowManager的实现类为WindowManagerImpl。

三、WindowManager#addView

view的add操作是委托给WindowManager的实现类WindowManagerImpl#addView来完成
的。

1、WindowManagerImpl
public final class WindowManagerImpl implements WindowManager {

    private final Window mParentWindow; //phoneWindow 对象
    private final Context mContext;
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params){
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

...
    //getSystemService获取WindowManager时调用这个构造,第二个参数Window对象传空。
    public WindowManagerImpl(Context context) {
        this(context, null);
    }


    private WindowManagerImpl(Context context, Window parentWindow) {
        mContext = context;
        mParentWindow = parentWindow;
    }

    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }
...
}
public final class WindowManagerGlobal {

    private static WindowManagerGlobal sDefaultWindowManager;
    private static IWindowManager sWindowManagerService;
    private static IWindowSession sWindowSession;
    
     // View列表
    private final ArrayList<View> mViews = new ArrayList<View>();
    //ViewRootImpl 列表
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); 
     // 布局参数列表
    private final ArrayList<WindowManager.LayoutParams> mParams =new ArrayList<WindowManager.LayoutParams>();

   
 public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
 
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    // 1、首先判断是否具有父window,如果当前窗口要作为子窗口,
    //就会根据父窗口对子窗口的WindowManager.LayoutParams 类型的wparams 
    //对象进行相应调整.
    
        if (parentWindow != null) { //(1)有父Window
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else { //(2)无父window
            // If there's no parent, then hardware acceleration for this view is
            // set from the application's hardware acceleration setting.
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
        ...
        
       // If this is a panel window, then find the window it is being
       // attached to for future reference.
        panelParentView = mViews.get(i);
        
        .....   
         
           //2、创建ViewRootImpl对象
            root = new ViewRootImpl(view.getContext(), display);
            
           // 3、给View设置布局参数
            view.setLayoutParams(wparams);

            mViews.add(view); //view添加到View列表中
            mRoots.add(root);//ViewRootImpl添加到rootView列表中
            mParams.add(wparams);//布局参数添加大布局列表中

            // do this last because it fires off messages to start doing things
            try {
             // 4、把View、view布局参数、view的父view 设置给ViewRootImpl,
             //交付给ViewRootImpl处理。
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        } 


}

WindowManagerGlobal是进程内的单例,内部主要维护了3个表,最终view是被ViewRootImpl#setView来处理的。

2、ViewRootImpl

可以说VIewRootImpl是应用进程与WMS交互的桥梁,二者的IPC工作就在这个类中开始的。除此之外ViewRootImpl还具有很多功能的。先来熟悉下这个类的功能:

  • View树的根,并管理View树
  • 触发View的measure、draw、layout
  • 输入事件的中转站
  • 管理suface
  • 负责与WMS进行通信
public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
        
    //mWindowSession:
    //(1)在ViewRootImpl 构造中进行的初始化.
    //(2)它是一个服务端Binder代理对象,用于进行进程间通信,
    //IWindowSession是Client端的代理对象,它的Server端的实现为Session类
    final IWindowSession mWindowSession;

   //构造
    public ViewRootImpl(Context context, Display display) {
           //获取WindowSession对象,赋值给成员变量mWindowSession 
           mWindowSession = WindowManagerGlobal.getWindowSession();
           ...
    }

     public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
         ...

          int res;
           // Schedule the first layout -before- adding to the window
           // manager, to make sure we do the relayout before receiving
           // any other events from the system.
           //1、view三大流程的起点(layout、measure、draw)
           requestLayout();
           
           //2、调用mWindowSession的addToDisplay进行向WMS申请系统Window、Surface。
           res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                            mTempInsets);

     ...

   }

}

请添加图片描述

本地进程的ViewRootImpl要想和WMS进行通信需要经过Session,那么Session为何包含在WMS中呢?我们接着往下看Session的addToDisplay方法。

3、Session#addToDisplay
/**
 * This class represents an active client session.  There is generally one
 * Session object per process that is interacting with the window manager.
 */
class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {

    final WindowManagerService mService;
    
    // 代码运行在SystemSever进程中
     @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
            Rect outStableInsets, Rect outOutsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
            InsetsState outInsetsState) {
        // 调用WMS的addWindow向系统申请窗口
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel,
                outInsetsState);

}

Sission调用了WMS的addView方法,并且把自身作为参数传递给WMS。每个应用程序都对应一个session。WMS内部维护了个sission列表。

这样剩下的工作就交给WMS来处理,在WMS中会为这个添加的窗口分配Surface,并确定窗口显示次序,可见负责显示界面的是画布Surface,而不是窗口本身。WMS 会将它所管理的Surface 交由SurfaceFlinger处理,SurfaceFlinger会将这些Surface混合并绘制到屏幕上。

四 、总结梳理

走到这里感觉有必要针对前面的逻辑再次进行下梳理了,View的显示原理如下:

这里还是结合根Activity的启动来梳理 ^ - ^

(1)Activity对象被创建后开始回调attach方法,方法内部创建WindlwManager对象、PhoneWindow对象。以便后续使用。

(2)attach方法回调后执行Activity#onCreate回调。这里通过setContextView加载布局View。内部通过PhoneWindow的setContextView加载布局view。

(3)PhoneWindow#setContextView内部 会创建DecorView,这个ViewGroup是应用布局的根布局。我们平时所写的xml布局最终被转换为View添加到这个ViewGroup中。

(3)onCreate回调完成后开始回调onResume方法。onResume方法被回调完成后Activity开始通过WindowManager#addView吧DecorView添加到Window中。(这段逻辑在ActivityThread#handResumeActivity中。)然后使Activity可见,这时我们就可以和用户进行交互了。

其实到这里已经可以知道View是被Window所管理的。那么Window是如何添加View的具体细节?继续总结.

(4)WindowManager的addView最终交付给ViewRootImpl#setView来处理,由于安卓的窗口都是被系统的WMS所管理。WMS处于SystemSever进程,WindowManager#addView逻辑在应用进程。二者之间需要通信这时就需要中间类了。这个中间类就是ViewRootImpl。

(5)ViewRootImpl中有IWindowSession 对象,这个对象是SystemSever 进程中Session类的Binder代理对象。ViewRootImpl#setView 实现就是最终调用Session#addToDisplay。Session#addToDisplay中调用WMS#addWindow。

(6)WMS会向系统申请窗口进行统一管理。

WMS为这个添加的窗口分配Surface,并确定窗口显示次序,可见负责显示界面的是画布Surface,而不是窗口本身。WMS 会将它所管理的Surface 交由SurfaceFlinger处理,SurfaceFlinger会将这些Surface混合并绘制到屏幕上。

五、继续深究

1、填坑

到了这里细心的同学可能会发现ViewRootImpl这个类中还存在一些坑呢,经过前面的分解我们都知道view最终被添加到window上。Window需要由WMS向系统申请,系统统一管理。那么问题来了这样Window上View不就是死的吗?添加时啥样就是啥样 其实这里我们忽略了viewRootImpl类中setView方法中一个重要的api#requestLayout。

 viewRootImpl#setView
 
 res = mWindowSession.addToDisplay(mWindow,,,,,

requestLayout这个Api可以说是View#measure、View#layout、View#draw三大流程的的起点。这个方法中牵涉到个重要的类Choreographer,这个类收到系统的Vsync信号时可以像app主进程发送同步屏障、异步消息优先处理系统的UI绘制、刷新工作。具体到后面文章《屏幕刷新机制》相关的文章再总结吧。可以先参考简书的一篇文章了解下。感觉写的挺不错的。

view.requestLayout源码解析

2、继续探究WMS#addView的处理

Sission调用了WMS的addView方法这时最终的处理工作就交付给WMS了。

六、WMS是如何添加VIew的?

主要就是做了下面4件事:

  • 对所要添加的窗口进行检查,如果窗口不满足一些条件,就不会再执行下面的代码逻辑。

  • WindowToken相关的处理,比如有的窗口类型需要提供WindowToken,没有提供的话就不会执行下面的代码逻辑,有的窗口类型则需要由WMS 隐式创建WindowToken。

  • WindowState的创建和相关处理,将WindowToken和WindowState相关联。

  • 创建和配置DisplayContent,完成窗口添加到系统前的准备工作。

The end

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值