关于mvvm简易封装(三)

序言

主要是关于前两篇文章的优化总结,之前很多人问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

    //每个用到的模块都要加这两个,不能使用apiimplementation "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 {
//注入接口@InjectErrorService errorService;/*** 注意判断空,根据自己需求更改* @param model 错误吗实体*/@Overridepublic 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 {@InjectUserRouterService userRouterService;@Injectpublic ErrorServiceImpl() {}@Overridepublic boolean isLogin(String errorCode) {return !UserAccountHelper.isLoginPast(errorCode);}@Overridepublic void toLogin(Context mContext, ActivityResultLauncher<Intent> activityResultLauncher) {userRouterService.toLogin(mContext,activityResultLauncher);}@Overridepublic void toLogin(Context context) {userRouterService.toLogin(context);}@Overridepublic boolean hasPermission(String errorCode) {return true;}@Overridepublic void toNoPermission(Context mContext, ActivityResultLauncher<Intent> activityResultLauncher) {}@Overridepublic void toNoPermission(Context context) {}@Overridepublic void uploadErrorInfo(String errorInfo) {}@Overridepublic Class<?> getMainActivity() {return MainActivity.class;}@Overridepublic String getToken() {return UserAccountHelper.getToken();}@Overridepublic String getRefreshToken() {return UserAccountHelper.getRefreshToken();}@Overridepublic 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 {@ProvidesErrorService 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 {@Injectpublic UserRouterServiceImpl() {}@Overridepublic void toLogin(Context mContext, ActivityResultLauncher<Intent> activityResultLauncher) {activityResultLauncher.launch(new Intent(mContext, LoginActivity.class).addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT));}@Overridepublic 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 {@ProvidesUserRouterService provideUserRouterService() {return new UserRouterServiceImpl();}
}

请求接口的无缝刷新token

有的时候长期未登录的情况下,但是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;@Injectpublic RetryServiceImpl() {}/*** @param maxRetries 最大重试次数*/public RetryServiceImpl(int maxRetries) {this.maxRetries = maxRetries;}@Overridepublic 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过期就会进入到这里// 然后下面的方法就是更新tokenUserAccountHelper.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);});}@Overridepublic void setMaxRetryCount(int maxRetryCount) {this.maxRetries = maxRetryCount;}
}

同样我们需要编写module

@Module//必须配置的注解,表示这个对象是Module的配置规则
@InstallIn(SingletonComponent.class)//表示这个module中的配置是用来注入到Activity中的
public class RetryModule {@ProvidesRetryService 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;}@Overridepublic Observable<?> apply(Observable<? extends Throwable> throwableObservable) throws Exception {// 参数Observable<Throwable>中的泛型 = 上游操作符抛出的异常,可通过该条件来判断异常的类型return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {@Overridepublic 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官方了,不做详细介绍了

写在最后

好了我们简单优化先讲到这里。后面可能还会讲到图片选择器的封装、视频选择器的封装等等看情况吧

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/58966.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

centos7 ESXi 磁盘扩充容量

1、背景 有一天&#xff0c;突然程序报空间不足了。。。。。。 2023-06-23 02:26:51.631 UTC [26190] LOG: could not open temporary statistics file "pg_stat_tmp/global.tmp": No space left on device 2023-06-23 02:26:51.631 UTC [26190] LOG: could not …

vue table动态合并, 自定义合并,参照合并,组合合并

<template><div><el-table:data"tableData":span-method"objectSpanMethod"border:header-cell-style"{ textAlign: center }"><el-table-column prop"area" label"区域" align"center">…

Django Rest_Framework(二)

文章目录 1. http请求响应1.1. 请求与响应1.1.1 Request1.1.1.1 常用属性1&#xff09;.data2&#xff09;.query_params3&#xff09;request._request 基本使用 1.1.2 Response1.1.2.1 构造方式1.1.2.2 response对象的属性1&#xff09;.data2&#xff09;.status_code3&…

【升职加薪秘籍】我在服务监控方面的实践(4)-日志监控

大家好,我是蓝胖子&#xff0c;关于性能分析的视频和文章我也大大小小出了有一二十篇了&#xff0c;算是已经有了一个系列&#xff0c;之前的代码已经上传到github.com/HobbyBear/performance-analyze 接下来这段时间我将在之前内容的基础上&#xff0c;结合自己在公司生产上构…

SpringCloud实用篇1——eureka注册中心 Ribbon负载均衡原理 nacos注册中心

目录 1 微服务1.1 微服务的演变1.2 微服务1.3 SpringCloud1.4 小结 2 服务拆分及远程调用2.1 服务拆分2.2 服务拆分案例2.3 实现远程调用2.4 提供者与消费者 3 Eureka注册中心3.1 Eureka的结构和作用3.2 搭建eureka-server3.3 服务注册3.4 服务发现 4 Ribbon负载均衡4.1 负载均…

【css】渐变

渐变是设置一种颜色或者多种颜色之间的过度变化。 两种渐变类型&#xff1a; 线性渐变&#xff08;向下/向上/向左/向右/对角线&#xff09; 径向渐变&#xff08;由其中心定义&#xff09; 1、线性渐变 语法&#xff1a;background-image: linear-gradient(direction, co…

webshell链接工具-Godzilla(哥斯拉)

项目地址 https://github.com/BeichenDream/Godzilla

将Map存到数据库中,并且支持数据类型原样取回

1.数据库设计 1.1 表设计 create table variables (id bigint not null comment 主键,business_key varchar(128) null comment 业务key,key varchar(128) null comment Map中的key,value varchar(25…

机器学习概述及其主要算法

目录 1、什么是机器学习 2、数据集 2.1、结构 3、算法分类 4、算法简介 4.1、K-近邻算法 4.2、贝叶斯分类 4.3、决策树和随机森林 4.4、逻辑回归 4.5、神经网络 4.6、线性回归 4.7、岭回归 4.8、K-means 5、机器学习开发流程 6、学习框架 1、什么是机器学习 机器…

Linux C 语言 mosquitto 方式 MQTT 发布消息

1 说明 采用 mosquitto 库&#xff0c;实现对主题发布消息。 其中服务器有做限制&#xff0c;需要对应的 cilent id &#xff0c;cafile 、certfile 、keyfile 等配置 2 开发环境 采用ubuntu 直接编译调试 安装mosquitto 库 sudo apt install libmosquitto-dev sudo apt-ge…

如何通过ChatGPT优化简历帮助自己找到合适的工作

​ 通过对1000多名当前和最近的求职者进行调查发现&#xff0c;46%的人表示使用ChatGPT来撰写简历或求职信或两者兼而有之。其中大约70%确实得到了雇主更高的回应率&#xff1b;59%被录用。 2023年1月&#xff0c;三名麻省理工学院教授进行的一项研究发现&#xff0c;使用“…

PoseiSwap 开启“Poseidon”池,治理体系或将全面开启

PoseiSwap 曾在前不久分别以 IDO、IEO 的方式推出了 POSE 通证&#xff0c;但 PoseiSwap DEX 中并未向除 Zepoch 节点外的角色开放 POSE 资产的交易。而在前不久&#xff0c;PoseiSwap 推出了全新的“Poseidon”池&#xff0c;该池将向所有用户开放&#xff0c;并允许用户自由的…