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 影响。