每日一问:View.getContext() 的返回一定是 Activity 么? (3)

我们直接从我前两天线上灰测包出现的一个 bug 说起。先说说 bug 背景,灰测包是 9.5.0,而线上包是 9.4.0,在灰测包上发生崩溃的代码是三个月前编写的代码,也就是说这可能是 8.43.0 或者 9.0.0 加入的代码,在线上稳定运行了 4 个版本以上没有做过任何修改。但在 9.5.0 灰测的时候,这里却出现了必现崩溃。

Fatal Exception: java.lang.ClassCastException: android.view.ContextThemeWrapper cannot be cast to android.app.Activity at com.codoon.common.dialog.CommonDialog.openProgressDialog + 145(CommonDialog.java:145) at com.codoon.common.dialog.CommonDialog.openProgressDialog + 122(CommonDialog.java:122) at com.codoon.common.dialog.CommonDialog.openProgressDialog + 116(CommonDialog.java:116) at com.codoon.find.product.item.detail.i$a.onClick + 57(ProductReceiveCouponItem.kt:57) at android.view.View.performClick + 6266(View.java:6266) at android.view.View$PerformClick.run + 24730(View.java:24730) at android.os.Handler.handleCallback + 789(Handler.java:789) at android.os.Handler.dispatchMessage + 98(Handler.java:98) at android.os.Looper.loop + 171(Looper.java:171) at android.app.ActivityThread.main + 6699(ActivityThread.java:6699) at java.lang.reflect.Method.invoke(Method.java) at com.android.internal.os.Zygote$MethodAndArgsCaller.run + 246(Zygote.java:246) at com.android.internal.os.ZygoteInit.main + 783(ZygoteInit.java:783)

单看崩溃日志应该非常好改吧,出现了一个强转错误,原来是在我编写的 ProductReceiveCouponItem 类的 57 行调用项目中的通用对话框 CommonDialog 直接崩溃了。翻看 CommonDialog 的相关代码发现,原来是之前的同学在使用传入的 Context 的时候没有做类型验证,直接强转为了 Activity。

// 得到等待对话框 public void openProgressDialog(String message, OnDismissListener listener, OnCancelListener mOnCancelistener) { if (waitingDialog != null) { waitingDialog.dismiss(); waitingDialog = null; } if (mContext == null) { return; } if (((Activity) mContext).isFinishing()) { return; } waitingDialog = createLoadingDialog(mContext, message); waitingDialog.setCanceledOnTouchOutside(false); waitingDialog.setOnCancelListener(mOnCancelistener); waitingDialog.setCancelable(mCancel); waitingDialog.setOnDismissListener(listener); waitingDialog.show(); }

而我的代码通过 View.getContext() 传入的 Context 类型是 ContextThemeWrapper。

// 领取优惠券 val dialog = CommonDialog(binding.root.context) dialog.openProgressDialog("领取中...") // 第 57 行出问题的代码 ProductService.INSTANCE.receiveGoodsCoupon(data.class_id) .compose(RetrofitUtil.schedulersAndGetData()) .subscribeNet(true) { // 逻辑处理相关代码 }

看到了日志改起来就非常简单了,第一种方案是直接在 CommonDialog 强转前做一下类型判断。第二种方案是直接在我这里的代码中通过判断 binding.root.context 的类型,然后取出里面的 Activity。

虽然 bug 非常好解决,但作为一名 Android 程序员,绝对不可以满足于仅仅解决 bug 上,任何事情都事出有因,这里为什么数月没有更改的代码,在 9.4.0 上没有问题,在 9.5.0 上就成了必现崩溃呢?

切换代码分支到 9.4.0,debug 发现,这里的 binding.root.context 返回的确实就是 Activity,而在 9.5.0 上 binding.root.context 确实就返回的是 ContextThemeWrapper,检查后确定代码没有任何改动。

分析出现 ContextThemeWrapper 的原因

看到 ContextThemeWrapper,不由得想起了这个类使用的地方之一:Dialog,熟悉 Dialog 的童鞋一定都知道,我们在构造 Dialog 的时候,会把 Context 直接变成 ContextThemeWrapper。

public Dialog(@NonNull Context context) { this(context, 0, true); } public Dialog(@NonNull Context context, @StyleRes int themeResId) { this(context, themeResId, true); } Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) { if (createContextThemeWrapper) { if (themeResId == ResourceId.ID_NULL) { final TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true); themeResId = outValue.resourceId; } mContext = new ContextThemeWrapper(context, themeResId); } else { mContext = context; } mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); final Window w = new PhoneWindow(mContext); mWindow = w; w.setCallback(this); w.setOnWindowDismissedCallback(this); w.setOnWindowSwipeDismissedCallback(() -> { if (mCancelable) { cancel(); } }); w.setWindowManager(mWindowManager, null, null); w.setGravity(Gravity.CENTER); mListenersHandler = new ListenersHandler(this); }

oh,在第三个构造方法中,通过构造的时候传入的 createContextThemeWrapper 总是 true,所以它一定可以进到这个 if 语句里面去,把 mContext 强行指向了 Context 的包装类 ContextThemeWrapper。所以这里会不会是由于这个原因呢?

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wssszf.html