Android升级Material库1.6.1后触发String转Boolean启动崩溃
问题现象
将自有应用中的Material组件库从1.4.0版本升级至1.6.1版本,依赖配置如下:
implementation 'com.google.android.material:material:1.6.1'
使用1.4.0版本时应用运行完全正常,升级后应用启动时立即崩溃,报错信息如下:
2022-07-05 17:13:17.784 17333-17333/? E/AndroidRuntime: FATAL EXCEPTION: main Process: com.nooriginalthought.amalfi, PID: 17333 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.nooriginalthought.amalfi/com.nooriginalthought.amalfi.MainActivity}: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Boolean at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2195) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2245) at android.app.ActivityThread.access$800(ActivityThread.java:135) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1196) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:136) at android.app.ActivityThread.main(ActivityThread.java:5017) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595) at dalvik.system.NativeStart.main(Native Method) Caused by: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Boolean at android.app.SharedPreferencesImpl.getBoolean(SharedPreferencesImpl.java:261) at o4.w.u() at com.nooriginalthought.amalfi.MainActivity.onCreate(:2) at android.app.Activity.performCreate(Activity.java:5231) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2159) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2245) at android.app.ActivityThread.access$800(ActivityThread.java:135) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1196) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:136) at android.app.ActivityThread.main(ActivityThread.java:5017) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595) at dalvik.system.NativeStart.main(Native Method) 2022-07-05 17:13:26.944 1609-1623/system_process E/WindowManager: Starting window AppWindowToken{ad48a468 token=Token{ad419be0 ActivityRecord{ad079590 u0 com.nooriginalthought.amalfi/.MainActivity t84}}} timed out
测试发现,注释掉onCreate()方法中如下Google Play服务校验代码行,应用即可正常启动,但会缺失必需的校验逻辑:
isGooglePlayServicesAvailable(this);
自定义的isGooglePlayServicesAvailable方法明确定义返回Boolean类型,方法实现如下:
public boolean isGooglePlayServicesAvailable(Activity activity) { GoogleApiAvailability googleApiAvailability = GoogleApiAvailability.getInstance(); int status = googleApiAvailability.isGooglePlayServicesAvailable(activity); if(status != ConnectionResult.SUCCESS) { if(googleApiAvailability.isUserResolvableError(status)) { googleApiAvailability.getErrorDialog(activity, status, GOOGLE_PLAY_SERVICES_API_REQUEST_CODE).show(); } return false; } return true; }
已尝试Gradle重同步、Clean后重新构建,问题依旧复现。测试设备均已正常安装Google Play服务,初步怀疑是错误弹窗调用时出现异常。
补充信息
完整onCreate()方法代码如下,排查后未发现方法内存在直接触发该类型转换错误的逻辑:
@Override protected void onCreate(Bundle _savedInstanceState) { super.onCreate(_savedInstanceState); isGooglePlayServicesAvailable(this); addExtraSslProtocolsIfNeeded(); amalfi = Amalfi.getInstance(getApplicationContext()); affiliateRegions = new AffiliateRegions(this); // Read prefs and aff_ids here because they are used in start activity decision making amalfi.read_prefs(); CommonFuncs.setLocale(this,amalfi.UIlang); // Go directly to the First Run Welcome Activity if it hasn't been shown AND acknowledged if (!amalfi.compWarnShown_firstRun) { showFirstRunMessage(); } amalfi.get_affids_fromprefs(affiliateRegions); // Get the intent that started this activity Intent intent = getIntent(); String action = intent.getAction(); String type = intent.getType(); // Figure out what to do based on the intent type if (Intent.ACTION_SEND.equals(action) && type != null) { if ("text/plain".equals(type)) { incomingString = intent.getStringExtra(Intent.EXTRA_TEXT); } } else if (Intent.ACTION_VIEW.equals(action)) { if (intent.getDataString() != null) { incomingString = intent.getDataString(); } } if ( amalfi.launchIntoBrowser && (incomingString == null || "".equals(incomingString) ) ) { startBrowseAmazonActivity(); } // If not starting another activity, then continue loading this one as normal. viewBinding = MainBinding.inflate(getLayoutInflater()); View view = viewBinding.getRoot(); drawerViewBinding = viewBinding.drawerMenu; setContentView(view); // This if block is trying to get the auto layout transitions working - but doesn't seem to make any difference. viewBinding.mainLayout.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); drawerViewBinding.drawerRoot.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); // Hide the expanded source URL block viewBinding.expandedSrcUrlContainer.setVisibility(GONE); amalfi.isPro(); enable_and_disable_buttons(); // Setup the in app purchases stuff setupBillingClient(); }
方法内仅在调用intent.getAction()和intent.getType()时赋值String类型变量,这两个API的返回值本身就是String类型,不存在类型转换错误。
问题根因
崩溃是Material 1.6.0+版本内部逻辑调整导致的:从1.6.0版本开始,Material组件初始化时会从自身的SharedPreferences文件中读取主题相关的布尔型配置标记,但本地缓存中该key对应的值是1.4.0版本时期存入的字符串类型,调用getBoolean方法读取时直接触发类型转换异常。
崩溃恰好出现在Google Play服务校验逻辑处,是因为getErrorDialog弹出的对话框默认使用全局Material主题,会提前触发Material组件的初始化流程;注释掉这段校验代码后,Material组件初始化延后到inflate布局阶段,并不是真的解决了问题。
修复方案
按优先级依次尝试:
- 在
onCreate方法最靠前的位置,清理Material组件的旧缓存偏好,这段逻辑不会影响任何业务数据:
注意这段代码必须放在所有调用Material组件、弹出系统对话框、inflate布局的逻辑之前执行。getSharedPreferences("com.google.android.material.theme", MODE_PRIVATE).edit().clear().apply(); - 排查所有主题配置文件,确认所有布尔类型的属性值都直接写
true/false,不要加引号写成字符串格式,重点检查状态栏明暗模式、深色主题开关、Material组件主题覆盖相关的属性。 - 如果清理缓存后仍有异常,可以给Google Play服务的错误对话框指定独立主题,避免和全局Material配置冲突:
先在styles.xml中定义独立的对话框主题:
再修改弹窗调用代码,传入自定义主题:<style name="GpsErrorDialogTheme" parent="Theme.MaterialComponents.Light.Dialog.Alert"> <item name="colorPrimary">@color/应用主色值</item> <item name="colorAccent">@color/应用强调色值</item> </style>googleApiAvailability.getErrorDialog(activity, status, GOOGLE_PLAY_SERVICES_API_REQUEST_CODE, R.style.GpsErrorDialogTheme).show();
内容的提问来源于stack exchange,提问作者Fat Monk

