为什么Application不能作为Dialog的Context

结论:

用activity作为context参数的dialog创建的windowManager是有token的,用service和application作为参数的windowManager的是没有token,所以会崩溃,这样做的好处是防止当你已经关闭页面了,或者已经打开其他app了,这个时候弹出一个操作弹窗,防止误操作。

源码分析

首先我们看dialog的构造方法的代码片段

        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
        w.setOnWindowSwipeDismissedCallback(() -> {
            if (mCancelable) {
                cancel();
            }
        });
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);

注意这里的mWindowManager,是通过context.getSystemService(Context.WINDOW_SERVICE);我们看这个方法在activity的实现

activity的getSystemService

  //Activity#
    @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);
    }

再看mWindowManager的在哪里赋值的

mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
    
    
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
    
    
            mWindow.setUiOptions(info.uiOptions);
        }
//···

//注释1
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());
        }
//注释2
 mWindowManager = mWindow.getWindowManager();

先看注释1处,注意这里的mWindow是phoneWindow,而且这个phonewindow的token不为空

		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);
    }
    //再看
    //WindowManagerImpl#createLocalWindowManager
    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
    
    
        return new WindowManagerImpl(mContext, parentWindow);
    }
    
    private WindowManagerImpl(Context context, Window parentWindow) {
    
    
        mContext = context;
        mParentWindow = parentWindow;
    }

可以看到创建了一个WindowManagerImpl,并且把PhoneWindow作为参数赋值给了WindowManagerImpl#mParentWindow,敲重点,这里的 WindowManagerImpl的mParentWindow是activity的phonewindow,而这个mParentWindow是有token的

我们再看Application的getSystemService

//先看Application的构造方法
		public Application() {
    
    
        super(null);
    }
//application中的实现是在ContextWrapper中
     Context mBase;
    public ContextWrapper(Context base) {
    
    
        mBase = base;
    }
    
    @Override
    public Object getSystemService(String name) {
    
    
        return mBase.getSystemService(name);
    }

//Context#getSystemService
public abstract @Nullable Object getSystemService(@ServiceName @NonNull String name);

可以看到Application的mBase是null,所以getSystemService返回的也是null,但是在dialog的构造方法里w.setWindowManager(mWindowManager, null, null);,而这个方法会创建一个WindowManagerImpl,类比刚才的activity,只不过这个时候传进来的window是没有token的,也就是此时的WindowManagerImpl#mParentWindow是没有token的

至此,windowManager的来源分析清楚。

dialog#show()

public void show() {
    
    
    	//······
    	mDecor = mWindow.getDecorView();
    	//······
    	WindowManager.LayoutParams l = mWindow.getAttributes();
        //······

        mWindowManager.addView(mDecor, l);
        
        //······
}

我们看mWindowManager.addView(mDecor, l);,第一部分我们已经分析了这个mWindowManager的创建(是WindowManagerImpl)。我们直接看WindowManagerImpl#addView

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

//接着看WindowManagerGlobal#addView
public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
    
    
        //······
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
  //注释1
        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;
            }
        }
    }

注意看注释1,因为这两种方式我们都是有window的,所以parentWindow != null成立,我们看parentWindow.adjustLayoutParamsForSubWindow(wparams);注意此时调用的是parentWindow是我们通过context.getSystemService(Context.WINDOW_SERVICE)获取的,也就是WindowManagerImpl.mParentWindow。

//Window#adjustLayoutParamsForSubWindow

void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
    
    
        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();
                }
            }
            //``````
        } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
    
    
            //``````
        } else {
    
    
          /注释1
            if (wp.token == null) {
    
    
                wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
            }
            //``````
        }
        //``````
    }

注意此时的wp.token的来源,是在Dialog.show()的时候取得mWindow.getAttributes(),此时的mWindow的token的空的,因此会调用注释1,而这个类又是我们第一部分讲的WindowManagerImpl.mParentWindow,所以当context时activity的时候,mAppToken不为null。

猜你喜欢

转载自blog.csdn.net/qq_31302233/article/details/115347382