MVVM + RxAndroid + RxView + DataBinding + LiveData + LiveEventBus + Retrofit
前言
本来想记录一下最近相机相关的知识点的,但发现需要时间整理一下,那这里就介绍一下最近写的直播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中包含几个模块
initViewModel() 是为了保证MVVM的完整性,进行的VIewModel初始化
init() 用于处理一些View中控件的初始化
bindUi() 是通过RxView,将页面的事件转换成Observable,然后在于ViewModel中具体的功能进行绑定
subscribeUi() 是例如ViewModel中LiveData的变化,或是通过LiveEventBus返回的通知引起的View变化
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.javapublic 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.javapublic 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代码量
作者:虞_18bd
链接:https://www.jianshu.com/p/3b869775cc92
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。