不知道有多少同伴在实践的项目中运用过WindowManager,假设有了解过WindowManager或许WMS的同伴应该知道,这在Android Framework中其实是很巨大的,由于所有页面的展现都是基于Window的,而且现在许多音视频软件,例如音视频通话、播放器等,在退出后台之后会在前台有一个悬浮窗口,咱们能够随意拖动,像这种技能完结手段便是经过WindowManager完结的,所以了解WMS的原理能够协助咱们完结高性能的悬浮窗组件。

1 Window的认知

首要咱们从常见的几个概念入手,开端了解WMS。

1.1 基础概念了解

  • Window

信任同伴们关于Window都有所了解,Window是一个笼统类,源码中声明现存唯一的完结类便是PhoneWindow。

The only existing implementation of this abstract class is android.view.PhoneWindow, which you should instantiate when needing a Window.

其实每个页面都对应一个Window,例如每发动一个Activity,都会创立一个Window,当时页面所有的View都会在这个容器中展现。

  • WindowManager

WindowManager是用于对Window的办理,例如前面咱们说到的每次发动一个Activity都会创立一个Window,除了创立之外,还包括更新以及删去。

public interface ViewManager
{
    /**
     * Assign the passed LayoutParams to the passed View and add the view to the window.
     * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
     * errors, such as adding a second view to a window without removing the first view.
     * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
     * secondary {@link Display} and the specified display can't be found
     * (see {@link android.app.Presentation}).
     * @param view The view to be added to this window.
     * @param params The LayoutParams to assign to view.
     */
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}
  • WMS

WMS,全称是WindowManagerService,也是与AMS、PKMS平级的体系服务,所以咱们平常运用的WindowManager仅仅服务端的一个署理,这才是真实去做View的增加、删去和更新逻辑的服务。

那么常见的Window有哪些呢?首要Activity便是一个窗口,页面所有的View都在Window之上;除此之外,Dialog、Toast、PopupWindow等,也是一个Window。

1.2 Window的分类

  • Application Window

这一类属于运用程序窗口,例如Activity便是典型,就不在此赘述了。

/**
 * Start of window types that represent normal application windows.
 */
public static final int FIRST_APPLICATION_WINDOW = 1;
/**
 * End of types of application windows.
 */
public static final int LAST_APPLICATION_WINDOW = 99;

这儿体系关于运用程序窗口做了上下界的操控,在type处于1-99之间的都是属于运用窗口。

  • Sub Window

咱们能够理解为子窗口,它不能独立存在,而是需求依附于父窗口才能展现。例如Dialog,它需求依附于Activity的窗口作为父窗口才能够展现,共用一个Token。

/**
 * Start of types of sub-windows.  The {@link #token} of these windows
 * must be set to the window they are attached to.  These types of
 * windows are kept next to their attached window in Z-order, and their
 * coordinate space is relative to their attached window.
 */
public static final int FIRST_SUB_WINDOW = 1000;
/**
 * End of types of sub-windows.
 */
public static final int LAST_SUB_WINDOW = 1999;

这儿体系关于子窗口做了上下界的操控,在type处于1000-1999之间的都是属于子窗口。

  • System Window

体系窗口比较特殊,它不需求依附于父窗口就能够独立存在,而且子窗口能够升级为体系窗口,只需求将Window的type修正为TYPE_SYSTEM_ALERT ,就能够将Dialog升级为体系窗口。

window?.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)

除此之外,还有体系的音量条、亮度条、Toast,以及发生crash之后弹出的体系弹窗。

/**
 * Start of system-specific window types.  These are not normally
 * created by applications.
 */
public static final int FIRST_SYSTEM_WINDOW     = 2000;
/**
 * End of types of system windows.
 */
public static final int LAST_SYSTEM_WINDOW      = 2999;

这儿体系关于体系窗口做了上下界的操控,在type处于2000-2999之间的都是属于体系窗口。

1.3 窗口的等级排序

那么这儿想一个问题,已然窗口分了3种类型,那么其排列顺序是什么样的呢,是否能够在两个子窗口中心弹出一个体系弹窗呢?

