主要是关于前两篇文章的优化总结,之前很多人问demo啥的,这次优化了一些框架贴上代码。这次就不讲封装思路了,只讲一些优化思路方法。代码之前一直没传,忘了,最近传上来了,虽然有的地方没优化,也没更新上新技术,但够用了,可以根据自己需求进行优化定制:
前两篇文章:
关于mvvm简易封装(一)
关于mvvm简易封装(二)
github代码地址:
https://github.com/fzkf9225/mvvm-componnent-master/tree/master
Android是面向对象的,因此想实现在公共模块跳转别的模块(例如:登录模块)是比较困难的。阿里有个框架ARouter专门为组件化开发的,具体大家可以看看实现原理,github有源码,但是不建议使用这个框架了,原因:
1、ARouter已经很久未更新了
2、未适配AndroidX
3、每个Activity都需要添加路由地址,而这个路由地址却是个字符串,我们虽然能做到组件间解耦但是大量的路由地址也是需要统一配置,当然不可能moduleA的AActivity跳转moduleB的BActivity的,需要在ModuleB中配置BActivity的路由地址,同样A中也要配置,这样容错率很高,而且如果再来个C组件的话,需要在写一份,当然可以通过公共模块去解决问题,那么同样是冗余,我可以直接再公共模块写跳转
4、新Api已经废弃startActivityForResult了,ARouter同样没有适配
综上几个原因啊,不建议使用,但是他的设计理念,源码还是很值得学习的,这里不带大家学习它的源码了。
因此可以结合Java面向切面的思路去实现它。因此我们用到一个库:hilt
//每个用到的模块都要加这两个,不能使用api
implementation "com.google.dagger:hilt-android:$hiltVersion"
annotationProcessor "com.google.dagger:hilt-android-compiler:$hiltVersion"
添加hilt插件支持,在项目根目录下的build.gradle中添加
id 'com.google.dagger.hilt.android' version '2.46.1' apply false
在各个用到hilt的模块添加插件使用
plugins {
id 'com.android.library'
id 'com.google.dagger.hilt.android'
}
hilt框架具体用法可以参考官方了,不解释了。
在公共模块common中添加一个接口ErrorService,里面提供一些方法供BaseActivity和BaseFragment使用,例如:自动跳转登录的路由啊等等方法
public interface ErrorService {
/**
* 是否登录,主要是判断errorCode是否满足跳转登录的条件
* @return
*/
boolean isLogin(String errorCode);
/**
* activity跳转登录
* @param mContext fromActivity
* @param activityResultLauncher launcher
*/
void toLogin(Context mContext, ActivityResultLauncher<Intent> activityResultLauncher);
void toLogin(Context context);
/**
* 是否有操作权限
* @return
*/
boolean hasPermission(String errorCode);
void toNoPermission(Context mContext, ActivityResultLauncher<Intent> activityResultLauncher);
void toNoPermission(Context context);
/**
* 崩溃日志
* @param errorInfo
*/
void uploadErrorInfo(String errorInfo);
/**
* 提供给其他模块跳转app模块
* @return
*/
Class<?> getMainActivity();
/**
* 获取用户token
* @return
*/
String getToken();
/**
* 获取用户RefreshToken
* @return
*/
String getRefreshToken();
/**
* 接口请求头
* @return
*/
Map<String,String> initHeaderMap();
}
在BaseActivity和BaseFragment中实现它
public abstract class BaseActivity<VM extends BaseViewModel, VDB extends ViewDataBinding> extends AppCompatActivity implements BaseView, LoginDialog.OnLoginClickListener {
//注入接口
@Inject
ErrorService errorService;
/**
* 注意判断空,根据自己需求更改
* @param model 错误吗实体
*/
@Override
public void onErrorCode(BaseModelEntity model) {
if (errorService == null || model == null) {
return;
}
if (!errorService.isLogin(model.getCode())) {
errorService.toLogin(this, loginLauncher);
return;
}
if (!errorService.hasPermission(model.getCode())) {
errorService.toNoPermission(this);
}
}
}
当然你再使用的时候记得给继承BaseActivity的类添加注解
@AndroidEntryPoint
@AndroidEntryPoint
public class LoginActivity extends BaseActivity<UserViewModel, LoginBinding> implements UserView {
}
app模块中实现ErrorService接口,我们新建接口实现类ErrorServiceImpl,这里只是参考啊,因此我就随便写的了
public class ErrorServiceImpl implements ErrorService {
@Inject
UserRouterService userRouterService;
@Inject
public ErrorServiceImpl() {
}
@Override
public boolean isLogin(String errorCode) {
return !UserAccountHelper.isLoginPast(errorCode);
}
@Override
public void toLogin(Context mContext, ActivityResultLauncher<Intent> activityResultLauncher) {
userRouterService.toLogin(mContext,activityResultLauncher);
}
@Override
public void toLogin(Context context) {
userRouterService.toLogin(context);
}
@Override
public boolean hasPermission(String errorCode) {
return true;
}
@Override
public void toNoPermission(Context mContext, ActivityResultLauncher<Intent> activityResultLauncher) {
}
@Override
public void toNoPermission(Context context) {
}
@Override
public void uploadErrorInfo(String errorInfo) {
}
@Override
public Class<?> getMainActivity() {
return MainActivity.class;
}
@Override
public String getToken() {
return UserAccountHelper.getToken();
}
@Override
public String getRefreshToken() {
return UserAccountHelper.getRefreshToken();
}
@Override
public Map<String, String> initHeaderMap() {
Map<String, String> headerMap = new HashMap<>();
headerMap.put("Authorization", ConstantsHelper.AUTHORIZATION);
headerMap.put("Tenant-Id", ConstantsHelper.TENANT_ID);
if (!TextUtils.isEmpty(UserAccountHelper.getToken())) {
headerMap.put("Blade-Auth", UserAccountHelper.getToken());
}
return headerMap;
}
}
新建module
@Module//必须配置的注解,表示这个对象是Module的配置规则
@InstallIn(SingletonComponent.class)//表示这个module中的配置是用来注入到Activity中的
public class ErrorServiceModule {
@Provides
ErrorService provideErrorService() {
return new ErrorServiceImpl();
}
}
我们上面需要调用UserRouterService ,因此在用户模块新增下面接口
public interface UserRouterService {
/**
* activity跳转登录
* @param mContext fromActivity
* @param activityResultLauncher launcher
*/
void toLogin(Context mContext,ActivityResultLauncher<Intent> activityResultLauncher);
void toLogin(Context context);
}
public class UserRouterServiceImpl implements UserRouterService {
@Inject
public UserRouterServiceImpl() {
}
@Override
public void toLogin(Context mContext, ActivityResultLauncher<Intent> activityResultLauncher) {
activityResultLauncher.launch(new Intent(mContext, LoginActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT));
}
@Override
public void toLogin(Context context) {
Intent intent = new Intent(context, LoginActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
context.startActivity(intent);
}
}
别忘了添加module,新建UserRouterServiceModule
@Module//必须配置的注解,表示这个对象是Module的配置规则
@InstallIn(SingletonComponent.class)//表示这个module中的配置是用来注入到Activity中的
public class UserRouterServiceModule {
@Provides
UserRouterService provideUserRouterService() {
return new UserRouterServiceImpl();
}
}
有的时候长期未登录的情况下,但是token过期了,我们又不想总是跳登录页面,理想的是:登录过期后我获取缓存信息,然后无缝刷新token然后继续当前请求。
这样我们就需要用到RxJava的一个属性retryWhen,我们看下他的源码
他传入的是一个
Function<Observable<? extends Throwable>, Observable<?>>
因此我们又可以用到面向切面的知识了,编写一个接口
public interface RetryService extends Function<Observable<? extends Throwable>, Observable<?>> {
void setMaxRetryCount(int maxRetryCount);
}
我们在用户模块添加实现类,记得修改isLoginPastOrNoPermission 参数的判断方法,就是判断当前返回的code是否满足登录、无权限等的要求
/**
* Created by fz on 2020/9/9 14:11
* describe:请求失败,重试机制,当请求过期时利用Function方法重新请求刷新token方法替换请求token,然后再重新请求
* 设置3次重试,每次间隔1秒,但仅适用于用户登录过期刷新token和无权限刷新用户菜单时使用
*/
public class RetryServiceImpl implements RetryService {
private final static String TAG = RetryService.class.getSimpleName();
/**
* 最大出错重试次数
*/
private int maxRetries = ConstantsHelper.RETRY_WHEN_MAX_COUNT;
/**
* 当前出错重试次数
*/
private int retryCount = 0;
@Inject
public RetryServiceImpl() {
}
/**
* @param maxRetries 最大重试次数
*/
public RetryServiceImpl(int maxRetries) {
this.maxRetries = maxRetries;
}
@Override
public Observable<?> apply(Observable<? extends Throwable> observable) throws Exception {
UserApiService userApiService = ApiRetrofit.getInstance().getApiService(UserApiService.class);
LogUtil.show(TAG, "-----------------RetryService-------------");
return observable
.flatMap((Function<Throwable, ObservableSource<?>>) throwable -> {
if (throwable instanceof BaseException) {
BaseException baseException = (BaseException) throwable;
LogUtil.show(TAG, "baseException:" + baseException.toString());
boolean isLoginPastOrNoPermission = true;//这里的true改成自己的逻辑
if (++retryCount <= maxRetries && isLoginPastOrNoPermission) {
return refresh(userApiService);
}
} else if (throwable instanceof HttpException) {
HttpException httpException = (HttpException) throwable;
LogUtil.show(TAG, "httpException:" + httpException);
if (401 == httpException.code()) {
return refresh(userApiService);
}
return Observable.error(throwable);
}
return Observable.error(throwable);
});
}
private Observable<List<WebSocketSubscribeBean>> refresh(UserApiService userApiService) {
// 如果上面检测到token过期就会进入到这里
// 然后下面的方法就是更新token
UserAccountHelper.saveLoginPast(false);
return userApiService.refreshToken(GrantType.REFRESH_TOKEN.getValue(), "all"
, UserAccountHelper.getRefreshToken())
.flatMap((Function<UserInfo, Observable<MqttBean>>) userInfo -> {
UserAccountHelper.setToken(userInfo.getAccess_token());
UserAccountHelper.setRefreshToken(userInfo.getRefresh_token());
UserAccountHelper.saveLoginState(userInfo, false);
return userApiService.getCloudConfig();
}).flatMap((Function<MqttBean, Observable<WorkSpaceBean>>) mqttBean -> {
CloudDataHelper.saveMqttData(mqttBean);
return userApiService.getWorkSpace();
}).flatMap((Function<WorkSpaceBean, Observable<List<WebSocketSubscribeBean>>>) workSpaceBean -> {
UserAccountHelper.setWorkSpace(workSpaceBean);
return userApiService.getWebSocketSubscribeInfo(workSpaceBean.getWorkspaceId());
})
.doOnNext(subscribeBeanList -> {
UserAccountHelper.setWebSocketSubscribe(subscribeBeanList);
UserAccountHelper.saveLoginPast(true);
});
}
@Override
public void setMaxRetryCount(int maxRetryCount) {
this.maxRetries = maxRetryCount;
}
}
同样我们需要编写module
@Module//必须配置的注解,表示这个对象是Module的配置规则
@InstallIn(SingletonComponent.class)//表示这个module中的配置是用来注入到Activity中的
public class RetryModule {
@Provides
RetryService provideRetryService() {
return new RetryServiceImpl();
}
}
然后我们就可以在BaseViewModel中添加retryWhen了,为了子类可以自定义我们可以给一个方法,提供重写
public Function<Observable<? extends Throwable>, Observable<?>> getRetryWhen() {
return retryService == null ? new RetryWhenNetworkException(ConstantsHelper.RETRY_WHEN_MAX_COUNT) : retryService;
}
这里我们有个默认的方法
public class RetryWhenNetworkException implements Function<Observable<? extends Throwable>, Observable<?>> {
private final String TAG = RetryWhenNetworkException.class.getSimpleName();
// 可重试次数
private final int maxConnectCount;
// 当前已重试次数
private int currentRetryCount = 0;
// 重试等待时间
private int waitRetryTime = 2000;
public RetryWhenNetworkException(int maxConnectCount) {
this.maxConnectCount = maxConnectCount;
}
public RetryWhenNetworkException(int maxConnectCount, int waitRetryTime) {
this.maxConnectCount = maxConnectCount;
this.waitRetryTime = waitRetryTime;
}
@Override
public Observable<?> apply(Observable<? extends Throwable> throwableObservable) throws Exception {
// 参数Observable中的泛型 = 上游操作符抛出的异常,可通过该条件来判断异常的类型
return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
@Override
public ObservableSource<?> apply(Throwable throwable) throws Exception {
// 输出异常信息
LogUtil.e(throwable);
/**
* 需求1:根据异常类型选择是否重试
* 即,当发生的异常 = 网络异常 = IO异常 才选择重试
*/
// if (throwable instanceof TimeoutException ) {
// FLog.d("属于IO异常,需重试");
LogUtil.show(TAG, "属于网络异常,需重试");
/**
* 需求2:限制重试次数
* 即,当已重试次数 < 设置的重试次数,才选择重试
*/
if (currentRetryCount < maxConnectCount) {
// 记录重试次数
currentRetryCount++;
LogUtil.show(TAG, "重试次数 = " + currentRetryCount);
/**
* 需求2:实现重试
* 通过返回的Observable发送的事件 = Next事件,从而使得retryWhen()重订阅,最终实现重试功能
*
* 需求3:延迟1段时间再重试
* 采用delay操作符 = 延迟一段时间发送,以实现重试间隔设置
*
* 需求4:遇到的异常越多,时间越长
* 在delay操作符的等待时间内设置 = 每重试1次,增多延迟重试时间0.5s
*/
// 设置等待时间
LogUtil.show(TAG, "等待时间 =" + waitRetryTime);
return Observable.just(1).delay(waitRetryTime, TimeUnit.MILLISECONDS);
} else {
// 若重试次数已 > 设置重试次数,则不重试
// 通过发送error来停止重试(可在观察者的onError()中获取信息)
return Observable.error(new Throwable("重试次数已超过设置次数 = " + currentRetryCount + ",即 不再重试;" + throwable));
}
}
});
}
}
记得子类继承BaseViewModel的时候记得在类上添加@HiltViewModel和@Inject注解,具体用法参考Hilt官方了,不做详细介绍了
好了我们简单优化先讲到这里。后面可能还会讲到图片选择器的封装、视频选择器的封装等等看情况吧