Android葵花宝典freeCode@ITWindow&Activity

深入理解 WindowManagerService

2019-01-23  本文已影响283人  lijiankun24

在上篇文章中 初步理解 Window 体系,我们初步分析了 Window 的体系,这篇文章我们分析一下 WindowManagerService(以下简称 WMS)。WMS 错综负责,与 ActivityManagerService、InputManagerService、SurfaceFlinger 关系也很紧密,如果想分析的清楚彻底,恐怕是一两篇文章难以做到的。本篇文章初步分析 WMS 的创建,以及应用进程中的 WindowManager 与 WMS 通信。

一. 理解 WindowManagerService 相关知识

1.1 WindowManagerService 的诞生

1.1.1 在 SystemServer 中的创建

WMS 是在 SystemServer 进程中启动的,SystemServer 进程是 Android 系统启动的时候初始化的,我们首先来看一下 SystemServer 的入口函数 main()

public final class SystemServer {

    /**
     * The main entry point from zygote.
     */
    public static void main(String[] args) {
        new SystemServer().run();
    }
}

从上述代码可见,是创建了一个 SystemServer 对象并调用了 run() 方法


    private void run() {

        ......
        mSystemServiceManager = new SystemServiceManager(mSystemContext);  // 代码 1

        // Start services.
        try {
            traceBeginAndSlog("StartServices");
            startBootstrapServices();                              // 代码 2
            startCoreServices();                                   // 代码 3
            startOtherServices();                                  // 代码 4
            SystemServerInitThreadPool.shutdown();
        } catch (Throwable ex) {
            Slog.e("System", "******************************************");
            Slog.e("System", "************ Failure starting system services", ex);
            throw ex;
        } finally {
            traceEnd();
        }
    }

startOtherServices() 方法很长,我们分析下其中和 WMS 相关的部分

    private void startOtherServices() {
        final Context context = mSystemContext;
        WindowManagerService wm = null;
        InputManagerService inputManager = null;
        
        ......
        try {
            // 代码 1
            traceBeginAndSlog("StartInputManagerService");
            inputManager = new InputManagerService(context);
            traceEnd();

            // 代码 2
            traceBeginAndSlog("StartWindowManagerService");
            // WMS needs sensor service ready
            ConcurrentUtils.waitForFutureNoInterrupt(mSensorServiceStart, START_SENSOR_SERVICE);
            mSensorServiceStart = null;
            wm = WindowManagerService.main(context, inputManager,
                    mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
                    !mFirstBoot, mOnlyCore, new PhoneWindowManager());
            ServiceManager.addService(Context.WINDOW_SERVICE, wm);
            ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
            traceEnd();

            // 代码 3
            traceBeginAndSlog("SetWindowManagerService");
            mActivityManagerService.setWindowManager(wm);
            traceEnd();

            // 代码 4
            traceBeginAndSlog("StartInputManager");
            inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
            inputManager.start();
            traceEnd();
            ......
        } catch (RuntimeException e) {
            Slog.e("System", "******************************************");
            Slog.e("System", "************ Failure starting core service", e);
        }
        ......
        // 代码 5
        traceBeginAndSlog("MakeDisplayReady");
        try {
            wm.displayReady();
        } catch (Throwable e) {
            reportWtf("making display ready", e);
        }
        traceEnd();
        ......
        // 代码 6
        traceBeginAndSlog("MakeWindowManagerServiceReady");
        try {
            wm.systemReady();
        } catch (Throwable e) {
            reportWtf("making Window Manager Service ready", e);
        }
        traceEnd();

        if (safeMode) {
            mActivityManagerService.showSafeModeOverlay();
        }
        // 代码 7
        // Update the configuration for this context by hand, because we're going
        // to start using it before the config change done in wm.systemReady() will
        // propagate to it.
        final Configuration config = wm.computeNewConfiguration(DEFAULT_DISPLAY);
        DisplayMetrics metrics = new DisplayMetrics();
        WindowManager w = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
        w.getDefaultDisplay().getMetrics(metrics);
        context.getResources().updateConfiguration(config, metrics);
        ......
    }
1.1.2 WMS 的构造函数

在上面一段代码中,最重要的莫过于调用 WMS 的 main() 方法创建一个 WindowManagerService 对象了,我们来分析下这个方法

