Android框架模式主要有MVC、MVP和MVVM,根据业务选择合适的框架。
(一)MVC(模型-视图-控制器)
业务、数据、界面分离的方法组织代码,在改进和个性化定制界面和用户交互时,无须重新编写业务逻辑。
模型层:针对业务模型, 建立数据结构和相关的类,可以理解为Model,Model与View无关,而与业务相关;
视图层:界面显示结果。一般以XML文件或者Java代码进行界面的描述;JS+HTML也可以作为View层;
控制器:桥梁效果。控制层通常位于Activity、Fragment或者由他们控制的其他业务类中。用来View层和Model层的通信。
MVC简单来说:使用Contoller来操作Model层的数据,并返回给View层显示。
我们往往把Android中界面部分的实现也理解为采用了MVC框架,常常把Activity理解为MVC模式中的Controller。
优点:
1、把业务逻辑全部分离到Controller中,模块化程度高。当业务逻辑变更的时候,不需要变更View和Model,只需要替换Controller,换成另外一个 Controller就行了(Swappable Controller)。
2、观察者模式可以做到多视图同时更新。
缺点:
1.随着界面及其逻辑的复杂度不断提升,Activity类的职责不断增加,导致它特别庞大冗余。
2.View层和Model层相互耦合,不宜开发和维护。即:如果需要把这个View抽出来作为一个另外一个应用程序可复用的组件就困难了,因为不同程序的的Domain Model是不一样的。
(二)MVP模式
MVP模式将MVC中的Controller改为了Presenter,View通过接口与Presenter进行交互,降低耦合,方便进行单元测试。
View:负责处理点击事件,绘制UI元素(Activity、View、Fragment都可以做为View层);
Model:对数据存取操作、对网络的操作,通过Model来获取、存储数据;
Presenter:作为View与Model交互的中间纽带,它从Model层检索数据后返回给View层,使得View层和Model之间没有耦合。
MVP的特点:(1)实现View和Model的分离,决不允许View直接访问Model。(2)Presenter与具体View没有直接关联,通过定义好的接口进行交互。保证变更View时候保持Presenter不变,View中只有简单的get、set方法。
优点:
1、便于测试。Presenter对View是通过接口进行,在对Presenter进行不依赖UI环境的单元测试的时候。可以通过Mock一个View对象,这个对象只需要实现了View的接口即可。然后依赖注入到Presenter中,单元测试的时候就可以完整的测试Presenter业务逻辑的正确性。
2、View可以进行组件化。在MVP当中,View不依赖Model。这样就可以让View从特定的业务场景中脱离出来,可以说View可以做到对业务逻辑完全无知。它只需要提供一系列接口提供给上层操作。这样就可以做高度可复用的View组件。
缺点:
Presenter中除了业务逻辑以外,还有大量的View->Model,Model->View的手动同步逻辑,造成Presenter比较笨重,维护起来会比较困难。
MVP结合RxJava和Dagger2效果较好。
以下代码参考帖子:https://www.jianshu.com/p/479aca31d993
代码示例:
==================================View=======================================
所有的view(Activity、FragmentActivity、Fragment...)都必须实现这个接口
public interface IView {
// 此方法是为了当Presenter中需要获取上下文对象时,传递上下文对象,而不是让Presenter直接持有上下 文对象
Activity getSelfActivity();
}
这是Activity的基类:
public abstract class BaseActivity<P extends IPresenter> extends Activity implements IView {
// Presenter对象
protected P MvpPre;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MvpPre = bindPresenter();
}
// 绑定Presenter
protected abstract P bindPresenter();
public <T> T $(int resId) {
return (T) findViewById(resId);
}
public <T> T $(int resId, View parent) {
return (T) parent.findViewById(resId);
}
@Override
public Activity getSelfActivity() {
return this;
}
@Override
protected void onDestroy() {
super.onDestroy();
/**
* 在生命周期结束时,将presenter与view之间的联系断开,防止出现内存泄露
*/
if (MvpPre != null) {
MvpPre.detachView();
}
}
}
==================================Presenter=======================================
public interface IPresenter {
void detachView();
}
Presenter的基类:
public abstract class BasePresenter<V extends IView> implements IPresenter {
// 此处使用弱引用是因为,有时Activity关闭不一定会走onDestroy,所以这时使用弱引用可以及时回收IView
protected Reference<V> MvpRef;
public BasePresenter(V view) {
attachView(view);
}
private void attachView(V view) {
MvpRef = new WeakReference<V>(view);
}
protected V getView() {
if (MvpRef != null) {
return MvpRef.get();
}
return null;
}
/**
* 主要用于判断IView的生命周期是否结束,防止出现内存泄露状况
*
* @return
*/
protected boolean isViewAttach() {
return MvpRef != null && MvpRef.get() != null;
}
/**
* Activity生命周期结束时,Presenter也清除IView对象,不在持有
*/
@Override
public void detachView() {
if (MvpRef != null) {
MvpRef.clear();
MvpRef = null;
}
}
}
===================================demo========================================
接口:
/**
* 创建一个类作为纽带,将view、presenter、model的接口方法都串联在一起,更加便于管理
*/
public final class MainContacts {
public interface IMain extends IView {
void showTips(boolean isSucceess);
}
public interface IMainPre extends IPresenter {
void login(String username, String password);
}
public interface IMainLgc {
boolean login(String username, String password);
}
}
Model部分:
public class MainLogic implements MainContacts.IMainLgc {
public boolean login(String username, String password) {
if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {
return false;
}
return true;
}
}
View部分:
public class MainActivity extends BaseActivity<MainPresnter> implements MainContacts.IMain {
private EditText editT_username, editT_password;
private Button btn_login;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initUI();
addListeners();
}
@Override
protected MainPresnter bindPresenter() {
return new MainPresnter(this);
}
private void initUI() {
editT_username = $(R.id.editT_username);
editT_password = $(R.id.editT_password);
btn_login = $(R.id.btn_login);
}
private void addListeners() {
btn_login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MvpPre.login(editT_username.getText().toString(), editT_password.getText().toString());
}
});
}
@Override
public void showTips(boolean isSucceess) {
if (isSucceess) {
Toast.makeText(this, "登录成功!", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "登录失败!", Toast.LENGTH_SHORT).show();
}
}
}
Presenter部分:
public class MainPresnter extends BasePresenter<MainContacts.IMain> implements MainContacts.IMainPre {
private MainLogic mMainLogic;
public MainPresnter(MainContacts.IMain view) {
super(view);
this.mMainLogic = new MainLogic();
}
@Override
public void login(String username, String password) {
// 判断activity的生命周期是否结束,不判断的话在极端情况下可能会出现内存泄露
if (isViewAttach()) {
MvpRef.get().showTips(mMainLogic.login(username, password));
}
}
}
(三)MVVM
MVVM模式(Model--View--ViewModel模式),和MVP模式相比,MVVM 模式用ViewModel替换了Presenter,其他层基本上与 MVP 模式一致,ViewModel可以理解成是View的数据模型和Presenter的合体。
MVVM是的ViewModel与Mode和View采用双向绑定技术(data-binding):View的变动,自动反映在ViewModel,ViewModel通知Model来改变数据,反之亦然。这种模式实际上是框架替应用开发者做了一些工作(相当于ViewModel类是由库帮我们生成的),开发者只需要较少的代码就能实现比较复杂的交互。
MVVM的调用关系
MVVM的调用关系和MVP一样。但是,在ViewModel当中会有一个叫Binder,或者是Data-binding engine的东西。以前全部由Presenter负责的View和Model之间数据同步操作交由给Binder处理。你只需要在View的模版语法当中,指令式地声明View上的显示的内容是和Model的哪一块数据绑定的。当ViewModel对进行Model更新的时候,Binder会自动把数据更新到View上去,当用户对View进行操作(例如表单输入),Binder也会自动把数据更新到Model上去。这种方式称为:Two-way data-binding,双向数据绑定。可以简单而不恰当地理解为一个模版引擎,但是会根据数据变更实时渲染。MVVM把View和Model的同步逻辑自动化了。
优点:
1、提高可维护性。解决了MVP大量的手动View和Model同步的问题,提供双向绑定机制。提高了代码的可维护性。
2、简化测试。因为同步逻辑是交由Binder做的,View跟着Model同时变更,所以只需要保证Model的正确性,View就正确。大大减少了对View同步更新的测试。
缺点:
1、过于简单的图形界面不适用,或说牛刀杀鸡。
2、对于大型的图形应用程序,视图状态较多,ViewModel的构建和维护的成本都会比较高。
3、数据绑定的声明是指令式地写在View的模版当中的,这些内容是没办法去打断点debug的。
参考自:https://mp.weixin.qq.com/s/JMjERFMA-ZetM7nU-0378Q
View
View层,不再是我们之前理解的是一个TextView、LinearLayout等View控件,只要是可以和用户进行交互的都可以归属到View层,比如:Activity、Fragment、Dialog、PopupWindow、XML布局、布局Adapter、系统及自定义View控件等等,只要是用户看到的、摸到的都归View层管。也就是说禁止在这里做业务逻辑、数据操作等和View无直接相关的事情。
Model
model层,与我们之前把定义的实体bean对象称为model不同的是,这里的model被赋予了数据管理的职责。数据的管理包括数据存储与数据获取,这里存储的位置不仅限于本地(SharedPreferences、SQLite),而且也会是网络上的任何存储方式。
ViewModel
ViewModel层,说是只处理业务逻辑,更准确的说法是Model层和View层的粘合剂,从Model中获取数据整合之后提供给View层进行显示,响应View层的事件调用Model层进行响应的落地。
MVVM的步骤:
https://www.jianshu.com/p/545af5bbb93f
https://blog.csdn.net/xuehuayous/article/details/82777022
步骤一:gradle里面启用dataBinding,我们使用mvvm,必然会用到android为我们提供的dataBinding支持包。
dataBinding{
enabled true
}
步骤二:修改View布局,引入layout进行外包,引入Data,import变量引入Model的包名,type是变量类型,name是是变量名称。Class是自定义类名的方法。android:text="@{user.phoneNumber}"引入双向绑定时改变的域。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
>
<!--如果想使用databinding框架,需要按照mvvm的规矩来办,而mvvm的规矩就是,需要布局文件
按照 「固定的写法」 来编写。-->
<!--最外层用<layout>标签嵌套,注意layout的首字母是小写的“l”-->
<!--* <layout>标签的下面紧跟着一个<data>标签,这个标签其实就是让我们进行数据绑定的一个标签-->
<!--* <data>标签中,包含着<variable>标签,这个标签就是我们将“变量”放置的位置-->
<!--* <variable>标签里面分别有<type> <name>两个标签,分别来标识变量类型和变量名称-->
<!--* <type>标签 标识变量类型,比如java.lang.String这就是String类型,com.guaju.mvvm.bean.User 这个就是一个我自定义的一个User类型-->
<!--* <name>标签 表示的就是我们定义的一个变量名称,这个变量名称我们会在下方的布局和对应的java代码中引用到-->
<data class = "Testing">
<import type="com.example.hzk.myapplication.User"/>
<variable
name="user"
type="User"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="19sp"
android:text="@{user.name}"
android:textColor="#000"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24sp"
android:text="@{user.phoneNumber}"
android:textColor="#666"/>
</LinearLayout>
</layout>
步骤三:修改Model域。记得Rebuild一下。
/**
* 充当Model的作用:我们就提供一个User类,存储人物和电话。
*/
public class User {
public String name;
public String phoneNumber;
public User(String name, String phoneNumber) {
this.name = name;
this.phoneNumber = phoneNumber;
}
public String getName() {
return name;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setName(String name) {
this.name = name;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
步骤四:ModelView域,通过DataBindingUtil来返回。在java代码中直接设置数据,改变布局中的显示。
/**
* 编写字符串,保存到本地,再读取出来修改下并保存。
*/
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//双向绑定机制,Testing为布局里面的Class,记得rebuild生成下。
Testing test = DataBindingUtil.setContentView(this,R.layout.activity_main);
// 在java代码中直接设置数据,改变布局中的显示
User xiaoming = new User("小明","110");
test.setUser(xiaoming);
}
}