MVVM + RxAndroid + RxView + Data

作者: 虞_18bd | 来源:发表于2021-01-04 21:31 被阅读0次

    前言

    本来想记录一下最近相机相关的知识点的,但发现需要时间整理一下,那这里就介绍一下最近写的直播app中使用的整体架构吧。

    由于之前项目大多是用MVC,MVP的整体架构,所以这次一个人写直播项目时就干脆用MVVM进行开发(sunflower的架构让我很馋)

    简介

    最后现阶段是 基于 MVVM

    • UI: AndroidX + DataBinding + RxView + Bravh
    • 数据传递: LiveData + LiveEventBus
    • 网络请求: Retrofit + RxAndroid + OkHttp3
     // 分包工具
        implementation deps.support.multidex
        // androidX
        implementation deps.androidX.appcompat
        implementation deps.androidX.recyclerview
        implementation deps.androidX.constraintLayout
        implementation deps.androidX.lifecycle
        implementation deps.androidX.palette
        // material
        implementation deps.material.runtime
    //    implementation deps.support.design
    //    implementation deps.support.recyclerview
        // 腾讯直播SDK
        implementation deps.liteavSdk.liteavsdk_smart
        // 自定义采集控件
        implementation deps.liveKit.runtime
        // OkHttp3 + OkHttp3拦截器 腾讯云需要
        implementation deps.okHttp3.runtime
        implementation deps.okHttp3.interceptor
        // gson
        implementation deps.gson.runtime
        // 腾讯IM
        implementation deps.imsdk.runtime
        // Glide
        implementation deps.glide.runtime
        // 腾讯存储服务
        implementation deps.cosxml.runtime
        // B站弹幕
        implementation deps.DanmakuFlameMaster.runtime
        // rxAndroid + rxJava
        implementation deps.rxAndroid.runtime
        implementation deps.rxAndroid.rxjava
        // rxBinding
        implementation deps.rxBinding.runtime
        // autoDispose
        implementation deps.autoDispose.android
        implementation deps.autoDispose.lifecycle
        // retrofit
        implementation deps.retrofit.runtime
        implementation deps.retrofit.adapter
        implementation deps.retrofit.converter
        // xxpermissions
        implementation deps.xxpermissions.runtime
        // liveEventBus
        implementation deps.liveEventBus.runtime
        // banner
        implementation deps.banner.runtime
        // bravh
        implementation deps.bravh.runtime
        // hilt
    //    implementation deps.hilt.runtime
    //    implementation deps.hilt.lifecycle
    //    kapt deps.hilt.kapt
    //    kapt deps.hilt.compiler
        // leakCanary
        debugImplementation deps.leakCanary.runtime
    

    以上就是大致引入的包,然后接下来就是针对业务场景的一整套流程演示了:

    登录场景
    View
    /**
     * 登录页面
     */
    public class LoginActivity extends MVVMActivity {
        private static final String TAG = "LoginActivity";
    
        private LoadingDialog.Builder mLoading; // 加载页面
        private ActivityLoginBinding mDataBinding;// DataBinding
        private LoginViewModel mViewModel;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
        }
    
        @Override
        public void initViewModel() {
            mDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_login);
            ViewModelProvider.Factory factory = new LoginViewModelFactory(getApplication(), this);
            mViewModel = ViewModelProviders.of(this, factory).get(LoginViewModel.class);
        }
    
        @Override
        public void init(){
            mLoading = new LoadingDialog.Builder(LoginActivity.this);
            mLoading.setMessage(getString(R.string.login_loading_text));
            mLoading.create();
        }
    
        @Override
        public void bindUi(){
            // 登录请求
            RxView.clicks(mDataBinding.loginBtn)
                    .subscribeOn(AndroidSchedulers.mainThread())
                    .to(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(this)))
                    .subscribe(unit ->
                            PermissionTools.requestPermission(this, () ->             //                       校验读写权限
                                            mViewModel.Login(mDataBinding.userNameEdt.getText().toString().trim()  //  登录请求
                                                    , mDataBinding.passwordEdt.getText().toString().trim())
                                    , Permission.READ_PHONE_STATE));
            // 注册按钮
            RxView.clicks(mDataBinding.registerImg)
                    .to(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(this)))
                    .subscribe(unit -> startActivity(new Intent(LoginActivity.this, RegisterActivity.class)));  //  跳转注册页面
        }
    
        /**
         * 不带粘性消息
         */
        @Override
        public void subscribeUi() {
            // 页面状态变化通知  带粘性消息
            mViewModel.getLoginState().observe(this, state -> {
                switch (state) {
                    case ERROR_CUSTOMER_SUCCESS_PASS:   // 通过校验
                        mLoading.getObj().show();
                        break;
                    case ERROR_CUSTOMER_PASSWORD_ERROR: // 账号错误
                    case ERROR_CUSTOMER_USERNAME_ERROR: // 密码错误
                        mDataBinding.passwordEdt.setText("");  // 清空密码输入框
                        ToastUtil.showToast(this, TCErrorConstants.getErrorInfo(state));
                        break;
                }
            });
    
            // 登录信息返回通知
            LiveEventBus.get(RequestTags.LOGIN_REQ, BaseResponBean.class)
                    .observe(this, bean -> {
                        Optional.ofNullable(mLoading).ifPresent(builder -> mLoading.getObj().dismiss());  // 取消 Loading
                        if (bean.getCode() == 200) { // 登录成功
                            ToastUtil.showToast(LoginActivity.this, "登录成功!");
                            startActivity(new Intent(LoginActivity.this, MainActivity.class));
                            finish();
                        } else {                     // 登录失败
                            ToastUtil.showToast(LoginActivity.this, "登录失败:" + TCErrorConstants.getErrorInfo(bean.getCode()));
                            mDataBinding.passwordEdt.setText("");  // 清空密码输入框
                        }
                    });
        }
    
        @Override
        public void initRequest() {
    
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            Optional.ofNullable(mLoading).ifPresent(builder -> mLoading.getObj().dismiss()); // 取消 Loading
        }
    
    }
    

    以上的登录View中包含几个模块

    1. initViewModel() 是为了保证MVVM的完整性,进行的VIewModel初始化
    2. init() 用于处理一些View中控件的初始化
    3. bindUi() 是通过RxView,将页面的事件转换成Observable,然后在于ViewModel中具体的功能进行绑定
    4. subscribeUi() 是例如ViewModel中LiveData的变化,或是通过LiveEventBus返回的通知引起的View变化
    5. initRequest() 用于处理刚进入View时就要请求的方法
    public abstract class MVVMActivity extends AppCompatActivity {
    
        public abstract void initViewModel();
    
        public abstract void init();
    
        public abstract void bindUi();
    
        public abstract void subscribeUi();
    
        /**
         * 请求网络数据
         */
        public abstract void initRequest();
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            initViewModel();
            init();
            subscribeUi();
            initRequest();
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            bindUi();
        }
    }
    

    以上就是每个方法的调用顺序

    ViewModel
    public class LoginViewModel extends ViewModel {
    
        private final LoginRepository repository;
        private final LifecycleOwner lifecycleOwner;
        private final MutableLiveData<Integer> loginState = new MutableLiveData<>();  // 登录失败
    
        public LoginViewModel(LoginRepository repository,LifecycleOwner lifecycleOwner) {
            this.repository = repository;
            this.lifecycleOwner = lifecycleOwner;
        }
    
        /**
         * 登录行为
         *
         * @param userName 账号
         * @param passWord 密码
         */
        public void Login(String userName, String passWord) {
            if (checkInfo(userName, passWord)) {
                loginState.postValue(ERROR_CUSTOMER_SUCCESS_PASS);
                repository.loginReq(lifecycleOwner, userName, passWord);
            }
        }
    
        /**
         * 检测用户输入的账号密码是否合法
         *
         * @param userName 账号
         * @param passWord 密码
         * @return true:通过检测 false:未通过
         */
        private boolean checkInfo(String userName, String passWord) {
            if (!TCUtils.isUsernameVaild(userName)) {
                loginState.postValue(ERROR_CUSTOMER_USERNAME_ERROR);
                return false;
            }
            if (!TCUtils.isPasswordValid(passWord)) {
                loginState.postValue(ERROR_CUSTOMER_PASSWORD_ERROR);
                return false;
            }
            return true;
        }
    
        public LiveData<Integer> getLoginState() {
            return loginState;
        }
    }
    

    ViewModel作为连通View以及Model之间的通道,负责管理LiveData,以及一些业务上的逻辑,而View尽量通过LiveData的双向绑定实现UI的更新。

    Model

    这里时Model的代表 Repository

    public class LoginRepository extends BaseRepository {
    
        private final static String TAG = "LoginRepository";
    
        private final static String PREFERENCE_USERID = "userid";
        private final static String PREFERENCE_USERPWD = "userpwd";
    
        /**
         * 单例模式
         */
        @SuppressLint("StaticFieldLeak")
        private static volatile LoginRepository singleton = null;
    
        /**********************************     本地数据缓存    **************************************/
        private LoginResponBean mUserInfo = new LoginResponBean();  // 登录返回后 用户信息存在这
        private final LoginSaveBean loginSaveBean = new LoginSaveBean();  // 用于保存用户登录信息
        private TCUserMgr.CosInfo mCosInfo = new TCUserMgr.CosInfo();   // COS 存储的 sdkappid
    
        private Context mContext;              //                                       初始化一些组件需要使用
    
        /**
         * 初始化缓存数据
         */
        private void initData() {
            loadUserInfo(); //  是否有缓存账号数据
        }
    
        private void loadUserInfo() {
            if (mContext == null) return;
            TXLog.d(TAG, "xzb_process: load local user info");
            SharedPreferences settings = mContext.getSharedPreferences("TCUserInfo", Context.MODE_PRIVATE);
            loginSaveBean.setmUserId(settings.getString(PREFERENCE_USERID, ""));
            loginSaveBean.setmUserPwd(settings.getString(PREFERENCE_USERPWD, ""));
        }
    
        private void saveUserInfo() {
            if (mContext == null) return;
            TXLog.d(TAG, "xzb_process: save local user info");
            SharedPreferences settings = mContext.getSharedPreferences("TCUserInfo", Context.MODE_PRIVATE);
            SharedPreferences.Editor editor = settings.edit();
            editor.putString(PREFERENCE_USERID, loginSaveBean.getmUserId());
            editor.putString(PREFERENCE_USERPWD, loginSaveBean.getmUserPwd());
            editor.apply();
        }
    
        /**
         * 登录请求
         *
         * @param userName 账号
         * @param passWord 密码
         */
        public void loginReq(LifecycleOwner lifecycleOwner, String userName, String passWord) {
            LoginRequestBuilder.loginFlowable(userName, passWord)
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .flatMap((Function<BaseResponBean<LoginResponBean>, Flowable<BaseResponBean<AccountInfoBean>>>) loginBean -> {
                        if (loginBean != null) { // 登录成功
                            Optional.ofNullable(loginBean.getData()).ifPresent(userInfo -> mUserInfo = userInfo); //                        保存返回的数据
                            if (loginBean.getMessage() != null) {
                                LiveEventBus.get(RequestTags.LOGIN_REQ, BaseResponBean.class)
                                        .post(new BaseResponBean<>(loginBean.getCode(), loginBean.getMessage()));         // 页面要处理的逻辑(注册返回)
                            }
                            if (loginBean.getCode() == 200
                                    && loginBean.getData() != null
                                    && loginBean.getData().getToken() != null
                                    && loginBean.getData().getRoomservice_sign() != null
                                    && loginBean.getData().getRoomservice_sign().getUserID() != null) {
                                setToken(loginBean.getData().getToken());  //                                              Token 保存到本地 用于后期请求鉴权
                                setUserId(loginBean.getData().getRoomservice_sign().getUserID());//                        UserId 保存到本地 当前登录的账号
                                initMLVB();//                                                                              初始化直播SDK
                                return LoginRequestBuilder.accountFlowable(getUserId(), getToken()); //                             请求账户信息
                            } else {
                                return Flowable.error(new ApiException(loginBean.getCode(), loginBean.getMessage()));  // 抛出登录异常  不会继续链式调用
                            }
                        }
                        return Flowable.error(new ApiException(-1, "网络异常"));  // 抛出登录异常  不会继续链式调用
                    })
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .to(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(lifecycleOwner)))
                    .subscribe(new DisposableSubscriber<BaseResponBean<AccountInfoBean>>() {
                        @Override
                        public void onNext(BaseResponBean<AccountInfoBean> accountBean) {
                            if (accountBean != null && accountBean.getCode() == 200) {  // 查询账户信息返回
                                if (accountBean.getData() != null) {
                                    if (accountBean.getData().getAvatar() != null)
                                        loginSaveBean.setmUserAvatar(accountBean.getData().getAvatar());  //      保存用户头像信息
                                    if (accountBean.getData().getNickname() != null)
                                        loginSaveBean.setmUserName(accountBean.getData().getNickname()); //       用户称呼
                                    if (accountBean.getData().getFrontcover() != null)
                                        loginSaveBean.setmCoverPic(accountBean.getData().getFrontcover());//      直播封面?
                                    if (accountBean.getData().getSex() >= 0) {
                                        loginSaveBean.setmSex(accountBean.getData().getSex());//                  用户性别
                                    }
                                }
                            }
                        }
    
                        @Override
                        public void onError(Throwable t) {
                            if (t instanceof ApiException) {
                                Log.e("TAG", "request error" + ((ApiException) t).getStatusDesc());
                            } else {
                                Log.e("TAG", "request error" + t.getMessage());
                            }
                        }
    
                        @Override
                        public void onComplete() {
    
                        }
                    });
        }
    
    
    
        /**
         * 注册账号请求
         *
         * @param username 账户名
         * @param password 密码
         */
        public void registerReq(LifecycleOwner lifecycleOwner,String username, String password) {
            LoginRequestBuilder.registerFlowable(username, password)
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .to(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(lifecycleOwner)))
                    .subscribe(new DisposableSubscriber<BaseResponBean>() {
                        @Override
                        public void onNext(BaseResponBean registerBean) {
                            if (registerBean != null) {
                                LiveEventBus.get(RequestTags.REGISTER_REQ, BaseResponBean.class)
                                        .post(new BaseResponBean<>(registerBean.getCode(), registerBean.getMessage()));         // 页面要处理的逻辑(登录返回)
                            }
                        }
    
                        @Override
                        public void onError(Throwable t) {
    
                        }
    
                        @Override
                        public void onComplete() {
    
                        }
                    });
        }
    
    
        /**
         * 初始化直播SDK
         */
        public void initMLVB() {
            // 校验数据完整性
            if (mUserInfo == null || mContext == null
                    || mUserInfo.getRoomservice_sign() == null
                    || mUserInfo.getRoomservice_sign().getSdkAppID() == 0
                    || mUserInfo.getRoomservice_sign().getUserID() == null
                    || mUserInfo.getRoomservice_sign().getUserSig() == null) return;
    
            LoginInfo loginInfo = new LoginInfo();
            loginInfo.sdkAppID = mUserInfo.getRoomservice_sign().getSdkAppID();
            loginInfo.userID = getUserId();
            loginInfo.userSig = mUserInfo.getRoomservice_sign().getUserSig();
    
            String userName = loginSaveBean.getmUserName();
            loginInfo.userName = !TextUtils.isEmpty(userName) ? userName : getUserId();
            loginInfo.userAvatar = loginSaveBean.getmUserAvatar();
            MLVBLiveRoom liveRoom = MLVBLiveRoom.sharedInstance(mContext);
            liveRoom.login(loginInfo, new IMLVBLiveRoomListener.LoginCallback() {
                @Override
                public void onError(int errCode, String errInfo) {
                    Log.i(TAG, "MLVB init onError: errorCode = " + errInfo + " info = " + errInfo);
                }
    
                @Override
                public void onSuccess() {
                    Log.i(TAG, "MLVB init onSuccess: ");
                }
            });
        }
    
        /**
         * 自动登录
         */
        public void autoLogin() {
    
        }
    
        public void setmContext(Context context) {
            this.mContext = context;
            initData();
        }
    
        public LoginSaveBean getLoginInfo(){
            return loginSaveBean;
        }
    
        public static LoginRepository getInstance() {
            if (singleton == null) {
                synchronized (LoginRepository.class) {
                    if (singleton == null) {
                        singleton = new LoginRepository();
                    }
                }
            }
            return singleton;
        }
    }
    

    除去里面复杂的业务逻辑,可以看到Repository的主要作用是数据仓库,如用单例形式保存一些业务上的数据(用户账户信息),负责处理请求中的业务逻辑,通过RxAndroid和Retrofit的组合,来完成一系列的请求,并通过LiveEventBus或是LiveData来通知页面

    HttpRequest

    网络请求模块

    // LoginRequestBuilder.java
    public static Flowable<BaseResponBean<LoginResponBean>> loginFlowable(String userName, String passWord) {
        HashMap<String, String> requestParam = new HashMap<>();
        requestParam.put("userid", userName);
        requestParam.put("password", TCUtils.md5(TCUtils.md5(passWord) + userName));
        return RetrofitTools.getInstance(LoginService.class) // 这里是很标准的Retrofit写法
                .login(RequestBodyMaker.getRequestBodyForParams(requestParam));
    }
    
    // LoginService.java
    @POST("/login")
    Flowable<BaseResponBean<LoginResponBean>> login(@Body RequestBody requestBody);
    
    // RetrofitTools.java
    public static <T> T getInstance(final Class<T> service) {
        if (okHttpClient == null) {
            synchronized (RetrofitTools.class) {
                HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(new HttpInteraptorLog());
                interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
                okHttpClient = new OkHttpClient.Builder()
                            .addInterceptor(interceptor)
                            .connectTimeout(5, TimeUnit.SECONDS)
                            .readTimeout(5, TimeUnit.SECONDS)
                            .writeTimeout(5, TimeUnit.SECONDS)
                            .build();
                }
            }
    
            if (retrofit == null) {
                synchronized (RetrofitTools.class) {
                    if(retrofit == null) {
                        retrofit = new Retrofit.Builder()
                                .baseUrl(TCGlobalConfig.APP_SVR_URL)         //BaseUrl
                                .client(okHttpClient)                       //请求的网络框架
                                .addConverterFactory(GsonConverterFactory.create())     //解析数据格式
                                .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) // 使用RxJava作为回调适配器
                                .build();
                    }
                }
            }
        return retrofit.create(service);
    }
    

    网络请求返回的Flowable(背压)可以直接通过组合,链式的方式,组合成符合业务逻辑的结构

    以上看上去十分简单的一个例子就是糅合了MVVM + RxAndroid + RxView + DataBinding + LiveData + LiveEventBus + Retrofit

    一些复杂的列表页面,则加入了Bravh,来优Adapter代码量

    相关文章

      网友评论

        本文标题:MVVM + RxAndroid + RxView + Data

        本文链接:https://www.haomeiwen.com/subject/hlrboktx.html