public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {

    private static WindowManagerService sInstance;
    static WindowManagerService getInstance() {
        return sInstance;
    }

    public static WindowManagerService main(final Context context, final InputManagerService im,
            final boolean haveInputMethods, final boolean showBootMsgs, final boolean onlyCore,
            WindowManagerPolicy policy) {
        DisplayThread.getHandler().runWithScissors(() ->
                sInstance = new WindowManagerService(context, im, haveInputMethods, showBootMsgs,
                        onlyCore, policy), 0);
        return sInstance;
    }

}

我们接着上面的 main() 方法分析,上面代码调用了 WMS 的构造方法创建了 WMS 实例对象,我们来看一下 WMS 中的一些重要的成员属性和构造方法,如下所示:

public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {

    final WindowManagerPolicy mPolicy;
    final ArraySet<Session> mSessions = new ArraySet<>();
    final WindowHashMap mWindowMap = new WindowHashMap();
    final ArrayList<AppWindowToken> mFinishedStarting = new ArrayList<>();

    final H mH = new H();

    final InputManagerService mInputManager;

    final WindowAnimator mAnimator;

    private WindowManagerService(Context context, InputManagerService inputManager,
            boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore,
            WindowManagerPolicy policy) {

        ......
        // 代码 1
        mInputManager = inputManager; // Must be before createDisplayContentLocked.
        // 代码 2 
        mPolicy = policy;
        if(mInputManager != null) {
            final InputChannel inputChannel = mInputManager.monitorInput(TAG_WM);
            mPointerEventDispatcher = inputChannel != null
                    ? new PointerEventDispatcher(inputChannel) : null;
        } else {
            mPointerEventDispatcher = null;
        }

        ......
        // 代码 3
        mAnimator = new WindowAnimator(this);

        ......
        // 代码 4
        initPolicy();

        // 代码 5
        // Add ourself to the Watchdog monitors.
        Watchdog.getInstance().addMonitor(this);
        ......
    }
}

1.2 WMS 中的几个重要概念

除开上面构造方法中提到的一些成员属性之外,还有一些非常重要的概念需要理解

1.2.1 Session

在上篇文章 初步理解 Window 体系 中,我们提到 ViewRootImpl 和 WMS 之间的通信就是通过 Session 对象完成的。
Session 类继承自 IWindowSession.Stub,每一个应用进程都有一个唯一的 Session 对象与 WMS 通信,如下图所示

Session.png

图片来源:Window与WMS通信过程

在 WMS 中的 mSessions 成员属性,是 ArraySet 类型,其中保存着 Session 型的对象。

public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
        /**
         * All currently active sessions with clients.
         */
        final ArraySet<Session> mSessions = new ArraySet<>();

        ......

        @Override
        public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
            IInputContext inputContext) {
            if (client == null) throw new IllegalArgumentException("null client");
            if (inputContext == null) throw new IllegalArgumentException("null inputContext");
            Session session = new Session(this, callback, client, inputContext);
            return session;
    }
}
public class Session extends IWindowSession.Stub
        implements IBinder.DeathRecipient {
    final WindowManagerService mService;
    private int mNumWindow = 0;                  // 代码 1
    ......

    // 代码 2
    public Session(WindowManagerService service, IWindowSessionCallback callback,
            IInputMethodClient client, IInputContext inputContext) {
        mService = service;
        ......
    }

    void windowAddedLocked(String packageName) {
        mPackageName = packageName;
        mRelayoutTag = "relayoutWindow: " + mPackageName;
        if (mSurfaceSession == null) {
            if (WindowManagerService.localLOGV) Slog.v(
                TAG_WM, "First window added to " + this + ", creating SurfaceSession");
            mSurfaceSession = new SurfaceSession();
            if (SHOW_TRANSACTIONS) Slog.i(
                    TAG_WM, "  NEW SURFACE SESSION " + mSurfaceSession);
            // 代码 3
            mService.mSessions.add(this);
            if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
                mService.dispatchNewAnimatorScaleLocked(this);
            }
        }
        mNumWindow++;
    }

    void windowRemovedLocked() {
        mNumWindow--;
        killSessionLocked();
    }

    private void killSessionLocked() {
        if (mNumWindow > 0 || !mClientDead) {
            return;
        }
        // 代码 4
        mService.mSessions.remove(this);
        ......
    }

}
1.2.2 WindowState

