我们再看看我们的代码,我这个 ProductReceiveCouponItem 实际上是一个 RecyclerView 的 Item,而这个相应的 RecyclerView 是显示在 DialogFragment 上的。熟悉 DialogFragment 的小伙伴可能知道,DialogFragment 实际上也是一个 Fragment。而 DialogFragment 里面,其实是有一个 Dialog 的变量 mDialog 的,这个 Dialog 会在 onStart() 后通过 show() 展示出来。
在我们使用 DialogFragment 的时候,一定都会重写 onCreatView() 对吧,有一个 LayoutInflater 参数,返回值是一个 View,我们不禁想知道这个 LayoutInflater 是从哪儿来的? onGetLayoutInflater(),我们看看。
@Override public LayoutInflater onGetLayoutInflater(Bundle savedInstanceState) { if (!mShowsDialog) { return super.onGetLayoutInflater(savedInstanceState); } mDialog = onCreateDialog(savedInstanceState); if (mDialog != null) { setupDialog(mDialog, mStyle); return (LayoutInflater) mDialog.getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE); } return (LayoutInflater) mHost.getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE); }我们是以一个 Dialog 的形式展示,所以不会进入其中的 if 条件。所以我们直接通过了 onCreateDialog() 构造了一个 Dialog。如果这个 Dialog 不为空的话,那么我们的 LayoutInflater 就会直接通过 Dialog 的 Context 构造出来。我们来看看 onCreateDialog() 方法。
public Dialog onCreateDialog(Bundle savedInstanceState) { return new Dialog(getActivity(), getTheme()); }很简单,直接 new 了一个 Dialog,Dialog 这样的构造方法上面也说了,直接会把 mContext 指向一个 Context 的包装类 ContextThemeWrapper。
至此我们能做大概猜想了,DialogFragment 负责 inflate 出布局的 LayoutInflater 是由 ContextThemeWrapper 构造出来的,所以我们暂且在这里说一个结论:DialogFragment onCreatView() 里面这个 layout 文件里面的 View.getContext() 返回应该是 `ContextThemeWrapper。
但是!!!我们出问题的是 Item,Item 是通过 RecyclerView 的 Adapter 的 ViewHolder 显示出来的,而非 DialogFragent 里面 Dialog 的 setContentView() 的 XML 解析方法。看起来,分析了那么多,并没有找到问题的症结所在。所以得看看我们的 Adapter 是怎么写的,直接打开我们的 MultiTypeAdapter 的 onCreateViewHolder() 方法。
@NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { if (typeMap.get(viewType, TYPE_DEFAULT) == TYPE_ONE) { return holders.get(viewType).createHolder(parent); } ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), viewType, parent, false); return new ItemViewHolder(binding); }oh,在这里我们的 LayoutInflater.from() 接受的参数是 parent.getContext()。parent 是什么?就是我们的 RecyclerView,这个 RecyclerView 是从哪儿来的?通过 DialogFragment 的 LayoutInflater 给 inflate 出来的。所以 parent.getContext() 返回是什么?在这里,一定是 ContextThemeWrapper。
也就是说,我们的 ViewHolder 的 rootView 也就是通过 ContextThemeWrapper 构造的 LayoutInflater 给 inflate 出来的了。所以我们的 ProductReceiveCouponItem 这个 Item 里面的 binding.root.context 返回值,自然也就是 ContextThemeWrapper 而不是 Activity 了。自然而然,在 CommonDialog 里面直接强转为 Activity 一定会出错。
那为什么在 9.4.0 上没有出现这个问题呢?我们看看 9.4.0 上 MultiTypeAdapter 的 onCreateViewHolder() 方法:
@Override public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { ViewDataBinding binding = DataBindingUtil.inflate(mInflater, viewType, parent, false); return new ItemViewHolder(binding); }咦,看起来似乎不一样,这里直接传入的是 mInflater,我们看看这个 mInflater 是在哪儿被初始化的。
public MultiTypeAdapter(Context context) { mInflater = LayoutInflater.from(context); }oh,在 9.4.0 的分支上,我们的 ViewHolder 的 LayoutInflater 的 Context,是从外面传进来的。再看看我们 DialogFragment 中对 RecyclerView 的处理。
val rvAdapter = MultiTypeAdapter(context) binding.recyclerView.run { layoutManager = LinearLayoutManager(context) val itemDecoration = DividerItemDecoration(context, DividerItemDecoration.VERTICAL_LIST) itemDecoration.setDividerDrawable(R.drawable.list_divider_10_white.toDrawable()) addItemDecoration(itemDecoration) adapter = rvAdapter }是吧,在 9.4.0 的时候,MultiTypeAdapter 的 ViewHolder 会使用外界传入的 Context,这个 Context 是 Activity,所以我们的Item 的 binding.root.context 返回为 Activity。而在 9.5.0 的时候,同事重构了 MultiTypeAdapter,而让其 ViewHolder 的 LayoutInflater 直接取的 parent.getContext(),这里的情况即 ContextThemeWrapper,所以出现了几个月没动的代码,在新版本上灰测却崩溃了。
总结写了这么多,还是做一些总结。首先对题目做个答案: View.getContext() 的返回不一定是 Activity。