答案是不能够!其实这儿咱们需求记住一点,Window的type值越大,那么其层级就越深,也就意味着会在屏幕的最上层展现。

所以体系窗口会在运用窗口以及子窗口的最上面,见下图展现:

Android进阶宝典 -- WindowManager原理深度分析

所以假设咱们想要在运用上层做View的展现,能够挑选运用子窗口或许体系窗口 ,当然运用Dialog可能无法满足产品需求,所以才会考虑运用WindowManager。

2 WindowManager的运用

首要画一下与Window相关的类图:

Android进阶宝典 -- WindowManager原理深度分析

这儿是采用了桥接设计模式,笼统与完结分离,每个完结类(PhoneWindow和WindowManagerImpl)自在开展,Window作为笼统类,持有了WindowManager的引证,子类能够在此基础上做相应的扩展。

2.1 Window的创立

当咱们发动一个Activity的时分,就会创立一个Window,所以咱们从Activity的发动开端看起,经过前面咱们关于Activity发动章节的介绍,会在performLaunchActivity办法中,做真实Activity的发动。

在这个办法中,经过Instrument创立一个新的Activity,然后调用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,
        IBinder shareableActivityToken) {
    attachBaseContext(context);
    mFragments.attachHost(null /*parent*/);
    //创立PhoneWindow目标
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(mWindowControllerCallback);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    // 创立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());
    }
    mWindowManager = mWindow.getWindowManager();
    mCurrentConfig = config;
    mWindow.setColorMode(info.colorMode);
    mWindow.setPreferMinimalPostProcessing(
            (info.flags & ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING) != 0);
    setAutofillOptions(application.getAutofillOptions());
    setContentCaptureOptions(application.getContentCaptureOptions());
}

咱们看到,在Activity的attach办法中,创立了PhoneWindow目标,然后获取到WindowManager目标并调用setWindowManager办法。

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
        boolean hardwareAccelerated) {
    mAppToken = appToken;
    mAppName = appName;
    mHardwareAccelerated = hardwareAccelerated;
    if (wm == null) {
        wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
    }
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}

在setWindowManager中,将mWindowManager赋值,其实便是创立了WindowManagerImpl目标,在调用getWindowManager办法时,拿到的便是WindowManagerImpl实例,后续所有View的处理都是经过WindowManagerImpl来完结。

那么当Window创立完结之后,内部View是怎么增加的呢?现在仅仅创立一个空壳容器,那么在Activity创立完结之后,调用onCreate办法,便是开端往空壳中填充元素,详细的一个笼统层级能够看下图:

Android进阶宝典 -- WindowManager原理深度分析

这儿咱们拿运用窗口举例,其实子窗口与体系窗口与其架构共同,当发动一个Activity的时分,前面咱们剖析到在调用attach办法时,会创立PhoneWindow以及WindowManager完结类。然后往下会有一个DecorView,这个是什么呢?

2.1.1 DecorView的创立

DecorView是FrameLayout的一个子类,它是页面树形结构中的根节点,那么它是什么时分创立的呢?

public void setContentView(int resId) {
    ensureSubDecor();
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    mAppCompatWindowCallback.bypassOnContentChanged(mWindow.getCallback());
}

当调用setContentView时,首要调用ensureSubDecor判别DecorView是否创立;

假设想要看详细的源码剖析,能够看这篇文章

所以DecorView便是在setContentView时完结了创立,而且与Window完结了绑定联系,当然DecorView的布局可能会由于不同的配置而烘托不同的xml文件,可是一定会存在一个承载页面的容器,咱们看在DecorView创立完结之后,会经过查找id为content的容器,移除里边全部的子View,并把最新的布局增加到content中。

所以咱们页面的布局,其实便是DecorView的一个子View。

2.1.2 View的增加时机

当Activity调用onCreate办法时,会将布局解析并实例化View,那么详细显现在页面上时便是在onResume生命周期完结后,此刻页面能够与用户交互了,所以咱们看下handleResumeActivity中详细做了什么事。