WindowState 是 WMS 中一个重要的概念,在 WMS 中的一个 WindowState 对象就对应着一个应用进程中的 Window 对象。
我们在上篇文章 初步理解 Window 体系 最后,在调用WindowManagerGlobal.addView 方法时,经过一系列的方法调用,最后走到了 WindowManagerService.addWindow 方法中

addWindow.png

在 WindowManagerService.addWindow 方法中,会创建一个与 Window 对象对应的 WindowState 对象并调用 WindowState.attach 方法,然后将该 WindowState 对象添加到 WMS 的 mWindowMap Map 中

public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {

    final WindowHashMap mWindowMap = new WindowHashMap();

    public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
            ......

            final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], seq, attrs, viewVisibility, session.mUid,
                    session.mCanAddInternalSystemWindow);
            ......
            win.attach();
            mWindowMap.put(client.asBinder(), win);
            ......
            win.mToken.addWindow(win);
    }
}
class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState {
    final WindowManagerService mService;
    final WindowManagerPolicy mPolicy;
    final Context mContext;
    final Session mSession;
    final IWindow mClient;


    WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
           WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
           int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow) {
        mService = service;
        mSession = s;
        mClient = c;
        mAppOp = appOp;
        mToken = token;
        mAppToken = mToken.asAppWindowToken();

        ......
    }

    void attach() {
        if (localLOGV) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
        mSession.windowAddedLocked(mAttrs.packageName);
    }
    
    ......
}

在 WindowState 中保存了 WMS 对象、WMP 对象、Session 对象和 IWindow 对象,IWindow 对象就是与此 WindowState 对象相对应的在应用进程中的 Window 对象。

final WindowHashMap mWindowMap = new WindowHashMap(); 是一个 HashMap 的子类,key 是 IBinder,value 是 WindowState,用于保存 WMS 中所有的 WindowState 对象,

/**
 * Subclass of HashMap such that we can instruct the compiler to boost our thread priority when
 * locking this class. See makefile.
 */
class WindowHashMap extends HashMap<IBinder, WindowState> {
}
 mWindowMap.put(client.asBinder(), win);

IWindow client 对象,其实是 ViewRootImpl 中的 final W mWindow 成员,如下所示:

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
    final W mWindow;

    ......

    static class W extends IWindow.Stub {
        ......
    }
    ......
}
1.2.3 WindowToken

简单的理解,WindowToken 有两个作用:

  1. 在 WMS 中,一个 WindowToken 就代表着一个应用组件,应用组件包括:Activity、InputMethod 等。在 WMS 中,会将属于同一 WindowToken 的做统一处理,比如在对窗口进行 ZOrder 排序时,会将属于统一 WindowToken 的排在一起。
  2. WindowToken 也具有令牌的作用。应用组件在创建 Window 时都需要提供一个有效的 WindowToken 以表明自己的身份,并且窗口的类型必须与所持有的 WindowToken 类型保持一致。如果是系统类型的窗口,可以不用提供 WindowToken,WMS 会自动为该系统窗口隐式的创建 WindowToken,但是要求应用必须具有创建该系统类型窗口的权限

概念上有了初步的理解,我们来看下 WindowToken 的代码,如下所示,在 WindowToken 类中,最重要的其实是其中的成员属性

/**
 * Container of a set of related windows in the window manager. Often this is an AppWindowToken,
 * which is the handle for an Activity that it uses to display windows. For nested windows, there is
 * a WindowToken created for the parent window to manage its children.
 */
class WindowToken extends WindowContainer<WindowState> {
    private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowToken" : TAG_WM;

    // The window manager!
    protected final WindowManagerService mService;

    // The actual token.
    final IBinder token;

    // The type of window this token is for, as per WindowManager.LayoutParams.
    final int windowType;    
    ......

    // The display this token is on.
    protected DisplayContent mDisplayContent;
    ......

    WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty,
            DisplayContent dc, boolean ownerCanManageAppTokens) {
        mService = service;
        token = _token;
        windowType = type;
        mPersistOnEmpty = persistOnEmpty;
        mOwnerCanManageAppTokens = ownerCanManageAppTokens;
        onDisplayChanged(dc);
    }

    void onDisplayChanged(DisplayContent dc) {
        dc.reParentWindowToken(this);
        mDisplayContent = dc;

        // TODO(b/36740756): One day this should perhaps be hooked
        // up with goodToGo, so we don't move a window
        // to another display before the window behind
        // it is ready.
        SurfaceControl.openTransaction();
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final WindowState win = mChildren.get(i);
            win.mWinAnimator.updateLayerStackInTransaction();
        }
        SurfaceControl.closeTransaction();

        super.onDisplayChanged(dc);
    }

    ......

}

