proguard rule 惨痛教训(记录)
问题
今天再给项目配置混淆打release包时,遇到了个很尴尬的问题,项目打正式包下奔溃,打测试包正常运行。下面是打包配置(app下的build.gradle),关于为什么要proguard应用程序,请看这篇文章为什么每个人都应该将ProGuard用于他们的Android应用程序
android { compileSdkVersion rootProject.ext.android.compileSdkVersion defaultConfig { applicationId "com.kanghanbin.wanandroid" minSdkVersion rootProject.ext.android.minSdkVersion targetSdkVersion rootProject.ext.android.targetSdkVersion versionName rootProject.ext.android.versionName testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" ndk { // 设置支持的SO库架构 abiFilters 'armeabi', 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a' } } signingConfigs { release { //RELEASE_KEY_ALIAS等常量放在gradle.properties文件中 keyAlias RELEASE_KEY_ALIAS keyPassword RELEASE_KEY_PASSWORD storeFile file(RELEASE_STORE_FILE) storePassword RELEASE_STORE_PASSWORD } } buildTypes { release { minifyEnabled true shrinkResources true // 自动清理无用资源 proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release } debug { signingConfig signingConfigs.release minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } //只需禁用lint警告。有时应用不会发布到Google Play,因此不需要深层链接等: lintOptions { disable 'GoogleAppIndexingWarning' baseline file("lint-baseline.xml") checkReleaseBuilds false abortOnError false }}
然后我就将buildType
为debug时的minifyEnabled
也改为true,在手机上调试
debug { signingConfig signingConfigs.release minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' }
不出意外,来了个NPE
定位错误是在BaseSubscriber
的61行代码报错
if (((ApiException) t).getCode() == Constant.UN_LOGIN || t.getMessage().equals(Constant.UN_LOGIN_MESSAGE)) { mView.startLoginActivity(); } mView.showToast(t.getMessage());
发现就是t.getmessage为空然后我就多加了个判断,BaseSubscriber类的完整代码如下
public abstract class BaseSubscriber extends ResourceSubscriber { private BaseView mView; private String mErrorMsg = ""; private boolean isShowErrorState = true; public BaseSubscriber(BaseView mView) { this.mView = mView; } public BaseSubscriber(BaseView mView, boolean isShowErrorState) { this.mView = mView; this.isShowErrorState = isShowErrorState; } public BaseSubscriber(BaseView mView, String mErrorMsg) { this.mView = mView; this.mErrorMsg = mErrorMsg; } public BaseSubscriber(BaseView mView, String mErrorMsg, boolean isShowErrorState) { this.mView = mView; this.mErrorMsg = mErrorMsg; this.isShowErrorState = isShowErrorState; } @Override public void onComplete() { } @Override public void onError(Throwable t) { if (mView == null) { return; } if (mErrorMsg != null && !TextUtils.isEmpty(mErrorMsg)) { mView.showToast(mErrorMsg); } else if (t instanceof ApiException) { if (((ApiException) t).getCode() == Constant.UN_LOGIN || t.getMessage() == null || t.getMessage().equals(Constant.UN_LOGIN_MESSAGE)) { mView.startLoginActivity(); } mView.showToast(t.getMessage()); } else if (t instanceof HttpException) { mView.showToast(MyApplication.getInstance().getString(R.string.data_fail)); } else { mView.showToast(MyApplication.getInstance().getString(R.string.unknow_error)); } if (isShowErrorState) { mView.showFailed(); } }}
但是我就纳闷为啥会走到onError里,回头一想既然不混淆没问题肯定是混淆规则有问题
然后我就开始看混淆规则,排查了一遍发现这里用到的retrofit和rxjava2混淆都没问题呢,可是什么导致的一直走onerror。最后发现我只对项目实体类进行了keep
#-----------处理实体类---------------# 在开发的时候我们可以将所有的实体类放在一个包内,这样我们写一次混淆就行了。-keep class com.kanghanbin.wanandroid.model.bean.**{ *; }
并没有将自己封装的BaseResponse
类进行keep,因为这里的BaseResponse用于http统一响应。BaseResponse完整代码:
/** * 创建时间:2018/10/26 * 编写人:kanghb * 功能描述:封装http响应格式 */public class BaseResponse { /** * data : null * errorCode : -1 * errorMsg : 账号密码不匹配! */ private T data; private int errorCode; private String errorMsg; public T getData() { return data; } public void setData(T data) { this.data = data; } public int getErrorCode() { return errorCode; } public void setErrorCode(int errorCode) { this.errorCode = errorCode; } public String getErrorMsg() { return errorMsg; } public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; } @Override public String toString() { return "BaseResponse{" + "data=" + data + ", errorCode=" + errorCode + ", errorMsg='" + errorMsg + '\'' + '}'; }}
BaseResponse用法举例:
addSubscribe(apiService.login(username, password) .compose(RxUtil.>rxFlowableSchedulerHelper()) .compose(RxUtil.handleResult()) .subscribeWith(new BaseSubscriber(mView) { @Override public void onNext(UserBean userBean) { mView.showToast("登录成功"); mView.gotoMain(); sharePreferencesHelper.setLoginAccount(username); sharePreferencesHelper.setLoginPassword(password); sharePreferencesHelper.setLoginStatus(true); RxBus.getDefault().post(new EventLogin(true)); } }));
解决
在proguard-rules.pro中加入完美结局问题,正式包也可以正常运行了
#-----------处理实体类---------------# 在开发的时候我们可以将所有的实体类放在一个包内,这样我们写一次混淆就行了。-keep class com.kanghanbin.wanandroid.model.bean.**{ *; }-keep class com.kanghanbin.wanandroid.http.BaseResponse{ *; }
科普一下
多简化序列化和反序列化这些字段的工具都依赖于反射。GSON,Retrofit,Firebase - 它们都检查数据类中的字段名称,并将它们转换为另一种表示形式(例如:),{“name”: “Sue”, “age”: 28}
用于传输或存储。当他们将数据读入Java对象时会发生同样的事情 - 他们看到一个键值对“name”:”John”
并尝试通过查找String name
字段将其应用于Java对象。
结论:我们不能让ProGuard重命名或删除这些数据类中的任何字段,因为它们必须与序列化格式匹配。可以安全地@Keep
在整个类上添加注释或在所有模型上添加通配符规则:
-keep class com.kanghanbin.wanandroid.model.bean.**{ *; }
更多相关文章
- Android(安卓)库 Gson
- android webkit 内核
- Android(安卓)drivers/switch驱动详解(用于通过GPIO状态检测耳机
- Android中,如何在其他类调用Activity的方法,适用于类似场景
- Android(安卓)2.0以后的Contacts API--ContactsContract
- AndroidManifest文件格式、Resource.arsc文件格式解析与混淆
- android 混淆时出现的一些问题
- AndroidManifest.xml文件详解(instrumentation)
- Android(安卓)app的混淆打包