preface
I originally wanted to record the knowledge points related to cameras recently, but I found that I needed time to sort them out. So here I would like to introduce the overall architecture used in the live broadcast app I wrote recently.
Sunflower (sunflower) : Most of the previous projects were based on MVC and MVP architecture, so this one alone will be developed using MVVM (sunflower architecture makes me greedy).
Introduction to the
The final stage is based on MVVM
- UI: AndroidX + DataBinding + RxView + Bravh
- Data transfer: LiveData + LiveEventBus
- Web request: Retrofit + RxAndroid + OkHttp3
// implementation demos.support. multidex // androidX implementation demos.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. The design/implementation deps. Support. Recyclerview / / tencent live broadcast of the SDK implementation deps. LiteavSdk. Liteavsdk_smart Implementation deps.okKit. runtime // Implementation deps.okKit. runtime // implementation deps.okKit. runtime // Implementation deps.okKit. runtime Implementation demos.okhttp3. interceptor // gson implementation demos.gson. Runtime // Tencent IM Implementation Implementation deps.cosxml.runtime // implementation deps.cosxml.runtime // implementation deps.cosxml.runtime // implementation deps.cosxml.runtime // implementation deps.cosxml.runtime // 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.runtimeCopy the code
This is the overview of the packages introduced, and then the complete process demonstration for the business scenario follows:
Login scenarios
View
/** * 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(){Override public void bindUi(){ .subscribeOn(AndroidSchedulers.mainThread()) .to(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(this))) .subscribe(unit -> PermissionTools.requestPermission(this, () - > / / check to read and write permissions mViewModel. Login (mDataBinding. UserNameEdt. GetText (), toString (). The trim () / / Login request, mDataBinding.passwordEdt.getText().toString().trim()) , Permission.READ_PHONE_STATE)); // Click rxview. clicks(mdatabinding.registerimg) .to(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(this))) .subscribe(unit -> startActivity(new Intent(LoginActivity.this, RegisterActivity.class))); Public void subscribeUi() {Override public void subscribeUi() {// Notification of page status changes with sticky messages mViewModel.getLoginState().observe(this, state -> { switch (state) { case ERROR_CUSTOMER_SUCCESS_PASS: // Check mload.getobj ().show(); break; Case ERROR_CUSTOMER_PASSWORD_ERROR: // Account error case ERROR_CUSTOMER_USERNAME_ERROR: / / password error mDataBinding. PasswordEdt. SetText (" "); / / to empty password input box 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()); // Unloading if (bean.getCode() == 200) {// ToastUtil.showtoast (loginactivity.this, "Login succeeded!" ); startActivity(new Intent(LoginActivity.this, MainActivity.class)); finish(); } else {/ / login failed ToastUtil showToast (LoginActivity. This "logon failure:" + TCErrorConstants. GetErrorInfo (bean. GetCode ())); mDataBinding.passwordEdt.setText(""); // Clear the password input field}}); } @Override public void initRequest() { } @Override protected void onDestroy() { super.onDestroy(); Optional.ofNullable(mLoading).ifPresent(builder -> mLoading.getObj().dismiss()); // cancel Loading}}Copy the code
The login View above contains several modules
- InitViewModel () is a VIewModel initialization to ensure the integrity of the MVVM
- Init () handles initialization of some controls in a View
- BindUi () converts page events into Observables via RxView and binds them to specific functions in the ViewModel
- SubscribeUi () is a change to LiveData in a ViewModel, for example, or a View change caused by notifications returned by LiveEventBus
- InitRequest () is used to handle methods that are requested as soon as the View is entered
public abstract class MVVMActivity extends AppCompatActivity { public abstract void initViewModel(); public abstract void init(); public abstract void bindUi(); public abstract void subscribeUi(); /** * request network data */ public void initRequest(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); initViewModel(); init(); subscribeUi(); initRequest(); } @Override protected void onResume() { super.onResume(); bindUi(); }}Copy the code
This is the order in which each method is called
ViewModel
public class LoginViewModel extends ViewModel { private final LoginRepository repository; private final LifecycleOwner lifecycleOwner; private final MutableLiveData<Integer> loginState = new MutableLiveData<>(); / / login failed public LoginViewModel (LoginRepository repository, LifecycleOwner LifecycleOwner) {enclosing the repository = repository; this.lifecycleOwner = lifecycleOwner; } /** * @param userName * @param passWord passWord */ public void Login(String userName, String passWord) { if (checkInfo(userName, passWord)) { loginState.postValue(ERROR_CUSTOMER_SUCCESS_PASS); repository.loginReq(lifecycleOwner, userName, passWord); }} /** * Check whether the passWord entered by the user is valid ** @param userName account * @param passWord passWord * @return true: pass the check 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; }}Copy the code
The ViewModel, as the channel between the View and the Model, is responsible for managing LiveData and some business logic, while the View tries to update the UI through the two-way binding of LiveData.
Model
Here is the Repository for Model
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;
}
}
Copy the code
In addition to its complex business logic, Repository is mainly used as a data warehouse, storing business data (user account information) in singletons, handling business logic in requests, and performing a series of requests through a combination of RxAndroid and Retrofit. And notify the page via LiveEventBus or LiveData
HttpRequest
Network request module
// 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) / / this is a Retrofit of the standard way .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) // The requested network framework AddConverterFactory (GsonConverterFactory. The create ()) / / analytical data format. The addCallAdapterFactory (RxJava3CallAdapterFactory. The create ()) // Use RxJava as the callback adapter.build (); } } } return retrofit.create(service); }Copy the code
Flowable (back pressure) returned by network requests can be directly combined into a business logical structure in a chain manner
One of the above seemingly simple examples is MVVM + RxAndroid + RxView + DataBinding + LiveData + LiveEventBus + Retrofit
Some complex list pages, then added Bravh, to optimize the Adapter code volume
Public id: Programmer Cat