其实,对于 WMS 来讲,只要是一个 IBinder 对象都可以作为 Token,比如在之前分析添加 Window 时,调用 WindowManagerService.addWindow 方法时,传入的 Token 对象就是一个 IWindow.Stub 的对象。我们来看一下 WMS 中的 addWindowToken 方法,如下所示:

    @Override
    public void addWindowToken(IBinder binder, int type, int displayId) {
        if (!checkCallingPermission(MANAGE_APP_TOKENS, "addWindowToken()")) {
            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
        }

        synchronized(mWindowMap) {
            final DisplayContent dc = mRoot.getDisplayContentOrCreate(displayId);
            // 代码 1
            WindowToken token = dc.getWindowToken(binder);
            // 代码 2
            if (token != null) {
                Slog.w(TAG_WM, "addWindowToken: Attempted to add binder token: " + binder
                        + " for already created window token: " + token
                        + " displayId=" + displayId);
                return;
            }
            // 代码 3
            if (type == TYPE_WALLPAPER) {
                new WallpaperWindowToken(this, binder, true, dc,
                        true /* ownerCanManageAppTokens */);
            } else {
                new WindowToken(this, binder, type, true, dc, true /* ownerCanManageAppTokens */);
            }
        }
    }

AppWindowToken 是 WindowToken 的子类,与 WindowToken 不同的是,AppWindowToken 只可以用于 Activity 中的 Window 的 WindowToken。

1.2.4 DisplayContent

DisplayContent 是 Android 4.2 中支持 WiFi Display 多屏幕显示提出的一个概念,一个 DisplayContent 对象就代表着一块屏幕信息,一个 DisplayContent 对象用一个整型变量作为其 ID,系统默认屏幕所对应的 DisplayContent 对象 ID 是 Display.DEFAULT_DISPLAY。

属于同一个 DisplayContent 对象的 Window 对象会被绘制到同一块屏幕上,在添加窗口时可以指定对应的 DisplayContent 的 id,从而指定被添加到哪个 DisplayContent 上面。

DisplayContent 对象是由 DisplayManagerService 统一管理的,在此只做概念性的介绍,详细的关于 DisplayContent 和 DisplayManagerService 知识请查阅相关文档和资料

二. WMS 与 WindowManager 的通信

在上篇文章 初步理解 Window 体系 中,我们最后分析到了 ViewRootImpl,ViewRootImpl 是连接 WindowManager 和 WMS 的桥梁,自然他们之间的通信也是通过 ViewRootImpl 完成的。

2.1 ViewRootImpl 的成员变量

在 ViewRootImpl 中有两个个非常重要的成员变量:mWindowSessionmWindow,这两个变量都是用于 ViewRootImpl 和 WMS 通信使用的

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
    ......
    final IWindowSession mWindowSession;
    final W mWindow;
    ......

    public ViewRootImpl(Context context, Display display) {
        mContext = context;
        // 代码 1,通过 WindowManagerGlobal.getWindowSession() 方法得到一个 IWindowSession 对象
        mWindowSession = WindowManagerGlobal.getWindowSession();   
        ......
        // 代码 2,通过 W 构造方法直接创建一个新的 W 对象
        mWindow = new W(this);                                     
        ......
    }

    ......
}
2.1.1 IWindowSession