@Override
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
        boolean isForward, String reason) {
// .....
    final Activity a = r.activity;
    // .....
    if (r.window == null && !a.mFinished && willBeVisible) {
        r.window = r.activity.getWindow();
        View decor = r.window.getDecorView();
        decor.setVisibility(View.INVISIBLE);
        ViewManager wm = a.getWindowManager();
        WindowManager.LayoutParams l = r.window.getAttributes();
        a.mDecor = decor;
        l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
        l.softInputMode |= forwardBit;
        if (r.mPreserveWindow) {
            a.mWindowAdded = true;
            r.mPreserveWindow = false;
            // Normally the ViewRoot sets up callbacks with the Activity
            // in addView->ViewRootImpl#setView. If we are instead reusing
            // the decor view we have to notify the view root that the
            // callbacks may have changed.
            ViewRootImpl impl = decor.getViewRootImpl();
            if (impl != null) {
                impl.notifyChildRebuilt();
            }
        }
        if (a.mVisibleFromClient) {
            if (!a.mWindowAdded) {
                a.mWindowAdded = true;
                // ※ 这儿增加了DecorView
                wm.addView(decor, l);
            } else {
                // The activity will get a callback for this {@link LayoutParams} change
                // earlier. However, at that time the decor will not be set (this is set
                // in this method), so no action will be taken. This call ensures the
                // callback occurs with the decor set.
                a.onWindowAttributesChanged(l);
            }
        }
        // If the window has already been added, but during resume
        // we started another activity, then don't yet make the
        // window visible.
    } else if (!willBeVisible) {
        if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
        r.hideForNow = true;
    }
   // ...... 
}

咱们看到在handleResumeActivity中,调用了WindowManagerImpl的addView办法,所以咱们需求总结一下:

  • 当onCreate办法中调用setContentView办法时,首要会创立DecorView(假设没有创立的话),然后解析xml布局将View增加到DecorView傍边,此刻并没有显现;
  • 在onResume办法中,如上源码,此刻会将预备好的DecorView增加到Window傍边,此刻页面才会可见可交互。

2.2 ViewRootImpl大管家

经过前面的剖析,咱们知道当onResume的时分,调用WindowManagerImpl的addView办法,咱们看了下源码发现是空完结,其实在调用getWindowManager().addView办法时,实践上是调用的WindowManagerGlobal的addView办法。

@UnsupportedAppUsage
private final ArrayList<View> mViews = new ArrayList<View>();
@UnsupportedAppUsage
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow, int userId) {
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }
    if (display == null) {
        throw new IllegalArgumentException("display must not be null");
    }
    if (!(params instanceof WindowManager.LayoutParams)) {
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    if (parentWindow != null) {
        parentWindow.adjustLayoutParamsForSubWindow(wparams);
    } else {
        // 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) {
        // Start watching for system property changes.
        if (mSystemPropertyUpdater == null) {
            mSystemPropertyUpdater = new Runnable() {
                @Override public void run() {
                    synchronized (mLock) {
                        for (int i = mRoots.size() - 1; i >= 0; --i) {
                            mRoots.get(i).loadSystemProperties();
                        }
                    }
                }
            };
            SystemProperties.addChangeCallback(mSystemPropertyUpdater);
        }
        int index = findViewLocked(view, false);
        if (index >= 0) {
            if (mDyingViews.contains(view)) {
                // Don't wait for MSG_DIE to make it's way through root's queue.
                mRoots.get(index).doDie();
            } else {
                throw new IllegalStateException("View " + view
                        + " has already been added to the window manager.");
            }
            // The previous removeView() had not completed executing. Now it has.
        }
        // If this is a panel window, then find the window it is being
        // attached to for future reference.
        if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
            final int count = mViews.size();
            for (int i = 0; i < count; i++) {
                if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                    panelParentView = mViews.get(i);
                }
            }
        }
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
        // do this last because it fires off messages to start doing things
        try {
            // 与view树树立相关
            root.setView(view, wparams, panelParentView, userId);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            if (index >= 0) {
                removeViewLocked(index, true);
            }
            throw e;
        }
    }
}

