Android 探索Dialog的本质

作者: 梵依然 | 来源:发表于2017-11-04 09:59 被阅读91次

    标签: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只是做了相应的封装。


    欢迎订阅我的公众号.jpg

    相关文章

      网友评论

        本文标题:Android 探索Dialog的本质

        本文链接:https://www.haomeiwen.com/subject/jhrfmxtx.html