IWindowSession 是一个 AIDL 接口,其服务端进程是 WMS,客户端进程是应用进程,IWindowSession 的创建是在 WindowManagerGlobal 中,如下所示:

    public final class WindowManagerGlobal {

        private static IWindowManager sWindowManagerService;
        private static IWindowSession sWindowSession;
        ......
        public static IWindowManager getWindowManagerService() {
            synchronized (WindowManagerGlobal.class) {
                if (sWindowManagerService == null) {
                    sWindowManagerService = IWindowManager.Stub.asInterface(
                            ServiceManager.getService("window"));
                    try {
                        if (sWindowManagerService != null) {
                            ValueAnimator.setDurationScale(
                                    sWindowManagerService.getCurrentAnimatorScale());
                        }
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }
                }
                return sWindowManagerService;
            }
        }

        public static IWindowSession getWindowSession() {
            synchronized (WindowManagerGlobal.class) {
                if (sWindowSession == null) {
                    try {
                        InputMethodManager imm = InputMethodManager.getInstance();
                        IWindowManager windowManager = getWindowManagerService();
                        sWindowSession = windowManager.openSession(
                                new IWindowSessionCallback.Stub() {
                                    @Override
                                    public void onAnimatorScaleChanged(float scale) {
                                        ValueAnimator.setDurationScale(scale);
                                    }
                                },
                                imm.getClient(), imm.getInputContext());
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }
                }
                return sWindowSession;
            }
        }
        ......
    }

可以看到,其实 ViewRootImpl 中的 IWindowSession 对象实际对应着 WMS 中的 Session 对象。

WindowManagerGlobal 和 WMS 实现的是单方向的通信,都是通过如下图所示的 Binder 方式进行进程间通信的


WindowManagerGlobal.png
2.1.2 W

W 类是 ViewRootImpl 的一个内部类,实现了 IWindow 接口,IWindow 也是一个 AIDL 接口,可以猜想到,IWindow 接口是供 WMS 使用的,WSM 通过调用 IWindow 一些方法,通过 Binder 通信的方式,最后执行到了 W 中对应的方法中

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {


    static class W extends IWindow.Stub {
        private final WeakReference<ViewRootImpl> mViewAncestor;
        private final IWindowSession mWindowSession;

        W(ViewRootImpl viewAncestor) {
            mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
            mWindowSession = viewAncestor.mWindowSession;
        }

        @Override
        public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
                Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
                MergedConfiguration mergedConfiguration, Rect backDropFrame, boolean forceLayout,
                boolean alwaysConsumeNavBar, int displayId) {
            final ViewRootImpl viewAncestor = mViewAncestor.get();
            if (viewAncestor != null) {
                viewAncestor.dispatchResized(frame, overscanInsets, contentInsets,
                        visibleInsets, stableInsets, outsets, reportDraw, mergedConfiguration,
                        backDropFrame, forceLayout, alwaysConsumeNavBar, displayId);
            }
        }

        @Override
        public void moved(int newX, int newY) {
            final ViewRootImpl viewAncestor = mViewAncestor.get();
            if (viewAncestor != null) {
                viewAncestor.dispatchMoved(newX, newY);
            }
        }

        @Override
        public void dispatchAppVisibility(boolean visible) {
            final ViewRootImpl viewAncestor = mViewAncestor.get();
            if (viewAncestor != null) {
                viewAncestor.dispatchAppVisibility(visible);
            }
        }

        @Override
        public void dispatchGetNewSurface() {
            final ViewRootImpl viewAncestor = mViewAncestor.get();
            if (viewAncestor != null) {
                viewAncestor.dispatchGetNewSurface();
            }
        }

        @Override
        public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
            final ViewRootImpl viewAncestor = mViewAncestor.get();
            if (viewAncestor != null) {
                viewAncestor.windowFocusChanged(hasFocus, inTouchMode);
            }
        }

        private static int checkCallingPermission(String permission) {
            try {
                return ActivityManager.getService().checkPermission(
                        permission, Binder.getCallingPid(), Binder.getCallingUid());
            } catch (RemoteException e) {
                return PackageManager.PERMISSION_DENIED;
            }
        }
        
        ......

    }
}

比如在 ViewRootImpl#setView 方法中,有如下代码,在代码 1 处通过 mWindowSession 调用 addToDisplay 方法时,会将 mWindow 传入,最后传给 WMS,这样 WMS 便得到了一个 W 对象的实例对象。

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {

                ......

                int res; /* = WindowManagerImpl.ADD_OKAY; */

                // 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.
                requestLayout();
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }
                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    // 代码 1
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    mInputChannel = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }

                ......

            }
        }
    }
}

从上面代码可以看到,在 ViewRootImpl 中不仅实现了从 ViewRootImpl 向 WMS 的通信,也实现了从 WMS 向 ViewRootImpl 的通信,如下图所示


ViewRootImpl.png
上一篇 下一篇

猜你喜欢

热点阅读