Android 中 Dialog 的 Context 暗藏玄机
今天在开发中遇到了一个关于 Dialog
的 Context
的诡异问题。
原本定义了一个 Dialog
的派生类 BaseDialog
,并调用其重载的构造方法设置了 Theme
。
然后还定义了一个继承 BaseDialog
的 MyDialog
,由于在一个 private
方法中需要用到 Context
的方法,不得已在前面直接声明了一个 Context
对象。
但是在后面调用时发现,由于本身构造方法就传入了一个 Context
对象,实际上存在两个 Context
,因此后面出现了空指针异常。
为了解决这个问题,于是在 BaseDialog
中声明了一个 protected
类型的 Context
,这样 MyDialog
类就不用再去定义全局变量了。
但 Eclipse 提示可以调用 Dialog
类的 getContext()
方法,颇为惊喜和意外,这样看来貌似不用定义 protected
类型的 Context
了。
但一跑发现虽然解决了 NPE ,但是里面自定义组件的 Theme
居然莫名其妙地变了!百思不得其解,后来想到这个 getContext()
方法,一看 Dialog 类源码才发现果然暗藏玄机。
这是其 getContext()
方法:
public final Context getContext() {
return mContext;
}
乍一看也没啥问题,这应该就是构造方法传入的 Context
啊,那么继续看构造方法:
public Dialog(Context context) {
this(context, 0, true);
}
public Dialog(Context context, int theme) {
this(context, theme, true);
}
问题出来了,我们外部调用的构造方法除了 Context
,最多传个 Theme
进来,没想到内部居然都是调用的这个带三个参数的非 public
构造方法:
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.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
而且最外层的 if
判断完全没用,因为前面两个构造方法内部传过来都是 true
! 而这个 Context
直接被赋值为封装了 Theme
的对象,早就不是原来的纯粹的 Context
对象了。
这个是最新的 4.4 源码,于是怀疑是不是新的 SDK 才是这样的。再来看看 2.3 版 Dialog 源码:
public Dialog(Context context) {
this(context, 0);
}
public Dialog(Context context, int theme) {
mContext = new ContextThemeWrapper(context, theme == 0 ? com.android.internal.R.style.Theme_Dialog : theme);
mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
Window w = PolicyManager.makeNewWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mUiThread = Thread.currentThread();
mListenersHandler = new ListenersHandler(this);
}
尼玛这个更坑爹,都没判断,直接调用了 new ContextThemeWrapper()
。
看来只能乖乖的定义 protected
类型的 Context
,这个 getContext()
方法真是暗藏玄机:
如果你自定义了 Dialog
的 Theme
,而且在初始化内部 View
的时候使用的是 getContext()
得到的 Context
对象,那么这个 View
的主题会受到 Dialog
的 Theme
影响。