在addView办法中,创立了一个ViewRootImpl目标,这个目标负责全部View的办理,相当于View的根节点,每创立一个Window,都会创立一个ViewRootIMpl,由于WindowManagerGlobal是一个单例,所以会有一个mRoots调集存储所有Window的树形结构。

经过下面这张图能够直观地看到两者的联系:

Android进阶宝典 -- WindowManager原理深度分析

创立ViewRootImpl之后,会调用setView办法与View树树立相关,在setView办法中,会经过mWindowSession与WMS树立通讯。

try {
    mOrigWindowType = mWindowAttributes.type;
    mAttachInfo.mRecomputeGlobalAttributes = true;
    collectViewAttributes();
    adjustLayoutParamsForCompatibility(mWindowAttributes);
    controlInsetsForCompatibility(mWindowAttributes);
    res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
            getHostVisibility(), mDisplay.getDisplayId(), userId,
            mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
            mTempControls);
    if (mTranslator != null) {
        mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
        mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
    }
} catch (RemoteException e) {
    mAdded = false;
    mView = null;
    mAttachInfo.mRootView = null;
    mFallbackEventHandler.setView(null);
    unscheduleTraversals();
    setAccessibilityFocus(null, null);
    throw new RuntimeException("Adding window failed", e);
} finally {
    if (restore) {
        attrs.restore();
    }
}

IWindowSession能够认为是WMS服务端的署理目标,双方经过Binder完结进程间通讯,告诉WMS要增加一个窗口,这儿咱们后续会详细地介绍。

那么到这儿一个Window窗口就显现在了用户面前。

2.2.1 制作流程的触发时机

前面咱们讲到了ViewRootImpl首要用来办理View树,已然能够办理View树,那么View的改写也是交由ViewRootImpl来操控。

详细可见2.3节的介绍。

2.2.2 事情分发流程

已然作为全部View的大管家,那么事情的分发其实也是由ViewRootImpl来完结,详细流程如下:

Android进阶宝典 -- WindowManager原理深度分析

首要ViewRootImpl将事情给到DecorView,可是DecorView并没有直接将事情给到ViewGroup,而是先给了Activity,看起来赤色区域的是有点儿重复,可是能略过1-2-3过程,直接从过程4开端吗?

其实是不行的!为什么呢?信任同伴们在开发中,必定涉及到对Window设置flag,也便是所谓的“标志”,例如咱们设置了一个FLAG_NOT_FOCUSABLE标志位,此刻Window不会拿到任何的焦点。

window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)

假设仅仅从过程4进行分发,那么窗体一定是能够获取到焦点,由于这是默认的行为,所以这样设置Flag就无效了,因此DecorView先将事情给到Activity,然后Activity将事情给到PhoneWindow,由PhoneWindow自身设置的flag决定事情怎么分发。

2.3 Window的改写

前面咱们介绍了Window的创立以及View的增加,接下来咱们剖析ViewManager的第二个办法改写办法。其实在调用改写办法时,终究必定是调用了WindowManagerGlobal的updateViewLayout办法。

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }
    if (!(params instanceof WindowManager.LayoutParams)) {
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
    view.setLayoutParams(wparams);
    synchronized (mLock) {
        int index = findViewLocked(view, true);
        ViewRootImpl root = mRoots.get(index);
        mParams.remove(index);
        mParams.add(index, wparams);
        root.setLayoutParams(wparams, false);
    }
}

经过前面咱们关于addView源码的剖析,咱们需求先熟知两个变量:mRoots和mViews,首要mRoots是ViewRootImpl的调集,mViews是增加进来的View的调集,假设咱们拿发动Activity为例,增加进来的View是DecorView,所以在更新Window的时分,首要会调用findViewLocked从mViews中拿到对应的方位index。

private int findViewLocked(View view, boolean required) {
    final int index = mViews.indexOf(view);
    if (required && index < 0) {
        throw new IllegalArgumentException("View=" + view + " not attached to window manager");
    }
    return index;
}

找到View地点的index之后,由于增加View和Roots是同步的,所以也能够拿到当时Window对应的ViewRootImpl,然后调用setLayoutParams办法进行改写。

