指数据逻辑和实体模型
指布局文件
指Activity,既要负责页面的展示和交互,还得负责数据的请求和业务逻辑之类的工作。
看起来MVC架构很清晰,但是实际的开发中,请求的业务代码往往被丢到了Activity里面,大家都知道layout.xml的布局文件只能提供默认的UI设置,所以开发中视图层的变化也被丢到了Activity里面,再加上Activity本身承担着控制层的责任。所以Activity达成了MVC集合的成就,最终我们的Activity就变得越来越难看,从几百行变成了几千行,维护的成本也越来越高。
MVP和MVC 相比而言,唯一的差别是Model和View之间不进行通讯,都是通过Presenter完成。
实体模型
Activity 或者Fragment,负责View的绘制以及与用户交互
负责View与Model间的交互与逻辑处理
Presenter可以使View(Activity)不用直接和Model打交道,View(Activity)只用负责页面的显示和交互,剩下的和Model交互的事情都交给Presenter做,比如一些网络请求、数据的获取等,当Presenter获取到数据后再交给View(Activity)进行展示,这样,Activity的任务就大大减小了。
具体操作思路:MVP把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,Model类还是原来的Model。在MVP模式中Activity的功能就是响应生命周期和显示界面,具体其他的工作都丢到了Presenter层中进行完成,Presenter其实是Model层和View层的桥梁。下面的例子解决了内存泄漏问题
界面需要提供的UI方法中会有很多类似的UI方法,可以把它们提取到一个公共的父类接口中。比如提取显示loading界面和隐藏loading界面的方法,其他的view层接口就可以直接继承BaseView接口,不必重复的写显示和隐藏loading界面方法。
public interface BaseView {
void showLoading();
void hideLoading();
void showMessage(String message);
}
共有的功能:添加view的绑定与销毁。解决内存泄漏问题。
public abstract class BasePresenter <V extends BaseView>{
private V mView;
/**
* 绑定 View
* @param mView
*/
public void attachMView(V mView){
this.mView=mView;
}
/**
* 解绑View
*/
public void detachMView(){
mView=null;
}
public V getMView() {
return mView;
}
}
共有的功能:Presenter绑定到activity,View的绑定和解绑操作。
public abstract class BaseActivity<V extends BaseView,P extends BasePresenter<V>> extends AppCompatActivity {
protected P mPresenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (mPresenter==null){
mPresenter=initPresenter();
}
mPresenter.attachMView((V) this);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mPresenter!=null){
mPresenter.detachMView();
}
}
// 初始化presenter
protected abstract P initPresenter();
}
public interface LoginView extends BaseView {
void onResultSuccess(User user);
void onResultFail(String errorMessage);
}
public class LoginPresenter extends BasePresenter<LoginView> {
public void request_login(String name, String pwd) {
NetWorkManager.getInstance().getApiService().login(name, pwd)
.compose(SchedulerProvider.getInstance().applySchedulers())
.subscribe(new Observer<ApiResponse<User>>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
if (getMView()!=null){
getMView().showLoading();
}
}
@Override
public void onNext(@NonNull ApiResponse<User> userApiResponse) {
if (getMView()!=null){
getMView().hideLoading();
if (userApiResponse.getErrorCode()==0){
getMView().onResultSuccess(userApiResponse.getData());
}else {
getMView().showMessage(userApiResponse.getErrorMsg());
}
}
}
@Override
public void onError(@NonNull Throwable e) {
if (getMView()!=null){
getMView().hideLoading();
getMView().onResultFail(e.getMessage());
}
}
@Override
public void onComplete() {
}
});
}
}
public class LoginActivity extends BaseActivity<LoginView,LoginPresenter> implements LoginView{
private EditText etName;
private EditText etPassword;
private Button btn_login;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initView();
}
@Override
protected LoginPresenter initPresenter() {
return new LoginPresenter();
}
private void initView() {
etName = findViewById(R.id.etName);
etPassword = findViewById(R.id.etPassword);
btn_login = findViewById(R.id.btn_login);
btn_login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
login();
}
});
}
private void login(){
String username=etName.getText().toString();
String passWord=etPassword.getText().toString();
mPresenter.request_login(username,passWord);
}
@Override
public void onResultSuccess(User user) {
showMessage(user.toString());
}
@Override
public void onResultFail(String errorMessage) {
}
@Override
public void showLoading() {
}
@Override
public void hideLoading() {
}
@Override
public void showMessage(String message) {
Toast.makeText(this,message,Toast.LENGTH_SHORT).show();
}
}
以上就是以登录为例MVP例子。完整代码
MVVM 通过双向绑定的机制,实现数据和UI内容,只要想改其中一方,另一方都能够及时更新的一种设计理念。
主要是指Activity或者Fragment,负责页面的展示以及View的变化,不参与任何逻辑和数据的处理。
主要负责业务逻辑和数据处理,本身不持有View层引用。
实体类JavaBean。主要负责从本地数据库或者远程服务器来获取数据。
MVVM的本质是数据驱动,把解耦做的更彻底,viewModel不持有view。View产生事件,使用 ViewModel进行逻辑处理后,通知Model更新数据,Model把更新的数据给ViewModel,ViewModel自动通知View更新界面,而不是主动调用View的方法。
在BaseActivity 主要处理了初始化viewModel, viewModel 和lifecycle 生命周期绑定 等工作。
open class BaseActivity <VM : BaseViewModel>: AppCompatActivity(){
lateinit var mViewModel: VM
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initViewModel()
createObserve()
}
/** 提供编写LiveData监听逻辑的方法 */
open fun createObserve() {
mViewModel.apply {
exception.observe(this@BaseActivity) {
LogUtil.e("网络请求错误:${it.message}")
when (it) {
is SocketTimeoutException -> ToastUtil.showShort(
this@BaseActivity,
"网络超时"
)
is ConnectException, is UnknownHostException -> ToastUtil.showShort(
this@BaseActivity,
"网络连接异常"
)
else -> ToastUtil.showShort(
this@BaseActivity, it.message ?: "网络错误"
)
}
}
complete.observe(this@BaseActivity){
// todo 请求完成工作
}
}
}
open fun providerVMClass(): Class<VM>? = null
private fun initViewModel() {
providerVMClass()?.let {
mViewModel = ViewModelProvider(this).get(it)
lifecycle.addObserver(mViewModel)
}
}
override fun onDestroy() {
super.onDestroy()
lifecycle.removeObserver(mViewModel)
}
}
providerVMClass()方法中通过BaseViewModel子类泛型类型参数获取Class,在通过 ViewModelProviders.of(this).get(it)实例化ViewModel。
open class BaseViewModel :ViewModel(),LifecycleObserver{
/** 请求异常(服务器请求失败,譬如:服务器连接超时等) */
val exception = MutableLiveData<Exception>()
/** 请求完成 */
val complete=MutableLiveData<Int>()
/**
* 启动协程,封装了viewModelScope.launch
*
* @param tryBlock try语句运行的函数
*
*/
fun launch(tryBlock: suspend CoroutineScope.() -> Unit) {
// 默认是执行在主线程,相当于launch(Dispatchers.Main)
viewModelScope.launch {
try {
tryBlock()
} catch (e: Exception) {
exception.value = e
} finally {
complete.value=0
}
}
}
}
BaseViewModel 里主要做了协程请求数据状态封装。使用LiveData及时通知数据更新。
主要是获取ApiService和网络请求订阅容器,方便管理网络请求。
open class BaseRepository {
suspend fun <T> apiCall(api: suspend () -> ApiResponse<T>): ApiResponse<T> {
return withContext(Dispatchers.IO) { api.invoke() }
}
}
class LoginViewModel :BaseViewModel() {
// 获取 用户名
val userName =ObservableField<String>()
// 获取 密码
val passWord = ObservableField<String>()
// 登录结果liveData
private var loginResultData=MutableLiveData<ApiResponse<User>>()
// 对外提供获取登录结果方法
fun getLoginResult()=loginResultData
fun login(userName:String,pwd:String)=launch{
// 网络请求
val loginResult=ApiManager.login(userName,pwd)
// 传递登录结果值
loginResultData.postValue(loginResult)
}
}
LoginViewModel中持有数据观察容器LiveData和真正发起网络请求动作,在接收到服务端返回的数据通过loginResultData.postValue(loginResult)通知Observer数据的更改,此处需注意的是,setValue方法只能在主线程中调用,postValue可以在任何线程中调用,如果是在后台子线程中更新LiveData的值,必须调用postValue。
class LoginActivity : BaseActivity<LoginViewModel>() {
private lateinit var mBinding: ActivityLoginBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 使用 DataBinding 双向绑定
mBinding=DataBindingUtil.setContentView<ActivityLoginBinding>(this,R.layout.activity_login)
initView()
// DataBinding 关联ViewModel
mBinding.viewModel=mViewModel
}
override fun providerVMClass(): Class<LoginViewModel>? {
return LoginViewModel::class.java
}
private fun initView() {
mBinding.btnLogin.setOnClickListener {
// 发起登录请求
mViewModel.login(mViewModel.userName.get()!!,mViewModel.passWord.get()!!)
}
// 登录结果
mViewModel.getLoginResult().observe(this){
ToastUtil.showShortInCenter(this@LoginActivity, it.errorMsg)
}
}
}
LoginActivity的工作是UI初始化,发请网络请求以及数据观察更新UI。
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.xf.mvvmexample.ui.LoginViewModel" />
data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_marginTop="30dp"
android:layout_marginEnd="15dp"
android:hint="请输入名字"
android:text="@={viewModel.userName}"
android:textSize="15sp" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_marginTop="30dp"
android:layout_marginEnd="15dp"
android:inputType="textPassword"
android:hint="请输入密码"
android:text="@={viewModel.passWord}"
android:textSize="15sp" />
<Button
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="50dp"
android:textColor="@color/white"
android:text="登录"
android:layout_marginEnd="15dp"
android:layout_marginStart="15dp" />
LinearLayout>
layout>
使用DataBinding进行数据和UI双向绑定,及时获取数据。以上就是MVVM架构,实现登录功能。完整代码