@SmartDengg
2015-12-24T03:45:23.000000Z
字数 7128
阅读 3763
在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消息至handlersendShowMessage();} 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}.*/@Overridepublic 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-usedMessage.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);}@Overridepublic 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() {@Overridepublic 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 executedmHandler.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} constantprivate static final int MSG_DISMISS_DIALOG = 1;private WeakReference<DialogInterface> mDialog;public ButtonHandler(DialogInterface dialog) {mDialog = new WeakReference<>(dialog);}@Overridepublic 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,也需要开发者的共同努力)。
欢迎交流 :)