public void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
    synchronized (this) {
        // ......
        if (newView) {
            mSoftInputMode = attrs.softInputMode;
            requestLayout();
        }
        // Don't lose the mode we last auto-computed.
        if ((attrs.softInputMode & SOFT_INPUT_MASK_ADJUST)
                == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
            mWindowAttributes.softInputMode = (mWindowAttributes.softInputMode
                    & ~SOFT_INPUT_MASK_ADJUST) | (oldSoftInputMode & SOFT_INPUT_MASK_ADJUST);
        }
        if (mWindowAttributes.softInputMode != oldSoftInputMode) {
            requestFitSystemWindows();
        }
        mWindowAttributesChanged = true;
        scheduleTraversals();
    }
}

咱们关注下第二个参数,假设是newView,那么就会调用requestLayout进行改写,咱们看到调用updateViewLayout办法时,传入的为false,那么就会直接调用scheduleTraversals办法。

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

这个时分会发送UI改写的信号,经过postSyncBarrier发送同步屏障,终究进入到ViewRootImpl的performTraversals办法中,首要从以下几步入手:

  • 在发送同步栅门信号之后,其实就会等待VSYNC信号回来,然后履行TraversalRunnable中的run办法。
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
  • 其实doTraversal办法中,便是履行performTraversals办法,
void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }
        performTraversals();
        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

这个办法中详细干了什么,记住下面的图即可。

Android进阶宝典 -- WindowManager原理深度分析

是不是很熟悉,其实便是经过ViewRootImpl建议从头制作的流程,经过ViewRootImpl传递给ViewGroup,然后经过ViewGroup传递到子View,分别进行测量、布局、制作到页面上。

2.3.1 UI改写机制

前面咱们讲到,当调用updateViewLayout时,就会触发UI改写制作流程,例如咱们想要修正TextView的值,假设咱们连续调用两次setTextView会触发几回重绘呢?

这儿咱们先了解下Android的改写机制,Android手机目前设置的帧率为60FPS,也便是每秒60帧的改写速率,准确到毫秒,便是距离16ms会制作一帧,所以即使咱们多次调用setTextView,终究也仅仅会在16ms内触发一次重绘。

那么当UI预备改写时,到改写完结中心的流程是什么样的呢?咱们以setTextView为例。

当在上层主动调用改写接口时,一般是经过调用invalidate或许postinvalidate,调用setTextView办法时,底层其实也是调用了invalidate办法恳求建议重绘。

那么View自身必定不能说改写就能马上改写,需求一层一层向上打报告,直到找到ViewRootImpl,此刻就像调用ViewManager的updateViewLayout办法相同,会履行ViewRootImpl # scheduleTraversals办法进行重绘,往MessageQueue中插入同步栅门,一起向VSYNC服务建议恳求。

当VSYNC服务赞同重绘之后,会将VSYNC信号回来,ViewRootImpl接收到音讯后,删去同步栅门,调用performTravesals办法进行UI重绘。

所以整个的流程如下:

Android进阶宝典 -- WindowManager原理深度分析

这儿需求留意一点,即使是有16ms的刷机机制,可是体系一定是在每隔16ms的距离内完结改写吗?其实不是的,建议改写恳求一定是客户端主动恳求VSYNC,等到VSYNC回来之后才会改写,否则就不会改写,例如整个页面的UI都没有变化。

2.3.2 SurfaceFlinger烘托进程

前面咱们剖析在接收到VSYNC信号回来之后,会履行performTraversal办法进行重绘,终究结尾就在onDraw办法中,会在画布上画出图画数据。

那么已然说到了图画烘托,那么简略提一下Android体系的烘托中心进程SurfaceFlinger,包括咱们前面说到的画布烘托,运用的所有烘托逻辑都会进入到SF中进行,把处理后的图画数据交给CPU或许GPU进行制作。

其实经过对Window的基本概念的理解,以及从Window的创立到View的增加,咱们大概关于WindowManager的作用有了大致的了解,其实这儿没有涉及到WMS源码的剖析,在后续的文章中会深化WMS,看在体系底层怎么完结对View的操控逻辑。