Dialog源码分析

来源:互联网 发布:java pointer 编辑:程序博客网 时间:2024/06/05 20:01

构造函数入手

public Dialog(Context context) {    this(context, 0, true);}public Dialog(Context context, int theme) {    this(context, theme, true);}Dialog(Context context, int theme, boolean createContextThemeWrapper) {    if (createContextThemeWrapper) {        if (theme == 0) {            TypedValue outValue = new TypedValue();            context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme,                    outValue, true);            theme = outValue.resourceId;        }        mContext = new ContextThemeWrapper(context, theme);    } else {        mContext = context;    }    mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);    Window w = PolicyManager.makeNewWindow(mContext);    mWindow = w;    w.setCallback(this);    w.setOnWindowDismissedCallback(this);    w.setWindowManager(mWindowManager, null, null);    w.setGravity(Gravity.CENTER);    mListenersHandler = new ListenersHandler(this);}

对外提供了两个构造函数,最终都是调用了具有三个参数的构造函数:

  1. createContextThemeWrapper默认传递过来的就是true,所以会进入if (createContextThemeWrapper) {}内部

  2. theme:假如调用了的只有一个参数的构造函数,会默认传递0进来,这会使得Dialog最终使用系统提供的dialogTheme作为主题

  3. 创建了一个Window对象,并在本地保存了一个mWindowManager的引用

show()方法

接下来,看下它的显示过程(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);    }    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;    }    try {        mWindowManager.addView(mDecor, l);        mShowing = true;        sendShowMessage();    } finally {    }}

这里边有几个步骤:
1. mCreated默认值为false,于是会调用dispatchOnCreate(null);方法,进入该方法

// internal method to make sure mcreated is set properly without requiring// users to call through to super in onCreatevoid dispatchOnCreate(Bundle savedInstanceState) {    if (!mCreated) {        onCreate(savedInstanceState);        mCreated = true;    }}

主要做了两个操作:
- 调用onCreate方法
- 将mCreated标志设置为true

再进入onCreate方法查看

/** * Similar to {@link Activity#onCreate}, you should initialize your dialog * in this method, including calling {@link #setContentView}. * @param savedInstanceState If this dialog is being reinitalized after a *     the hosting activity was previously shut down, holds the result from *     the most recent call to {@link #onSaveInstanceState}, or null if this *     is the first time. */protected void onCreate(Bundle savedInstanceState) {}

这是个空方法,官方给的解析原意大概是:

类似与Activity的onCreate方法,你应该在这个方法里调用setContentView设置需要显示的视图和初始化Dialog

所以,我们在使用Dialog时,需要在这个重写该方法,并调用setContentView设置视图

那setContentView内部做了什么操作呢?

public void setContentView(int layoutResID) {    mWindow.setContentView(layoutResID);}public void setContentView(View view) {    mWindow.setContentView(view);}public void setContentView(View view, ViewGroup.LayoutParams params) {    mWindow.setContentView(view, params);}

最终是调用了Window的setContentView方法

/** * Set the screen content to an explicit view.  This view is placed * directly into the screen's view hierarchy.  It can itself be a complex * view hierarchy. * * <p>Note that calling this function "locks in" various characteristics * of the window that can not, from this point forward, be changed: the * features that have been requested with {@link #requestFeature(int)}, * and certain window flags as described in {@link #setFlags(int, int)}. * * @param view The desired content to display. * @param params Layout parameters for the view. */public abstract void setContentView(View view, ViewGroup.LayoutParams params);

大概原意:将view的内容设置给屏幕显示,这个视图直接添加到屏幕的视图层次结构中

  1. 调用onStart();
  2. 通过mDecor = mWindow.getDecorView();获取根视图
  3. 设置Window的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);}
  1. 显示
try {    mWindowManager.addView(mDecor, l);    mShowing = true;    sendShowMessage();} finally {}

