关于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

    //每个用到的模块都要加这两个,不能使用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过期了,我们又不想总是跳登录页面,理想的是:登录过期后我获取缓存信息,然后无缝刷新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<Throwable>中的泛型 = 上游操作符抛出的异常,可通过该条件来判断异常的类型
        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官方了,不做详细介绍了

写在最后

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青丶穗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值