切换至Release变体时ViewModelProvider触发ClassCastException问题求助
问题描述
切换应用构建变体为Release(仅修改端点、凭证等配置文件)时,出现如下崩溃:
FATAL EXCEPTION: main Process: com.timo.ecocloud.paciente, PID: 15435 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.timo.ecocloud.paciente/com.timo.ecocloudpaciente.ui.login.LoginActivity}: java.lang.ClassCastException at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3635) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3792) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:103) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2210) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:201) at android.os.Looper.loop(Looper.java:288) at android.app.ActivityThread.main(ActivityThread.java:7839) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003) Caused by: java.lang.ClassCastException at android.support.v4.media.session.b.a(SourceFile:1) at j1.a.<init>(SourceFile:1) at com.timo.ecocloudpaciente.ui.login.f.a(SourceFile:1) at androidx.lifecycle.s.b(SourceFile:1) at androidx.lifecycle.s.a(SourceFile:1) at com.timo.ecocloudpaciente.ui.login.LoginActivity.onCreate(Unknown Source:68) at android.app.Activity.performCreate(Activity.java:8051) at android.app.Activity.performCreate(Activity.java:8031) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1329) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3608) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3792) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:103) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2210) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:201) at android.os.Looper.loop(Looper.java:288) at android.app.ActivityThread.main(ActivityThread.java:7839) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
触发崩溃的代码:
loginViewModel = ViewModelProviders.of(this, new LoginViewModelFactory()) .get(LoginViewModel.class);
已尝试更新相关依赖,也改用了新写法:
new ViewModelProvider(this, new LoginViewModelFactory()).get(LoginViewModel.class);
但问题依旧。
补充代码
Activity中触发错误的代码
loginViewModel = ViewModelProviders.of(this, new LoginViewModelFactory()) .get(LoginViewModel.class);
LoginViewModel.java
package com.timo.ecocloudpaciente.ui.login; import android.app.Activity; import android.util.Log; import android.util.Patterns; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; import com.timo.ecocloudpaciente.data.LoginRepository; import com.timo.ecocloudpaciente.R; import com.timo.ecocloudpaciente.model.DefaultResponse; import com.timo.ecocloudpaciente.model.DefaultResponseResult; import com.timo.ecocloudpaciente.model.User; public class LoginViewModel extends ViewModel { private MutableLiveData<LoginFormState> loginFormState = new MutableLiveData<>(); private MutableLiveData<RecoverFormState> recoverFormState = new MutableLiveData<>(); private MutableLiveData<LoginResult> loginResult = new MutableLiveData<>(); private MutableLiveData<DefaultResponseResult> defaultResult = new MutableLiveData<>(); private LoginRepository loginRepository; LoginViewModel(LoginRepository loginRepository) { this.loginRepository = loginRepository; } /** * Login */ LiveData<LoginFormState> getLoginFormState() { return loginFormState; } LiveData<LoginResult> getLoginResult() { return loginResult; } public void login(String username, String password) { // can be launched in a separate asynchronous job loginRepository.login(username, password, this); } public void loginDataChanged(String username, String password) { if (!isUserNameValid(username)) { loginFormState.setValue(new LoginFormState(R.string.login_invalid_username, null)); } else if (!isPasswordValid(password)) { loginFormState.setValue(new LoginFormState(null, R.string.login_invalid_password)); } else { loginFormState.setValue(new LoginFormState(true)); } } // A placeholder username validation check private boolean isUserNameValid(String username) { return username != null && !username.equals(""); } // A placeholder password validation check private boolean isPasswordValid(String password) { return password != null && password.trim().length() == 6; } /** * Used for callback */ public void onSuccess(User user) { loginResult.setValue(new LoginResult(user)); } public void onError(String message) { loginResult.setValue(new LoginResult(message)); } /** * Recover password */ LiveData<RecoverFormState> getRecoverFormState() { return recoverFormState; } public void recoverDataChanged(String useremail) { if (!isEmailValid(useremail)) { recoverFormState.setValue(new RecoverFormState(R.string.recover_invalid_email)); }else { recoverFormState.setValue(new RecoverFormState(true)); } } LiveData<DefaultResponseResult> getDefaultResult() { return defaultResult; } public void recoverPwd(Activity activity, LoginViewModel loginViewModel, String email){ loginRepository.recoverPwd(activity, loginViewModel, email); } private boolean isEmailValid(String email){ if (email == null || email.trim().isEmpty()) return false; if (email.contains("@")) return Patterns.EMAIL_ADDRESS.matcher(email).matches(); else return false; } /** * Used for callback */ public void defaultReturnOnSuccess(DefaultResponse defaultResponse) { defaultResult.setValue(new DefaultResponseResult(defaultResponse, "")); } public void defaultReturnOnError(String message) { defaultResult.setValue(new DefaultResponseResult(message)); } }
LoginViewModelFactory.java
package com.timo.ecocloudpaciente.ui.login; import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; import androidx.annotation.NonNull; import com.timo.ecocloudpaciente.data.LoginDataSource; import com.timo.ecocloudpaciente.data.LoginRepository; public class LoginViewModelFactory implements ViewModelProvider.Factory { @NonNull @Override @SuppressWarnings("unchecked") public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { if (modelClass.isAssignableFrom(LoginViewModel.class)) { return (T) new LoginViewModel(LoginRepository.getInstance(new LoginDataSource())); } else { throw new IllegalArgumentException("Unknown ViewModel class"); } } }
解决方案
这个问题的核心是Release模式下的代码混淆导致ViewModel相关类被错误重命名,进而引发类型转换异常。从崩溃栈可以看到,类名已经被混淆成j1.a、com.timo.ecocloudpaciente.ui.login.f这类无意义名称,ViewModel的类型信息在混淆后丢失,导致get(LoginViewModel.class)时无法正确转换类型。
需要在项目的混淆规则文件(通常是proguard-rules.pro)中添加以下规则,排除ViewModel相关类的混淆:
# 保留所有继承自ViewModel的类及其构造方法 -keep public class * extends androidx.lifecycle.ViewModel { <init>(...); } # 保留所有实现ViewModelProvider.Factory的类及其构造方法 -keep public class * implements androidx.lifecycle.ViewModelProvider.Factory { <init>(...); } # 保留当前项目中的LoginViewModel和LoginViewModelFactory -keep class com.timo.ecocloudpaciente.ui.login.LoginViewModel -keep class com.timo.ecocloudpaciente.ui.login.LoginViewModelFactory # 保留LiveData相关类,避免潜在的混淆问题 -keep class androidx.lifecycle.LiveData -keep class androidx.lifecycle.MutableLiveData
如果希望更简单直接,也可以保留整个登录模块的类不被混淆:
-keep class com.timo.ecocloudpaciente.ui.login.** { *; }
添加完混淆规则后,重新构建Release变体即可解决这个ClassCastException问题。
内容的提问来源于stack exchange,提问作者Thiago Souza
相关产品推荐
相关产品推荐

