Dialog中的Window添加过程解析

Dialog中的Window添加过程解析

Dialog一直作为一种依附在Activity上下文才能存在的窗口视图,那是否可以逃脱Activity的上下文,采用其他Context存在呢?答案是肯定的,Dialog完全可以不依赖Activity上下文存在,这里我们说的只是非Activity的Context,不是不依赖Context

  • Dialog的组成:

    Dialog是一种承载Window的容器,而Window的唯一实现便是PhoneWindow,Dialog的setContentView就是将布局文件的id传给PhoneWindow, PhoneWindow通过该布局id解析然后创建一个DecorView,这是一个继承FrameLayout的ViewGroup,每个Window都有一个WindowManagerImpl,这里所说的是每个非子window类型的window,因为子window是依附于父window,父子共用一个WindowManagerImpl,普通的Dialog的WindowManagerImpl与Activity是共用的

  • Dialog的创建代码:


    Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {

        if (createContextThemeWrapper) {

            if (themeResId == ResourceId.ID_NULL) {

                final TypedValue outValue = new TypedValue();

                context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);

                themeResId = outValue.resourceId;

            }

            mContext = new ContextThemeWrapper(context, themeResId);

        } else {

            mContext = context;

        }

        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); // 获取windowmanager

        final Window w = new PhoneWindow(mContext);

        mWindow = w;

        w.setCallback(this);

        w.setOnWindowDismissedCallback(this);

        w.setOnWindowSwipeDismissedCallback(() -> {

            if (mCancelable) {

                cancel();

            }

        });

        w.setWindowManager(mWindowManager, null, null);  // phonewindow 与 Windowmanager绑定

        w.setGravity(Gravity.CENTER);

        mListenersHandler = new ListenersHandler(this);

    }

再来看一下setContentView代码:


  Dialog.java

    public void setContentView(@LayoutRes int layoutResID) {

        mWindow.setContentView(layoutResID);

    }

PhoneWindow.java

public void setContentView(int layoutResID) {

        if (mContentParent == null) {

            installDecor();  // 创建DecorView与PhoneWindow绑定

        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {

            mContentParent.removeAllViews();

        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {

            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,

                    getContext());

            transitionTo(newScene);

        } else {

            mLayoutInflater.inflate(layoutResID, mContentParent);

        }

        mContentParent.requestApplyInsets();

        final Callback cb = getCallback();

        if (cb != null && !isDestroyed()) {

            cb.onContentChanged();

        }

        mContentParentExplicitlySet = true;

    }

以上也说明Dialog中视图的构建过程,其实就是Dialog持有一个Context、phonewindow、windowmanager,其中Context如果是Activity的话,因为Activity是集成ContextWrapTheme类,所以由Activity上下文构建的Dialog是和Activity的主题一样的。phonewindow持有视图解析后的View结构树DecorView,而WindowManager,如果是子窗口类型,便使用父窗口的windowmanager,如果是系统窗口,将独立创建一个windowmanager,其实每个WindowManager也是一个傀儡,真正执行View操作的的事WindowManagerGlobal,这个每个Application只有一个该单例对象,管理当前app的所有window以及view的跟新操作,可以看一下为什么WindowManager是个傀儡,这个类代码就几十行:


public final class WindowManagerImpl implements WindowManager {

    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

    private final Context mContext;

    private final Window mParentWindow;

    private IBinder mDefaultToken;

    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 WindowManagerImpl createPresentationWindowManager(Context displayContext) {

        return new WindowManagerImpl(displayContext, mParentWindow);

    }

    /**

    * Sets the window token to assign when none is specified by the client or

    * available from the parent window.

    *

    * @param token The default token to assign.

    */

    public void setDefaultToken(IBinder token) {

        mDefaultToken = token;

    }

    @Override

    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {

        applyDefaultToken(params);

        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);

    }

    @Override

    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {

        applyDefaultToken(params);

        mGlobal.updateViewLayout(view, params);

    }

    private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {

        // Only use the default token if we don't have a parent window.

        if (mDefaultToken != null && mParentWindow == null) {

            if (!(params instanceof WindowManager.LayoutParams)) {

                throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");

            }

            // Only use the default token if we don't already have a token.

            final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;

            if (wparams.token == null) {

                wparams.token = mDefaultToken;

            }

        }

    }

    @Override

    public void removeView(View view) {

        mGlobal.removeView(view, false);

    }

    @Override

    public void removeViewImmediate(View view) {

        mGlobal.removeView(view, true);

    }

    @Override

    public void requestAppKeyboardShortcuts(

            final KeyboardShortcutsReceiver receiver, int deviceId) {

        IResultReceiver resultReceiver = new IResultReceiver.Stub() {

            @Override

            public void send(int resultCode, Bundle resultData) throws RemoteException {

                List result =

                        resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY);

                receiver.onKeyboardShortcutsReceived(result);

            }

        };

        try {

            WindowManagerGlobal.getWindowManagerService()

                .requestAppKeyboardShortcuts(resultReceiver, deviceId);

        } catch (RemoteException e) {

        }

    }

    @Override

    public Display getDefaultDisplay() {

        return mContext.getDisplay();

    }

