最近项目中想简单实现一个两个项目的Dialog,却一直报如题的错误。
起因是这样的:
写了个弹出以文本作为内容的AlertDialog类,想做一个简单弹窗选择。
public class SimpleDialogUtils {
public static void showSimpleChooseDialog(int itemsId, DialogInterface.OnClickListener listener) {
AlertDialog dialog = new AlertDialog
.Builder(Utils.getApp())
.setItems(itemsId, listener)
.create();
dialog.show();
}
}
注意这里:.Builder(Utils.getApp()) ,Bulider需要传入一个context作为参数。
这里我自作聪明,把全局的Application传进去了。这里就导致了问题。
使用时,如下进行调用:
public class EditProfileActivity extends BaseActivity {
@Override
protected int initLayoutRes() {
return R.layout.activity_edit_my_profile;
}
@OnClick(R.id.btn_change_pic)
void showChoose(){
SimpleDialogUtils.showSimpleChooseDialog(R.array.pic_choose
, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
});
}
}
运行,crash~
看log提示资源未找到,还以为values目录下的arrays.xml中的文本资源有问题,后来改为用写死的String[] 作为参数的形式仍然未果。
思考一下,发现log显示的 android.content.res.Resources$NotFoundException: Resource ID #0x0
资源id应该不是文本资源引起的,id为0,可能是父view的资源没有引用到。
查看了Dialog源码,发现创建Dialog的Builder初始化时,会解析主题
public Builder(@NonNull Context context) {
this(context, resolveDialogTheme(context, 0));
}
这里没有传入一个主题资源,默认传入为0,调用两个参数的构造器。构造前通过resolveDialogTheme()
获取主题的id;
static int resolveDialogTheme(@NonNull Context context, @StyleRes int resid) {
// Check to see if this resourceId has a valid package ID.
if (((resid >>> 24) & 0x000000ff) >= 0x00000001) { // start of real resource IDs.
return resid;
} else {
TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.alertDialogTheme, outValue, true);
return outValue.resourceId;
}
}
当传入了Application类作为context时,getTheme()后resolveAttribute()得到的值会放到outValue的resourceId中,这时获取到的resourceId为0;
当传入的是Activity作为context时,最终获取到的resourceId不是0;
这个resourceId 也会作为mTheme这个成员变量的值。
public Builder(@NonNull Context context, @StyleRes int themeResId) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, themeResId)));
mTheme = themeResId;
}
public AlertDialog create() {
// We can't use Dialog's 3-arg constructor with the createThemeContextWrapper param,
// so we always have to re-set the theme
final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
P.apply(dialog.mAlert);
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
dialog.setCanceledOnTouchOutside(true);
}
dialog.setOnCancelListener(P.mOnCancelListener);
dialog.setOnDismissListener(P.mOnDismissListener);
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener(P.mOnKeyListener);
}
return dialog;
}
在create时,p将引用的context 以及mTheme的值传递给AlertDialog的构造器。
protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
super(context, resolveDialogTheme(context, themeResId));
mAlert = new AlertController(getContext(), this, getWindow());
}
AlertController构造时调用的getContext(),实际上获取到的是Dialog的Builder的context
因为AlertDialog构造函数调用了父类的构造函数,最终调用的是Dialog类的构造函数。
Dialog的getContext如下
public final @NonNull Context getContext() {
return mContext;
}
也就是初始构造时传入的context;
AlertController类是控制对话框生成的。
当使用本次使用的setItem方式构造对话框,它会使用context获取到一个listview的资源id。
final TypedArray a = context.obtainStyledAttributes(null, R.styleable.AlertDialog,
R.attr.alertDialogStyle, 0);
...
mListLayout = a.getResourceId(R.styleable.AlertDialog_listLayout, 0);
这里obtainStyledAttributes获取到一个系统属性集合。该方法源码如下:
/**
* Retrieve styled attribute information in this Context's theme. See
* {@link android.content.res.Resources.Theme#obtainStyledAttributes(int[])}
* for more information.
*
* @see android.content.res.Resources.Theme#obtainStyledAttributes(int[])
*/
public final TypedArray obtainStyledAttributes(@StyleableRes int[] attrs) {
return getTheme().obtainStyledAttributes(attrs);
}
可以看出是通过getTheme的obtainStyledAttributes获取到的结果集。但是Application并没设置theme的方法,所以getTheme()无法获得theme,obtainStyledAttributes获取到的TypedArray 也无内容。从而也无法获取到mListLayout 。
最终会根据这个mListLayout 生成listview。
final RecycleListView listView =
(RecycleListView) mInflater.inflate(dialog.mListLayout, null);
这个listview就是最终在对话框中显示的内容。
当context为Application时,获取到的mListLayout 为0 ,不能inflate出listview。便报错。
最终改为当传入Activity作为context时,可以正常显示
public class EditProfileActivity extends BaseActivity {
@Override
protected int initLayoutRes() {
return R.layout.activity_edit_my_profile;
}
@OnClick(R.id.btn_change_pic)
void showChoose(){
SimpleDialogUtils.showSimpleChooseDialog(this,R.array.pic_choose
, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
});
}
}
public class SimpleDialogUtils {
public static void showSimpleChooseDialog(Context ctx ,int itemsId, DialogInterface.OnClickListener listener) {
AlertDialog dialog = new AlertDialog
.Builder(ctx)
.setItems(itemsId, listener)
.create();
dialog.show();
}
}
结论:Context的选择要慎重,并不是拿到了Application 的context就可以当万精油使用的。
涉及到View的context需要具体分析。