Dialog源码分析

构造函数入手

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 onCreate
void 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()方法

@Override
public 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()方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值