Android Dialog源码分析

1.当我们查看android dialog源码,首先查看dialog的构造方法,初始化相关数据。由源码可知Dialog获取当前的themeId,通过 new ContextThemeWrapper(context, themeResId);设置主题返回Context对象。通过new PhoneWindow()创建Window对象,设置Window相关方法,

 public Dialog(@NonNull Context context) {
        this(context, 0, true);
}
    
 public Dialog(@NonNull Context context, @StyleRes int themeResId) {
        this(context, themeResId, true);
}
 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);

        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);

        mListenersHandler = new ListenersHandler(this);
    }  
    

Dialog创建类似activity创建,通过new PhoneWidow对象,我们查看以下源码调用setContentView()方法可知通过Window实现类PhoneWindow将布局文件添加到DectorView中。

//以下是Window相关的代码
public void setContentView(@LayoutRes int layoutResID) {
        mWindow.setContentView(layoutResID);
    }
  
  //以下是PhoneWindow的相关的代码  
 public void setContentView(int layoutResID) {
       
        if (mContentParent == null) {
            installDecor();
        } 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;
    }

2.查看dialog的show()方法,在show()方法中,判断当前dialog是否已经显示,调用oncreate(),onStart(),获取getDecorView(),通过windowManager添加DectorView,发送消息给主线程通知弹窗已经show.

    public void show() {
        if (mShowing) {
            if (mDecor != null) {
                if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
                    mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
                }
                mDecor.setVisibility(View.VISIBLE);
            }
            return;
        }

        mCanceled = false;

        if (!mCreated) {
            dispatchOnCreate(null);
        } else {
           
            final Configuration config = mContext.getResources().getConfiguration();
            mWindow.getDecorView().dispatchConfigurationChanged(config);
        }

        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);
        mShowing = true;

        sendShowMessage();
    }

当我们查看dismissDisDialog()方法时,可知通过widowManager移除DectorView,mWindowManager.removeViewImmediate(mDecor);

void dismissDialog() {
        if (mDecor == null || !mShowing) {
            return;
        }

        if (mWindow.isDestroyed()) {
            Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
            return;
        }

        try {
            mWindowManager.removeViewImmediate(mDecor);
        } finally {
            if (mActionMode != null) {
                mActionMode.finish();
            }
            mDecor = null;
            mWindow.closeAllPanels();
            onStop();
            mShowing = false;

            sendDismissMessage();
        }
    }

以下是消息处理,弹窗显示,取消,消失。

Private static final class ListenersHandler extends Handler {
        private final WeakReference<DialogInterface> mDialog;

        public ListenersHandler(Dialog dialog) {
            mDialog = new WeakReference<>(dialog);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case DISMISS:
                    ((OnDismissListener) msg.obj).onDismiss(mDialog.get());
                    break;
                case CANCEL:
                    ((OnCancelListener) msg.obj).onCancel(mDialog.get());
                    break;
                case SHOW:
                    ((OnShowListener) msg.obj).onShow(mDialog.get());
                    break;
            }
        }
    }

以上就是Dialog创建和消失的过程,普通Dialog的Context必须通过Activity持有的Context才可以,不然会报异常,报错信息是没有应用token所致,而应用token是Activity持有的,系统弹窗不需要token,在WindowManager.LayoutParams中的type表示window的类型,系统层级范围在2000~2999。若传Application 的Context对象,可以使用如下所示:

  dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);

在Manifest声明权限可以使用系统Window

 <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

推荐阅读更多精彩内容