这里主要有三个操作:
- 通过WindowManager将根视图Add进去并显示
- 将mShowing标识设置为true
- 调用sendShowMessage方法(下边再进行分析

以上就是Dialog创建和显示过程,那消失过程又是如何实现的呢?

dismiss()方法

@Overridepublic void dismiss() {    if (Looper.myLooper() == mHandler.getLooper()) {        dismissDialog();    } else {        mHandler.post(mDismissAction);    }}

这个方法主要是做了一个线程的判断工作,保证了线程安全:
- 假如调用该方法的线程与mHandler所在的线程是一致的,则直接调用dismissDialog()方法
- 不一致,则调用了mHandler.post(mDismissAction);,查看mDismissAction

private final Runnable mDismissAction = new Runnable() {    public void run() {        dismissDialog();    }};

这是一个Runnable接口,内部实现也是调用了dismissDialog()方法。通过mHandler.post方法使得切换到mHandler所在的线程执行dismissDialog()

这里就牵扯到了一个重要的知识点:

通过上篇的Loop、Handler、MessageQueue和Message源码分析,我们知道假如在子线程创建Handler,需要先调用Looper.prepare()方法。而从private final Handler mHandler = new Handler();知道mHandler作为Dialog的一个私有变量,所以假如要在子线程中使用Dialog,需要这样使用:

// 例子new Thread(new Runnable() {    @Override    public void run() {        Looper.prepare();        Dialog dialog = new Dialog(MainActivity.this);        dialog.setTitle("ffdfdfdf");        dialog.show();        Looper.loop();    }}).start();

回到主题,继续查看dismissDialog()方法

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();    }}
  1. 这里是Dialog消失的处理方法,主要在于mWindowManager.removeViewImmediate(mDecor),进入查看:
/** * Special variation of {@link #removeView} that immediately invokes * the given view hierarchy's {@link View#onDetachedFromWindow() * View.onDetachedFromWindow()} methods before returning.  This is not * for normal applications; using it correctly requires great care. *  * @param view The view to be removed. */public void removeViewImmediate(View view);

大概原意:这是removeView的特别版本,会立马调用给定的视图层次结构的onDetachedFromWindow()方法,然后再返回
2. 主要做一些收尾工作:将mDecor置为空,调用onStop()方法,设置标识mShowing为false。
3. 调用sendDismissMessage()方法

sendShowMessage和sendDismissMessage分析

我们上边的分析时有涉及到,在show()方法的最后调用了sendShowMessage()方法;在dismiss()方法的最后调用了sendDismissMessage()方法。这两个方法的作用是什么,我们接下来分析

sendShowMessage

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

当mShowMessage不为空时,再调用sendToTarget()方法。那mShowMessage是什么时候赋值的呢?在Dialog进行全文搜索,可以发现:

/** * Sets a listener to be invoked when the dialog is shown. * @param listener The {@link DialogInterface.OnShowListener} to use. */public void setOnShowListener(OnShowListener listener) {    if (listener != null) {        mShowMessage = mListenersHandler.obtainMessage(SHOW, listener);    } else {        mShowMessage = null;    }}

假如调用了setOnShowListener方法,并且传入的参数不为空,则对mShowMessage进行赋值,将一个SHOW类型的消息赋值个它。

再回到sendShowMessage方法,在这里调用了sendToTarget的方法。这是使用了Handler进行消息分发处理,那我们进入到mListenersHandler分析,该类是一个Handler对象

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

可以看到,在case SHOW的分支,调用了OnShowListener的onShow方法:

case SHOW:    ((OnShowListener) msg.obj).onShow(mDialog.get());

可以看到整个流程的目的就是为了用户可以设置OnShowListener,在dialog显示时获得回调,做其他处理

sendDismissMessage

整个流程与sendShowMessage相似,这里不再描述

总结

通过上边的代码分析,我们可以有以下几个结论:

  1. 假如使用只有一个参数的构造函数,则会使用系统提供的默认主题来创建Dialog
  2. 在onCreate()方法中对Dialog进行初始化,并调用setContentView()方法为Dialog设置视图
  3. 在子线程中使用Dialog时,需要先调用Looper.prepare()方法,因为Dialog内部使用了Handler
  4. 假如需要监听Dialog的显示和消失,可以通过方法setOnShowListener()和setOnDismissListener()方法
  5. 假如需要在Dialog启动时进行其他的操作,可以重写onStart()方法;假如需要在其消失时进行其他的操作,可以重写onStop()方法
0 0