可以看到WindowMangaerGlobal这个类才是真正执行window视图添加跟新操作的,这个类有四个数据结构:


    private final ArrayList mViews = new ArrayList();  // 存储所有的DecorView

    private final ArrayList mRoots = new ArrayList(); // 每个窗口对应的视图树管理类

    private final ArrayList mParams =

            new ArrayList();  // 存储所有window的布局参数 包括窗口类型

    private final ArraySet mDyingViews = new ArraySet(); // 存储刚才remove的窗口中的view

为了方便梳理这个window窗口的添加过程,看一下dialog.show():


public void show() {

        if (mShowing) { // 是否正在展示状态

            if (mDecor != null) {  // 是否设置了view

                if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {  // 判断Context带有的主题是否包含ActionBar 或者用户是否手动设置了

                    mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);

                }

                mDecor.setVisibility(View.VISIBLE); // 设置DecorView可见 当View属于Visible 才会触发绘制流程

            }

            return;

        }

        mCanceled = false;

        if (!mCreated) {

            dispatchOnCreate(null);

        } else {

            // Fill the DecorView in on any configuration changes that

            // may have occured while it was removed from the WindowManager.

            final Configuration config = mContext.getResources().getConfiguration();

            mWindow.getDecorView().dispatchConfigurationChanged(config);  // 当app配置改变时回调OnConfigChange

        }

        onStart();

        mDecor = mWindow.getDecorView();

        if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { // 设置样式

            final ApplicationInfo info = mContext.getApplicationInfo();

            mWindow.setDefaultIcon(info.icon);

            mWindow.setDefaultLogo(info.logo);

            mActionBar = new WindowDecorActionBar(this);

        }

        WindowManager.LayoutParams l = mWindow.getAttributes();

        if ((l.softInputMode

                & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {

            WindowManager.LayoutParams nl = new WindowManager.LayoutParams();

            nl.copyFrom(l);

            nl.softInputMode |=

                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;

            l = nl;

        }

        mWindowManager.addView(mDecor, l);  // 重点  将DecorView添加给windowmanager

        mShowing = true;

        sendShowMessage();

    }

在Dialog创建的时候PhoneWindow与WindowManager绑定,在show的时候讲DecorView交给WindowManager,继续看 mWindowManager.addView(mDecor, l); 上面知道真正执行addView的事WindowManagerGlobal:


public void addView(View view, ViewGroup.LayoutParams params,

            Display display, Window parentWindow) {

            ...

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;

        if (parentWindow != null) {  // 判断是否有父窗口 如果context 会执行下面一句 因为父窗就是Activity所持有的phonewindow

            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;

          // 创建View视图管理

            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 {

                root.setView(view, wparams, panelParentView);  // 将DecorView交由VIewRootImp管理与Wms交互

            } catch (RuntimeException e) {

                // BadTokenException or InvalidDisplayException, clean up.

                if (index >= 0) {

                    removeViewLocked(index, true);

                }

                throw e;

            }

        }

    }

上面最关键的两行代码:


void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {  // 如果窗口类型是子窗口类型 将该DecorView的WindowToken赋值给该窗口管理器的布局参数

        CharSequence curTitle = wp.getTitle();

        if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&

                wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {

            if (wp.token == null) {

                View decor = peekDecorView();

                if (decor != null) {

                    wp.token = decor.getWindowToken();

                }

            }

            }

  1. root.setView(view, wparams, panelParentView); // 将DecorView交由VIewRootImp管理与Wms交互

    最后其实addView交给了ViewRootImpl,截取添加有本文有关的代码:


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

                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,

                            getHostVisibility(), mDisplay.getDisplayId(),

                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,

                            mAttachInfo.mOutsets, mInputChannel);

                if (res < WindowManagerGlobal.ADD_OKAY) {

                    mAttachInfo.mRootView = null;

                    mAdded = false;

                    mFallbackEventHandler.setView(null);

                    unscheduleTraversals();

                    setAccessibilityFocus(null, null);

                    switch (res) {

                        case WindowManagerGlobal.ADD_BAD_APP_TOKEN:

                        case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:

                            throw new WindowManager.BadTokenException(

                                    "Unable to add window -- token " + attrs.token

                                    + " is not valid; is your activity running?");

                        case WindowManagerGlobal.ADD_NOT_APP_TOKEN:

                            throw new WindowManager.BadTokenException(

                                    "Unable to add window -- token " + attrs.token

                                    + " is not for an application");

                        case WindowManagerGlobal.ADD_APP_EXITING:

                            throw new WindowManager.BadTokenException(

                                    "Unable to add window -- app for token " + attrs.token

                                    + " is exiting");

                        case WindowManagerGlobal.ADD_DUPLICATE_ADD:

                            throw new WindowManager.BadTokenException(

                                    "Unable to add window -- window " + mWindow

                                    + " has already been added");

                        case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:

                            // Silently ignore -- we would have just removed it

                            // right away, anyway.

                            return;

                        case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:

                            throw new WindowManager.BadTokenException("Unable to add window "

                                    + mWindow + " -- another window of type "

                                    + mWindowAttributes.type + " already exists");

                        case WindowManagerGlobal.ADD_PERMISSION_DENIED:

                            throw new WindowManager.BadTokenException("Unable to add window "

                                    + mWindow + " -- permission denied for window type "

                                    + mWindowAttributes.type);

                        case WindowManagerGlobal.ADD_INVALID_DISPLAY:

                            throw new WindowManager.InvalidDisplayException("Unable to add window "

                                    + mWindow + " -- the specified display can not be found");

                        case WindowManagerGlobal.ADD_INVALID_TYPE:

                            throw new WindowManager.InvalidDisplayException("Unable to add window "

                                    + mWindow + " -- the specified window type "

                                    + mWindowAttributes.type + " is not valid");

                    }

                    throw new RuntimeException(

                            "Unable to add window -- unknown error code " + res);

                }

以上代码是ViewRootImpl中setView的代码其中最重要的就是res的赋值,res默认是window可以添加的 WindowManagerImpl.ADD_OKAY:


  res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,

                            getHostVisibility(), mDisplay.getDisplayId(),

                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,

                            mAttachInfo.mOutsets, mInputChannel);

  • mWindowSession是一个用户与Wms交互的接口,源码查看只有十个左右的接口:

