@SmartDengg
2015-12-24T03:45:23.000000Z
字数 7128
阅读 3651
在java语言中,匿名内部类和非静态内部类持有外部类的强引用。但是被static修饰的变量,不再属于该对象,而属于.class类字节码,生命周期几乎与整个应用是等长的。
Android官方开发组特别喜欢队列的概念,比如我们常用的Handler,Activity栈等都维持了队列的概念,甚至2.3之后的AsyncTask也变成了串行。因此,当我们维护一个handler的时候,应该在组件生命周期结束的时候通过调用.removeMessages()
、removeCallbacks()
移除对应消息,或者使用.removeCallbacksAndMessages(null)
移除Looper中的所有延迟消息或回调。当然这只是一段楔子。接下来要说的是Dialog中的回调监听(Callback Listener)。
首先来看一下Dialog中show()
方法代码片段:
/**
* Start the dialog and display it on screen. The window is placed in the
* application layer and opaque. Note that you should not override this
* method to do initialization when the dialog is shown, instead implement
* that in {@link #onStart}.
*/
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 {
//最重要的一行代码
//将mDecor添加到WindowManager中建立系统间联系,从而管理window的一些状态,如View的添加,移除、更新,以及消息的收集和处理等操作。
mWindowManager.addView(mDecor, l);
mShowing = true;
//发送showMessage消息至handler
sendShowMessage();
} finally {
}
}
调用show()
方法之后,我们的dialog就被添加到window中了。
接下来看一下与.dismiss()
相关的方法:
/**
* Dismiss this dialog, removing it from the screen. This method can be
* invoked safely from any thread. Note that you should not override this
* method to do cleanup when the dialog is dismissed, instead implement
* that in {@link #onStop}.
*/
@Override
public void dismiss() {
if (Looper.myLooper() == mHandler.getLooper()) {
//判断当前线程是否为主线程,如果是则在本线程调用,否则发送消息到主线程
dismissDialog();
} else {
mHandler.post(mDismissAction);
}
}
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 {
//立即!立即!立即(重要的事情要说三遍)从窗口中移除mDecor,与.show()方法相对应
mWindowManager.removeViewImmediate(mDecor,);
} finally {
if (mActionMode != null) {
mActionMode.finish();
}
mDecor = null;
mWindow.closeAllPanels();
onStop();
mShowing = false;
//发送一个dismiss消息
sendDismissMessage();
}
}
private void sendDismissMessage() {
if (mDismissMessage != null) {
// Obtain a new message so this dialog can be re-used
Message.obtain(mDismissMessage).sendToTarget();
}
}
既然dialog已经从window中移除了,那么这个发送消息sendDismissMessage()
的方法又是干嘛的呢,啊哈,聪明的你一定想到了,处理Listener监听回调(calback)。
不过在这之前要看这样一段代码:
/**
* Set a listener to be invoked when the dialog is dismissed.
* @param listener The {@link DialogInterface.OnDismissListener} to use.
*/
public void setOnDismissListener(final OnDismissListener listener) {
if (mCancelAndDismissTaken != null) {
throw new IllegalStateException(
"OnDismissListener is already taken by "
+ mCancelAndDismissTaken + " and can not be replaced.");
}
if (listener != null) {
//mDismissMessage对应的是mListenersHandler,所以只需要看看mListenersHandler是如何实现的,就能得出结论了。
mDismissMessage = mListenersHandler.obtainMessage(DISMISS, listener);
} else {
mDismissMessage = null;
}
}
mListenersHandler
实现如下:
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;
}
}
}
啊哈,果然是会处理我们的监听回调.onDismiss()
、.onCancel()
、.onShow()
,但是依然要遵循队列的概念,至于什么时候处理,还要看handler与looper的调度。不过显然这问题不大,因为view的更新,总是要先于回调的,所以把这些通知消息交给handler+looper处理是正确的处理方式。
那么问题来了这三个点击监听.setPositiveButton()
、.setNegativeButton()
、.setNeutralButton()
,也是通过handler发送message实现的:
private Handler mHandler;
private final View.OnClickListener mButtonHandler = new View.OnClickListener() {
@Override
public void onClick(View v) {
final Message m;
if (v == mButtonPositive && mButtonPositiveMessage != null) {
m = Message.obtain(mButtonPositiveMessage);
} else if (v == mButtonNegative && mButtonNegativeMessage != null) {
m = Message.obtain(mButtonNegativeMessage);
} else if (v == mButtonNeutral && mButtonNeutralMessage != null) {
m = Message.obtain(mButtonNeutralMessage);
} else {
m = null;
}
if (m != null) {
m.sendToTarget();
}
// Post a message so we dismiss after the above handlers are executed
mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialog)
.sendToTarget();
}
};
private static final class ButtonHandler extends Handler {
// Button clicks have Message.what as the BUTTON{1,2,3} constant
private static final int MSG_DISMISS_DIALOG = 1;
private WeakReference<DialogInterface> mDialog;
public ButtonHandler(DialogInterface dialog) {
mDialog = new WeakReference<>(dialog);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case DialogInterface.BUTTON_POSITIVE:
case DialogInterface.BUTTON_NEGATIVE:
case DialogInterface.BUTTON_NEUTRAL:
((DialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what);
break;
case MSG_DISMISS_DIALOG:
((DialogInterface) msg.obj).dismiss();
}
}
}
如果点击PositiveButton、NegativeButton、NeutralButton之后立即调用了.dismiss()
,这可就麻烦了,dialog立即从window中移除,因为looper是按照队列消费message的,所以如果这些点击事件的message遇到了某些延迟则还存在于handler中,那么就会遇到文章开头说的那种内存泄露,甚至是NullPointerException
等异常。既然找到了问题的所在,那么就很好解决了,只要监听dialog的viewTree,当dialog被windowManager从window中移除的时候,将所设置的listener置空就哦了。
这是我之前的一段代码,已经提交到了update_develop分支上了:
public final class DetachableClickListener implements DialogInterface.OnClickListener {
public static DetachableClickListener wrap(DialogInterface.OnClickListener delegate) {
return new DetachableClickListener(delegate);
}
private DialogInterface.OnClickListener delegateOrNull;
private DetachableClickListener(DialogInterface.OnClickListener delegate) {
this.delegateOrNull = delegate;
}
@Override public void onClick(DialogInterface dialog, int which) {
if (delegateOrNull != null) {
delegateOrNull.onClick(dialog, which);
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) public void clearOnDetach(Dialog dialog) {
dialog.getWindow()
.getDecorView()
.getViewTreeObserver()
.addOnWindowAttachListener(new ViewTreeObserver.OnWindowAttachListener() {
@Override public void onWindowAttached() {
}
@Override public void onWindowDetached() {
DetachableClickListener.this.delegateOrNull = null;
}
});
}
}
使用方法也很简单,只需要包裹一层listener即可:
DetachableClickListener warpClickListener = DetachableClickListener.wrap(new DialogInterface.OnClickListener() {
@Override public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
AlertDialog confirmDialog = new AlertDialog.Builder(context)
.setTitle("提示")
.setMessage(content)
.setCancelable(false)
.setInverseBackgroundForced(false)
.setPositiveButton("确定", warpClickListener)
.create();
warpClickListener.clearOnDetach(confirmDialog);
confirmDialog.getWindow().setWindowAnimations(R.style.AnimCenter);
confirmDialog.show();
OK,虽然梳理了好多源码,但是貌似解决了个小bug,我们的程序更加健壮了,我们就是要把它做好,不是吗:)
敬请期待下一篇,我将追溯Android的异步框架历史,从Service + Thread + Callback ——> AsyncTask + Handler ——> LoaderManager + Callback ——> RxJava,避开Android Framework中的一些坑和bug(其实Android官方开组和V包负责人也公开表示过Android并非无bug,也需要开发者的共同努力)。
欢迎交流 :)