源码分析->可以用applicationContext创建显示Dialog吗?

可以用applicationContext创建显示Dialog吗?

答案是不可以,估计很多同学知道不可以,但不知道为什么吧,下面我们一起来分析。

源码分析基于Android-28

1.Dialog的构造方法

主要工作:
(1)context.getSystemService,获取WindowManagerImpl对象,这里很关键,会对Dialog的显示造成影响,后面会分析,先记住;
(2)构建PhoneWindow,赋值给成员变量mWindow,Dialog和PopupWindow的不同,前者有自己的PhoneWindow;

 Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        ......
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        ......
    }
2.Application获取WINDOW_SERVICE

是通过成员变量mBase(ContextImpl)来获取WINDOW_SERVICE;

    @Override
    public Object getSystemService(String name) {
        return mBase.getSystemService(name);
    }
ContextImpl.getSystemService

SystemServiceRegistry保存了各种服务代理,常见的WINDOW_SERVICE以及ACTIVITY_SERVICE都是保存在这里;

    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }
SystemServiceRegistry静态代码块

注册WINDOW_SERVICE到HashMap当中,WINDOW_SERVICE其实是一个
WindowManagerImpl,它的mParentWindow属性默认为空,这个也很关键,先记住。

    static {
        ......
        registerService(Context.WINDOW_SERVICE, WindowManager.class,
                new CachedServiceFetcher() {
            @Override
            public WindowManager createService(ContextImpl ctx) {
                return new WindowManagerImpl(ctx);
            }});
        ......
        }
接着我们看下Activity获取的WINDOW_SERVICE有什么不同。
3.Activity.getSystemService

Activity重写了getSystemService方法,直接返回mWindowManager,那么这个是在哪里创建的呢?答案是Activity.attach方法里面;

    @Override
    public Object getSystemService(@ServiceName @NonNull String name) {
        if (getBaseContext() == null) {
            throw new IllegalStateException(
                    "System services not available to Activities before onCreate()");
        }

        if (WINDOW_SERVICE.equals(name)) {
            return mWindowManager;
        } else if (SEARCH_SERVICE.equals(name)) {
            ensureSearchManager();
            return mSearchManager;
        }
        return super.getSystemService(name);
    }
Activity.attach

主要工作:
(1)构建PhoneWindow;
(2) mWindow.setWindowManager,将mToken赋值给mWindow 成员变量的mAppToken ,这个值也很关键,在show Dialog的时候会用到,并且内部会构建一个mParentWindow不为空的WindowManagerImpl,mParentWindow就是Activity的PhoneWindow;
(3)将mWindow的WindowManager赋值给mWindowManager;

    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) {
       mWindow = new PhoneWindow(this, window, activityConfigCallback);
         ......
        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();
         ......
    }
PhoneWindow.setWindowManager

主要工作:
(1)给成员变量mAppToken 赋值;
(2)createLocalWindowManager,构建WindowManagerImpl,其成员变量mParentWindow就是Activity的PhoneWindow;

Tips:那么在Activity中获取到的WINDOW_SERVICE,mParentWindow不为空,并且mParentWindow带有token;
    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
       ......
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }
4.Dialog.show

主要工作:
调用 mWindowManager.addView,通知WMS添加Dialog。这里的mWindowManager很关键,Activity的WINDOW_SERVICE对象的mParentWindow属性不为空;

 public void show() {
        ......
        mDecor = mWindow.getDecorView();
        ......
        WindowManager.LayoutParams l = mWindow.getAttributes();
        ......
        mWindowManager.addView(mDecor, l);
        ......
    }
5.WindowManagerImpl.addView触发->WindowManagerGlobal.addView,

主要工作:
(1)如果是在Activity构建的Dialog,从WindowManagerImpl传递过来的parentWindow不为空,那么就会调用adjustLayoutParamsForSubWindow给params的token赋值,这里的parentWindow是Activity的PhoneWindow;
(2)构建ViewRootImpl,ViewRootImpl是负责view树的绘制工作;
(3)调用ViewRootImpl.setView方法,开始绘制view树;

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ......
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
           } 
            ......
            root = new ViewRootImpl(view.getContext(), display);
            ......            
            root.setView(view, wparams, panelParentView);
            ......     
    }
6.Window.adjustLayoutParamsForSubWindow

主要工作:
把Activity PhoneWindow的mAppToken 赋值给wp.token,

Tips:当context是Application时,该方法不会被调用,则wp.token为空。
void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
        CharSequence curTitle = wp.getTitle();
         ......
            if (wp.token == null) {
                wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
            }
       ......
    }
7.最后看下判断token的逻辑
WindowManagerGlobal.addView0触发-> ViewRootImpl.setView触发->
WMS.addWindow

判断token是否为空,则返回WindowManagerGlobal.ADD_BAD_APP_TOKEN,回到ViewRootImpl.setView;

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) {
            ......
            if (token == null) {
                if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
                    Slog.w(TAG_WM, "Attempted to add application window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;//(1)
                }
              ......
ViewRootImpl.setView,

(1)通知WMS添加窗口,并等待返回结果,
(2)判断返回结果,如果token为空则会抛出WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not valid; is your activity running?")
异常,相信大家对于这个异常都很熟悉。

   public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {         
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    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?");//(2)

至此就大家应该能清楚知道,为什么不能用applicationContext创建对话框。

8.总结

(1)在Application和Activity获取到的WINDOW_SERVICE有所不同,前者的WindowManagerImpl mParentView为空,后者不为空,而且就是Activity的PhoneWindow;
(2)如果mParentView不会为空,则把PhoneWindow appToken赋值给Dialog的wp.token;
(3)如果无法通过以上第二点来赋值,那么就会抛出WindowManager.BadTokenException异常;
(4)Dialog ,Activity分别都有各自的PhoneWindow,前者appToken为空,后者不会为空,在看源码的时候注意不要把两个搞混了;
(5)Dialog 的类型是TYPE_APPLICATION,属于应用窗口类型。
(6)appToken是Token类型,是一个Binder对象,是在启动Activity创建ActivityRecord的时候创建的,它持有Activity的弱引用;这个token创建后会发送到WMS,在WMS中封装成WindowToken,并存在一个HashMap
(7)拥有token的context可以创建界面、进行UI操作,而没有token的context如service、Application,是不允许添加view到屏幕上的;


时序图.png

以上分析有不对的地方,请指出,互相学习,谢谢哦!

你可能感兴趣的:(源码分析->可以用applicationContext创建显示Dialog吗?)