interface IWindowSession {

    int add(IWindow window, int seq, in WindowManager.LayoutParams attrs,

            in int viewVisibility, out Rect outContentInsets,

            out InputChannel outInputChannel);

    int addToDisplay(IWindow window, int seq, in WindowManager.LayoutParams attrs,

            in int viewVisibility, in int layerStackId, out Rect outContentInsets,

            out InputChannel outInputChannel);

    int addWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs,

            in int viewVisibility, out Rect outContentInsets);

    int addToDisplayWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs,

            in int viewVisibility, in int layerStackId, out Rect outContentInsets);

    void remove(IWindow window);



    int relayout(IWindow window, int seq, in WindowManager.LayoutParams attrs,

            int requestedWidth, int requestedHeight, int viewVisibility,

            int flags, out Rect outFrame,

            out Rect outContentInsets, out Rect outVisibleInsets,

            out Configuration outConfig, out Surface outSurface);

    void performDeferredDestroy(IWindow window);

    boolean outOfMemory(IWindow window);

    void setTransparentRegion(IWindow window, in Region region);



    void setInsets(IWindow window, int touchableInsets, in Rect contentInsets,

            in Rect visibleInsets, in Region touchableRegion);



    void getDisplayFrame(IWindow window, out Rect outDisplayFrame);



    void finishDrawing(IWindow window);



    void setInTouchMode(boolean showFocus);

    boolean getInTouchMode();



    boolean performHapticFeedback(IWindow window, int effectId, boolean always);



    IBinder prepareDrag(IWindow window, int flags,

            int thumbnailWidth, int thumbnailHeight, out Surface outSurface);



    boolean performDrag(IWindow window, IBinder dragToken, float touchX, float touchY,

            float thumbCenterX, float thumbCenterY, in ClipData data);

