标签:android dialog

本文中涉及的代码基于Android API 25.
本文主要探索Dialog的本质以及它是如何被显示和隐藏的。

Dialog显示

构造Dialog最终会调用其参数最多的重载函数

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        if (createContextThemeWrapper) {
            if (themeResId == 0) {
                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.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);

        mListenersHandler = new ListenersHandler(this);
    }

在方法中获取系统WindowsManager,初始化mWindows为PhoneWindow。

在Dialog构造完毕完毕之后,调用show()在屏幕上显示dialog。展示的window处于application层,并且是不透明状态。不能通过重写这个方法来初始化,而应该在onStart()方法中进行。

public void show() {
        if (mShowing) {//判断是否是显示状态
            if (mDecor != null) {//判断mDecor是否已经初始化
                if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
                    mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
                }
                mDecor.setVisibility(View.VISIBLE);
            }
            return;
        }

        mCanceled = false;

        if (!mCreated) {//还没创建的话最终会调用onCreate()方法
            dispatchOnCreate(null);
        } else {
            //当dialog从windowmanager中移除之后,如果系统configuration 
            //已经发生变化,再次展示dialog的时候需要将config传入
            //mWindow的decorview中以做出相应配置改变
            final Configuration config = mContext.getResources().getConfiguration();
            mWindow.getDecorView().dispatchConfigurationChanged(config);
        }

        onStart();
        mDecor = mWindow.getDecorView();

        //actionBar的一些处理,忽略不表
        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();
    }

dispatchOnCreate()方法会调用onCreate方法:

protected void onCreate(Bundle savedInstanceState) {
    }

在dialog方法中onCreate()方法为空,该方法和activity中的onCreate类似,需要用户在该方法中实现dialog初始化,包括调用setContent()方法。

public void setContentView(@LayoutRes int layoutResID) {
        mWindow.setContentView(layoutResID);
    }

setContentView()方法最终调用window的setContent()方法来初始化view。

之后最关键的地方就是调用mWindowManager的addView方法将mWindow的mDecor添加进来,也就是将dialog展示在界面上。

private void sendShowMessage() {
        if (mShowMessage != null) {
            // Obtain a new message so this dialog can be re-used
            Message.obtain(mShowMessage).sendToTarget();
        }
    }

如果mShowMessage已经被初始化,在绑定的handler中处理该message。
那么mShowMessage的是在哪被初始化的呢?

public void setOnShowListener(@Nullable OnShowListener listener) {
        if (listener != null) {
            mShowMessage = mListenersHandler.obtainMessage(SHOW, listener);
        } else {
            mShowMessage = null;
        }
    }

mShowMessage是被mListenerHandler初始化。
mListenrHandler的实现如下:

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

mListenerHandler内部持有一个WeakReference类型的当前diaolog对象,防止内存泄露。所以onDismiss/onCancelo/nShow的参数有可能为null,使用的时候需要注意下。

隐藏Dialog

调用hide()方法可以隐藏Dialog,注意这里是隐藏不是dimiss。

public void hide() {
        if (mDecor != null) {
            mDecor.setVisibility(View.GONE);
        }
    }

实现方式只是将window的mDecor隐藏掉,也就是说将view设置为gone。

Dialog消失

调用dismiss()方法使Dialog消失。该方法在是线程安全的,意思是说在非UI线程也可调用。注意不能在重写此方法来清理内存对象,而应该在onStop()方法中。

public void dismiss() {
        if (Looper.myLooper() == mHandler.getLooper()) {//调用者在UI线程
            dismissDialog();
        } else {//在非UI线程,在与主线程绑定的的mHandler中调用mDismissAction
            mHandler.post(mDismissAction);
        }
    }
    
    void dismissDialog() {
        if (mDecor == null || !mShowing) {//如果mDecor还没有初始化或者dialog还没有显示直接return。
            return;
        }

        if (mWindow.isDestroyed()) {//mWindow已经被destroyed
            return;
        }

        try {
            mWindowManager.removeViewImmediate(mDecor);//最重要的事
        } finally {
            if (mActionMode != null) {
                mActionMode.finish();
            }
            mDecor = null;
            mWindow.closeAllPanels();
            onStop();
            mShowing = false;

            sendDismissMessage();
        }
    }

该方法中主要做的事情就是从mWindowManager中移除mDecor。
sendDismissMessage()与上述的sendShowMessage()方法类似,不再赘述。

总结

从此可以看出Dialog的本质其实是一个管理以及封装类。dialog的显示以及隐藏都是通过WindowManager中添加或移除view来实现,dialog只是做了相应的封装