Dialog源码学习

作者: 小白龙vip | 来源:发表于2016-08-09 19:58 被阅读489次

恍然大悟,Java 万物皆是对象的真谛,当然Android也不列外,其实我们在写程序的时候也是在给写每一个对象; 所以我们在Android Studio中所看到的Java源码也是一个个对象的封装体;

一时如蒙雷击,我们看源码也是如此;

  1. 带着问题进入
  2. 如果是我写我该如何写?
  3. 每一个方法是如何调用?

即然如此我们便能无需太多的教学文档,就能驾驭Android中的基本用法。还能学到一些设计模式和一些写代码的技巧;

这是我第一次看试着把自己看的东西写成博客。写得不好请观者见谅;

Dialog继承结构

UML建模图

创建
创建 - 构造方法 - 重点

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

        // 构建显示时所用的Window;
        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);
    }

解说一下重要的代码

 // 1.获取Window管理器
 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
 //2.创建一个 Windows 来供 Dialog 挂载布局
 final Window w = new PhoneWindow(mContext);
// 3.设置Window和 窗口管理器关联
w.setWindowManager(mWindowManager, null, null);
// 4.
mListenersHandler = new ListenersHandler(this);

只要简单的3步就完成了Dialog和布局所显示的关联。
WindowManager是用来干什么的呢?
其实和我们的Activity一样,我们需要写XML布局,然后通过LayoutInflater转化为View设置给Acvitity是一样的。我们都需要一个显示的窗口;在Android里面就叫window;

简单来说Window是抽象类,具体实现是PhoneWindow,通过WindowManager就可以创建Window。WindowManager是外界访问Window的入口,但是Window的具体实现是在WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。所有的视图例如Activity、Dialog、Toast都是附加在Window上的。

Window w = new PhoneWindow(mContext);
具体关系如下图:

Window图

需要深入了解Window可以看老罗文章:
Android应用程序窗口(Activity)的窗口对象(Window)的创建过程分析

显示

在写码的时候发现一个问题,我自己继承写了一个dialog

 MyDialog dialog = new MyDialog(context, R.style.default_dialog_style);

为什么不会调用生命周期中的OnCreate()方法?一直以为在构建的时候就应该创建。后来才发现自己理解错误,要调用show()方法才会显示。如下

 public void show() {

        // 1.判断是否显示
   
     ··· 部分代码
    
        mCanceled = false;  
        // 2.调用OnCreate(); 
        if (!mCreated) {
            dispatchOnCreate(null);
        }
        // 3.启动
        onStart();
       // 4. 获取到Window跟布局
        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);
        }

        ··· 部分代码 -- 设置布局参数
        try {
         // 5. 添加 布局到 布局管理器 中 
            mWindowManager.addView(mDecor, l);
            mShowing = true;
            // 调用 show()示监听回调
            sendShowMessage();
        } finally {
        }
    }

可以看到第2条中如果如果 mCreated=false才会回调dispatchOnCreate()继续深入:

 void dispatchOnCreate(Bundle savedInstanceState) {
        if (!mCreated) {
            onCreate(savedInstanceState);
            mCreated = true;
        }
    }

// 空实现
 protected void onCreate(Bundle savedInstanceState) {
    }

由上代码可以看到onCreate是空实现,我们在自己定义Dialog时复写onCreate()才会有实际的作用;

接着看OnStart(); 也无太多实际作用。设置显示mActionBar动画;

    /**
     * Called when the dialog is starting.
     */
    protected void onStart() {
        if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true);
    }

那么我们是在哪里设置布局的? 如下;布局是设置在mWindow中的;

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

一般我们是复写在OnCreat()中。或者直接dialog.setContentView(R.layout.layout_view)来设置布局;
接着把拿到mDecor设置个布局管理器中。就是源码中的第5条。那么我们写得布局就显示在我们的程序上面了。由于我们直接加载window。因而我们可以直接显示在所以布局的顶部;

关闭对话框

所有对话框都实现了一个接口 DialogInterface

public interface DialogInterface{

... 省略 ...

    public void cancel();
    public void dismiss();
    
... 省略 ...

}

Dialog中还有一个方法hide();所以让一个对话框消失有三种方法;我们来看看有什么不同:

hide() ---- 只是让布局看不到,但是没有关闭它;并没有移除屏幕

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

dismiss

 @Override
    public void dismiss() {
        // 判断是否在同一线程 
        if (Looper.myLooper() == mHandler.getLooper()) {
            dismissDialog();
        } else {
            //不在同一线程。发送关闭的mHandler来关闭对话框
            mHandler.post(mDismissAction);
        }
    }

// 销毁对话框
 void dismissDialog() {
        if (mDecor == null || !mShowing) {
            return;
        }

        if (mWindow.isDestroyed()) {
            return;
        }

        // 1. 移除 WindowManger 中 Decor
        try {
            mWindowManager.removeViewImmediate(mDecor);
        } finally {
            //2. 销毁必要组件, 
            if (mActionMode != null) {
                mActionMode.finish();
            }
            mDecor = null;
            mWindow.closeAllPanels();
            //3. 回调 onStop方法
            onStop();
            mShowing = false;

            // 4. 发送 销毁 通知的监听 最后介绍:
            sendDismissMessage();
        }
    }

注意:官方文档上同时提到了一点注意事项:如果你要进行一些清理工作的话,不要在重写dismiss函数,而应该在onStop函数中进行这些清理工作

cancel 和 dismiss 区别

这样看下来。除了hide()和其他两个方法有点区别,其他两个好像就没有太大区别了:先说结论吧:
Hide()只是隐藏对话框;dismiss()就是关闭并结束对话框的方法;
cancel()如果没有设置setOnCancelListener()才会和dismiss()有所不同;
Dialog内部有这样一段代码


// 设置打开关闭监听
    private static final class ListenersHandler extends Handler {
        private WeakReference<DialogInterface> mDialog;

        public ListenersHandler(Dialog dialog) {
            mDialog = new WeakReference<DialogInterface>(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设置了相应的回调监听,会回调相应的监听方法;
我们在来看看cancel方法:

    public void cancel() {
        if (!mCanceled && mCancelMessage != null) {
            mCanceled = true;
            // Obtain a new message so this dialog can be re-used
            Message.obtain(mCancelMessage).sendToTarget();
        }
        //回调dismis方法; 
        dismiss();
    }

如果你设置了回调监听

  public void setOnCancelListener(final OnCancelListener listener)

上面可以得出的结论cancel()包含dismiss()cancel()会发送关闭的消息通知ListenersHandler去回调相应的监听;

Dialog的源码大概就是如此了,其实仔细回想下来还是非常简单的,就是对源码的包装,window的加载而已。难的是加载和显示的过程。这些我就不去深入研究了。
下一篇准备些Dialog用到的设计模式;里面包含messagehandle的用法,所以还是有许多可以学习的。

推荐:

一个不错Dialog开源项目

相关文章

网友评论

    本文标题:Dialog源码学习

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