void reportDropResult(IWindow window, boolean consumed);



    void dragRecipientEntered(IWindow window);

    void dragRecipientExited(IWindow window);

    void setWallpaperPosition(IBinder windowToken, float x, float y, float xstep, float ystep);



    void wallpaperOffsetsComplete(IBinder window);



    Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,

            int z, in Bundle extras, boolean sync)



    void wallpaperCommandComplete(IBinder window, in Bundle result);

    void setUniverseTransform(IBinder window, float alpha, float offx, float offy,

            float dsdx, float dtdx, float dsdy, float dtdy);



    void onRectangleOnScreenRequested(IBinder token, in Rect rectangle, boolean immediate);

}

而此处mWindowSession真正是实现是


class Session extends IWindowSession.Stub implements IBinder.DeathRecipient

每个application对应一个IwindowSession的实现类Session对象 session很明显是Binder的服务端对象,而真正实现窗口添加的还是WindowManagerService,下面是Session添加window的代码:


Session.java

    @Override

    public int add(IWindow window, int seq, WindowManager.LayoutParams attrs,

            int viewVisibility, Rect outContentInsets, Rect outStableInsets,

            InputChannel outInputChannel) {

        return addToDisplay(window, seq, attrs, viewVisibility, Display.DEFAULT_DISPLAY,

                new Rect() /* outFrame */, outContentInsets, outStableInsets, null /* outOutsets */,

                new DisplayCutout.ParcelableWrapper()  /* cutout */, outInputChannel);

    }

因此可以得出Session每个application进行窗口操作的唯一对应,想要进行窗口的操作必须通过Session来与Wms交互,因此下面一张图很清楚描述这一关系:

Session关系图

重点来了:

窗口的添加从WindowManager - > WIndowManagerGlobal - > ViewRootImpl -> Session - > WindowMangagerService 这一系列检查包装最终交给Wms,wms的添加窗口操作如下,这个添加方法较长 ,只分析跟本来关联较大代码,拆开分析:

  • 方法名 关注一下这个返回值是一个窗口添加结果返回值

public int addWindow(Session session, IWindow client, int seq,

            LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,

            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,

            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel)

  • 窗口 type 判断

// 判断该window是否权限允许

if (!displayContent.hasAccess(session.mUid)

                    && !mDisplayManagerInternal.isUidPresentOnDisplay(session.mUid, displayId)) {

                Slog.w(TAG_WM, "Attempted to add window to a display for which the application "

                        + "does not have access: " + displayId + ".  Aborting.");

                return WindowManagerGlobal.ADD_INVALID_DISPLAY;

            }

