android { ... dataBinding { enabled = true } }
...
...
...
屏幕旋转时viewModel生命周期如下:
public class LoginViewModel extends AndroidViewModel {...}
// 省略,看详情
布局:layout/activity_login.xml
view层LoginActivity:
package com.xtm.mvvmdemo.module.login;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import com.xtm.mvvmdemo.R;
import com.xtm.mvvmdemo.databinding.ActivityLoginBinding;
import com.xtm.mvvmdemo.module.login.bean.LoginResponseBean;
public class LoginActivity extends AppCompatActivity {
private ActivityLoginBinding mBinding;
private LoginViewModel loginViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_login);
createViewModel();
bindViewModelToLayout();
registerViewModelObserver();
initData();
}
private void initData() {
// 屏幕旋转,activity销毁重建,需要重新获取下保存在viewModel中的数据
mBinding.etUserName.setText(""+ (TextUtils.isEmpty(loginViewModel.inputName)?"":loginViewModel.inputName));
mBinding.etPwd.setText(""+ (TextUtils.isEmpty(loginViewModel.inputPwd)?"":loginViewModel.inputPwd));
}
/**
* 创建viewModel
*/
private void createViewModel() {
// loginViewModel = new LoginViewModel(getApplication());绝对不可以直接去创建ViewModel的实例,而是一定要通过ViewModelProvider来获取ViewModel的实例
loginViewModel = new ViewModelProvider(this,new CommonViewModelFactory(new LoginViewModel(getApplication()))).get(LoginViewModel.class);
}
/**
* ViewModelProvider及工厂是有个Map集合存放viewModel对象,相当于把viewModel存入内存中
*/
class CommonViewModelFactory implements ViewModelProvider.Factory {
ViewModel viewModel;
public CommonViewModelFactory(ViewModel viewModel) {
this.viewModel = viewModel;
}
@NonNull
@Override
public T create(@NonNull Class modelClass) {
return (T) viewModel;
}
}
/**
* 布局与viewModel绑定
* 例如:xml中,android:text="@{loginViewModel.success}"
*/
private void bindViewModelToLayout() {
mBinding.setVariable(1, loginViewModel);// 传递viewModel对象绑定到布局,当然也可以绑定其他对象,需要与布局中data标签下的类型匹配
mBinding.setLifecycleOwner(this);// 这里的参数类型为LifecycleOwner,把自己的生命周期告诉给已绑定对象的布局
mBinding.executePendingBindings();// setVariable时候立即更新所有绑定对象的UI
}
/**
* 向viewModel注册观察者
* 监听viewModel获取到的数据,来更新UI(数据是主角)
* 如果不想bindViewModelToLayout(),直接可以用这种方式监听viewModel数据来更新view
* liveData的observe与observeForever的区别:前者自动,后者需要手动removeObserver
*/
private void registerViewModelObserver() {
// 登录返回BaseResponse监听 这里第一个参数 LifecycleOwner类型,把自己的生命周期告诉给viewModel
loginViewModel.result.observe(this, new Observer>() {
@Override
public void onChanged(LoginResponseBean stringLoginResponseBean) {
// do nothing
}
});
// 登录成功监听
loginViewModel.success.observe(this, new Observer() {
@Override
public void onChanged(String s) {
// mBinding.tvLoginState.setText(s);// 可以放到xml中监听
}
});
// 登录失败监听
loginViewModel.failed.observe(this, new Observer() {
@Override
public void onChanged(String s) {
// mBinding.tvLoginState.setText(s);// 可以放到xml中监听
}
});
}
/**
* 点击登录按钮
* @param view
*/
public void startLogin(View view) {
loginViewModel.login(mBinding.etUserName.getText().toString(),mBinding.etPwd.getText().toString());
}
}
viewModel层:
package com.xtm.mvvmdemo.module.login;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.MutableLiveData;
import com.xtm.mvvmdemo.module.login.bean.LoginResponseBean;
import com.xtm.mvvmdemo.module.login.model.LoginModel;
/**
* viewModel层
* 不持有view层对象(相对于mvp,有效防止内存泄漏)
*/
public class LoginViewModel extends AndroidViewModel {
public MutableLiveData> result = new MutableLiveData<>();// 用于回调数据给v,整个登录返回实体
public MutableLiveData success = new MutableLiveData<>();// 用于回调数据给v,成功
public MutableLiveData failed = new MutableLiveData<>();// 用于回调数据给v,失败
private LoginModel loginModel;
public LoginViewModel(@NonNull Application application) {
super(application);
createModel();
}
/**
* 创建model
*/
private void createModel() {
loginModel = new LoginModel();
}
/**
* 登录操作
* @param name
* @param pwd
* LiveData的setValue()来更新UI只能在主线程中调用,postValue()可以在任何线程中调用(类比view的invalidate()与postInvalidate()的区别)
*/
public void login(String name, String pwd) {
loginModel.login(name, pwd, new LoginModel.ILoginDataCallback() {
@Override
public void onLoginState(LoginResponseBean baseResponse) {
result.postValue(baseResponse);// 通知v
if(baseResponse.getCode()==200){
success.postValue(baseResponse.getData());// 通知v
}else {
failed.postValue(baseResponse.getMsg());// 通知v
}
}
});
}
}
model层:
package com.xtm.mvvmdemo.module.login.model;
import android.os.SystemClock;
import com.xtm.mvvmdemo.module.login.bean.LoginResponseBean;
/**
* 登录业务m层的实现
*/
public class LoginModel {
public void login(String name, String pwd, ILoginDataCallback loginDataCallback) {
new Thread(){
@Override
public void run() {
SystemClock.sleep(3000);// 模拟耗时操作
LoginResponseBean responseBean;
if("xtm".equals(name) && "123".equals(pwd)){
responseBean = new LoginResponseBean<>(200,"登录成功","欢迎光临!");
}else {
responseBean = new LoginResponseBean<>(400,"登录失败",null);
}
loginDataCallback.onLoginState(responseBean);
}
}.start();
}
/**
* 用于把数据回传给p层
*/
public interface ILoginDataCallback{
void onLoginState(LoginResponseBean baseResponse);
}
}
实体类:
package com.xtm.mvvmdemo.module.login.bean;
/**
* 登录响应
*/
public class LoginResponseBean {
int code;// 状态码
String msg;// 状态消息
T data;// 响应数据
public LoginResponseBean(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
public T getData() {
return data;
}
}
ps:
lifecycle生命周期的处理流程:
ui把自己的生命周期通知给viewmodel
也就是UI是被观察者,viewmodel是观察者
而androidx.appcompat.app.AppCompatActivity已经实现了LifecycleOwner 接口,类似如下的MyActivity
被观察者:LifecycleOwner
public class MyActivity extends Activity implements LifecycleOwner {
private LifecycleRegistry lifecycleRegistry;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
lifecycleRegistry = new LifecycleRegistry(this);
lifecycleRegistry.markState(Lifecycle.State.CREATED);// 通知观察者
}
@Override
public void onStart() {
super.onStart();
lifecycleRegistry.markState(Lifecycle.State.STARTED);// 通知观察者
}
@NonNull
@Override
public Lifecycle getLifecycle() {
return lifecycleRegistry;
}
}
观察者:LifecycleObserver
class MyLocationListener implements LifecycleObserver {
private boolean enabled = false;
public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
...
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)// 被动收到通知
void start() {
if (enabled) {
// connect
}
}
public void enable() {
enabled = true;
if (lifecycle.getCurrentState().isAtLeast(STARTED)) {// 主动获取被观察者状态
// connect if not connected
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)// 被动收到通知
void stop() {
// disconnect if connected
}
}
谷歌推荐的架构如下:
现在在原来的基础上增加一个Repository
在这之前先了解下rxjava3的使用:
引入rxandroid:GitHub - ReactiveX/RxAndroid: RxJava bindings for Android
model层修改如下:
package com.xtm.mvvmdemo.module.login.model;
import android.os.SystemClock;
import com.xtm.mvvmdemo.module.login.bean.LoginResponseBean;
import com.xtm.mvvmdemo.module.login.bean.ReqUserBean;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.annotations.NonNull;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.functions.Function;
import io.reactivex.rxjava3.schedulers.Schedulers;
/**
* 登录业务m层的实现
*/
public class LoginModel {
public void login(String name, String pwd, ILoginDataCallback loginDataCallback) {
// 方式一
new Thread(){
@Override
public void run() {
SystemClock.sleep(3000);// 模拟耗时操作
LoginResponseBean responseBean;
if("xtm".equals(name) && "123".equals(pwd)){
responseBean = new LoginResponseBean<>(200,"登录成功","欢迎光临!");
}else {
responseBean = new LoginResponseBean<>(400,"登录失败",null);
}
loginDataCallback.onLoginState(responseBean);
}
}.start();
// 方式二
ReqUserBean reqUserBean = new ReqUserBean(name,pwd);
Disposable disposable1 = Observable.just(reqUserBean)
.map(new Function() {
@Override
public LoginResponseBean apply(ReqUserBean reqUserBean) throws Throwable {
SystemClock.sleep(3000);// 模拟耗时操作
LoginResponseBean responseBean;
if ("xtm".equals(reqUserBean.getName()) && "123".equals(reqUserBean.getPwd())) {
responseBean = new LoginResponseBean<>(200, "登录成功", "欢迎光临!");
} else {
responseBean = new LoginResponseBean<>(400, "登录失败", null);
}
return responseBean;
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer() {
@Override
public void accept(LoginResponseBean loginResponseBean) throws Throwable {
loginDataCallback.onLoginState(loginResponseBean);
}
});
// 方式三
Disposable disposable2 = Flowable.just(reqUserBean)
.map(new Function() {
@Override
public LoginResponseBean apply(ReqUserBean reqUserBean) throws Throwable {
SystemClock.sleep(3000);// 模拟耗时操作
LoginResponseBean responseBean;
if ("xtm".equals(reqUserBean.getName()) && "123".equals(reqUserBean.getPwd())) {
responseBean = new LoginResponseBean<>(200, "登录成功", "欢迎光临!");
} else {
responseBean = new LoginResponseBean<>(400, "登录失败", null);
}
return responseBean;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer() {
@Override
public void accept(LoginResponseBean loginResponseBean) throws Throwable {
loginDataCallback.onLoginState(loginResponseBean);
}
});
}
/**
* 用于把数据回传给p层
*/
public interface ILoginDataCallback{
void onLoginState(LoginResponseBean baseResponse);
}
}
虽然之前解决了viewModel持有view导致的view的内存泄漏问题,不过上述还存在一个问题,当view销毁的时候,并没有停止异步任务 ,导致资源浪费。
解决办法:
1.在每个view销毁的时候调用
// 丢弃掉提交的任务
if(disposable!=null && !disposable.isDisposed()){
disposable.dispose();
}
这样写不太优雅,可以统一在仓库类中处理:
新建仓库:
package com.xtm.mvvmdemo.module.login.base;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.schedulers.Schedulers;
/**
* viewModel专属仓库
* 统一对资源的管理(添加,移除,释放)
*
*/
public class BaseRepository {
private CompositeDisposable mCompositeDisposable;// rxjava订阅的任务队列
public BaseRepository() {
if(mCompositeDisposable==null){
mCompositeDisposable = new CompositeDisposable();
}
}
/**
* 绑定观察者并添加到任务队列
* @param flowable 被观察者
* @param subscriber 观察者
* @param 参数对象
*/
public void addDisposable(Flowable flowable, Consumer subscriber){
Disposable disposable = flowable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(subscriber);
mCompositeDisposable.add(disposable);
}
/**
* 从任务队列移除某个已绑定的Disposable
* @param disposable
*/
public void removeDisposable(Disposable disposable){
mCompositeDisposable.remove(disposable);
}
/**
* 清空任务队列
*/
public void clearDisposable(){
if(mCompositeDisposable!=null){
mCompositeDisposable.dispose();
}
}
}
去掉之前的LoginModel类,修改LoginViewModel类:
package com.xtm.mvvmdemo.module.login;
import android.app.Application;
import android.os.SystemClock;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.MutableLiveData;
import com.xtm.mvvmdemo.module.login.base.BaseRepository;
import com.xtm.mvvmdemo.module.login.bean.LoginResponseBean;
import com.xtm.mvvmdemo.module.login.bean.ReqUserBean;
import com.xtm.mvvmdemo.module.login.model.LoginModel;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.functions.Function;
/**
* viewModel层
* 不持有view层对象(相对于mvp,有效防止内存泄漏)
*/
public class LoginViewModel extends AndroidViewModel {
public String inputName;
public String inputPwd;
public MutableLiveData> result = new MutableLiveData<>();// 用于回调数据给v,整个登录返回实体
public MutableLiveData success = new MutableLiveData<>();// 用于回调数据给v,成功
public MutableLiveData failed = new MutableLiveData<>();// 用于回调数据给v,失败
private LoginModel loginModel;
private final BaseRepository mBaseRepository;
public LoginViewModel(@NonNull Application application) {
super(application);
mBaseRepository = new BaseRepository();// 0.创建仓库
}
/**
* 登录操作
* @param name
* @param pwd
*/
public void login(String name, String pwd) {
ReqUserBean reqUserBean = new ReqUserBean(name,pwd);
// 1.创建被观察者
Flowable flowable = Flowable.just(reqUserBean)
.map(new Function() {
@Override
public LoginResponseBean apply(ReqUserBean reqUserBean) throws Throwable {
SystemClock.sleep(3000);// 模拟耗时操作
LoginResponseBean responseBean;
if ("xtm".equals(reqUserBean.getName()) && "123".equals(reqUserBean.getPwd())) {
responseBean = new LoginResponseBean<>(200, "登录成功", "欢迎光临!");
} else {
responseBean = new LoginResponseBean<>(400, "登录失败", null);
}
return responseBean;
}
});
// 2.创建观察者
Consumer consumer = new Consumer() {
@Override
public void accept(LoginResponseBean baseResponse) throws Throwable {
result.postValue(baseResponse);// 通知v
if(baseResponse.getCode()==200){
success.postValue((String) baseResponse.getData());// 通知v
}else {
failed.postValue(baseResponse.getMsg());// 通知v
}
}
};
// 3.注册观察者
mBaseRepository.addDisposable(flowable,consumer);
}
@Override
protected void onCleared() {
super.onCleared();
// 4.移除观察者
mBaseRepository.clearDisposable();// 清空仓库任务
}
}