// 判断这个client是否已经存在 这个Client 是每个Wms在客户端回调的Binder类

            if (mWindowMap.containsKey(client.asBinder())) {

                Slog.w(TAG_WM, "Window " + client + " is already added");

                return WindowManagerGlobal.ADD_DUPLICATE_ADD;

            }

// 判断是否是子窗口类型的窗口

            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {

          // 如果是Dialog的Context是activity 且未修改dialog的窗口类型 下面这句代码就会获取到Activity的window

                parentWindow = windowForClientLocked(null, attrs.token, false);

                if (parentWindow == null) {  // 如果为null Activity的窗口已经不再显示(可能销毁 可能pause)

                    Slog.w(TAG_WM, "Attempted to add window with token that is not a window: "

                          + attrs.token + ".  Aborting.");

                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;

                }

                // 判断父window type如果也是子窗口类型window 返回ADD_BAD_SUBWINDOW_TOKEN

                if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW

                        && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {

                    Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: "

                            + attrs.token + ".  Aborting.");

                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;

                }

            }

以上就是为啥你在使用Window时进场报错的最终触发点,而这里的错误只是提供给google工程师看的,我们看到的是在ViewRootImpl中addView时根据IwindowSession.addDisplay()返回值抛给我们的:


int  res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,

                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,

                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,

                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);

if (res < WindowManagerGlobal.ADD_OKAY) {

                    mAttachInfo.mRootView = null;

                    mAdded = false;

                    mFallbackEventHandler.setView(null);

                    unscheduleTraversals();

                    setAccessibilityFocus(null, null);

                    switch (res) {

                        case WindowManagerGlobal.ADD_BAD_APP_TOKEN:

                        case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:

                            throw new WindowManager.BadTokenException(

                                    "Unable to add window -- token " + attrs.token

                                    + " is not valid; is your activity running?");

                        case WindowManagerGlobal.ADD_NOT_APP_TOKEN:

                            throw new WindowManager.BadTokenException(

                                    "Unable to add window -- token " + attrs.token

                                    + " is not for an application");

                        case WindowManagerGlobal.ADD_APP_EXITING:

                            throw new WindowManager.BadTokenException(

                                    "Unable to add window -- app for token " + attrs.token

                                    + " is exiting");

                        case WindowManagerGlobal.ADD_DUPLICATE_ADD:

                            throw new WindowManager.BadTokenException(

                                    "Unable to add window -- window " + mWindow

                                    + " has already been added");

                        case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:

                            // Silently ignore -- we would have just removed it

                            // right away, anyway.

                            return;

                        case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:

                            throw new WindowManager.BadTokenException("Unable to add window "

                                    + mWindow + " -- another window of type "

                                    + mWindowAttributes.type + " already exists");

                        case WindowManagerGlobal.ADD_PERMISSION_DENIED:

                            throw new WindowManager.BadTokenException("Unable to add window "

                                    + mWindow + " -- permission denied for window type "

                                    + mWindowAttributes.type);

                        case WindowManagerGlobal.ADD_INVALID_DISPLAY:

                            throw new WindowManager.InvalidDisplayException("Unable to add window "

                                    + mWindow + " -- the specified display can not be found");

                        case WindowManagerGlobal.ADD_INVALID_TYPE:

                            throw new WindowManager.InvalidDisplayException("Unable to add window "

                                    + mWindow + " -- the specified window type "

                                    + mWindowAttributes.type + " is not valid");

                    }

                    throw new RuntimeException(

                            "Unable to add window -- unknown error code " + res);

                }

以上的错误信息就是我们经常在as的logcat看到的 ,那么既然windowmanager添加之后了解到与wms通信的服务端,那么wms是如何与View通信的呢?其实ViewRootImpl中有一个匿名内部类W类,该类继承IWindow.stub,我们知道binder通信是客户端与服务端是相对的,此时WMS想发消息给客户端,也只能通过BInder了,那WMS持有的客户端的Binder对象便是W类的远程代理,这个bInder代理对象就是在Session.addToDisplay时的参数通过binder通信传过去的。

你可能感兴趣的:(Dialog中的Window添加过程解析)