最近公司项目重构,主管要求使用耳熟能详的MVP模式,优点是代码复用性高,易于调试维护。觉得很有必要记录一下自己的学习心得。
先看效果图:
很简单的布局与逻辑,点击按钮以后,模拟网络加载数据,两秒过后,将加载的数据显示。(截屏略卡)传统的MVC模式实现很简单,今天尝试用MVP模式怎么去实现。
Model:依然是业务逻辑和实体模型,用于数据的增删改查等,也包括一些数据对象
View:负责View的绘制以及与用户交互,用于界面的显示与用户操作的接收,在Android里面View通常就是Actvitiy,Fragment。
Presenter:负责完成View与Model之间的交互,接收View的请求后,从Model获取数据交给View。
与传统的MVC模式不同,在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会从直接Model中读取数据而不是通过 Controller。
接下来就是具体的应用了:
(1) StringView(接口),用于定义我们当前Activity(View)的行为,Activity会实现这个接口。接口里定义的方法最常见的一种情况就是:请求数据成功,返回result;请求数据失败,返回message
public interface StringView {
void ShowStringSuccess(String result);
void ShowStringFail(String message);
}
(2) OnStringListener(接口),数据返回之后的回调,回调给Presenter层
public interface OnStringListener {
//成功时回调
void OnSuccess(String result);
//失败时回调
void OnFail(String message);
}
(3) StringPresenter(类),定义着Activity(View层)与Presenter层交互的方法,并且处理Model层返回数据
/**
* Created by tangyangkai on 16/4/11.
* Presenter作为中间层,持有View和Model的引用,既对model层的数据进行处理,同事又控制View层的展示
*/
public class StringPresenter implements OnStringListener {
private StringView stringview;
private StringModel stringmodel;
private Context context;
public StringPresenter(Context context,StringView stringview) {
this.stringview = stringview;
this.context=context;
stringmodel = new StringModel();
}
//对view层提供方法,调用model层请求数据
public void SetUrl(String url) {
stringmodel.load(context,url, this);
}
//model层的回调,将返回的数据传递给view层
@Override
public void OnSuccess(String result) {
stringview.ShowStringSuccess(result);
}
@Override
public void OnFail(String message) {
stringview.ShowStringFail(message);
}
}
(4) StringModel(类) 实现我们的数据请求
/**
* Created by tangyangkai on 16/4/11.
* 模拟请求数据,具体实战根据返回结果处理
*/
public class StringModel {
private OnStringListener listener;
private Context context;
public void load(Context context, String url, OnStringListener listener) {
this.listener = listener;
this.context = context;
new MyAsyncTask().execute();
}
// 异步任务
private class MyAsyncTask extends android.os.AsyncTask<Void, Integer, Void> {
@Override
protected void onPreExecute() {
// TODO Auto-generated method stub
super.onPreExecute();
Toast.makeText(context, "正在加载数据,请等待...", Toast.LENGTH_SHORT).show();
}
@Override
protected Void doInBackground(Void... params) {
// TODO Auto-generated method stub
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
// TODO Auto-generated method stub
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(Void result) {
// TODO Auto-generated method stub
super.onPostExecute(result);
// 进行数据加载完成后的UI操作
listener.OnSuccess("模拟数据请求成功");
//listener.OnFail("模拟数据请求失败");
}
}
}
(5)最后就是Activity(View)的代码处理
public class MainActivity extends AppCompatActivity implements StringView {
private TextView txt;
private Button btn;
private StringPresenter stringPresenter;
private Context context;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
//初始化presenter
stringPresenter = new StringPresenter(context,this);
initviews();
}
private void initviews() {
txt = (TextView) findViewById(R.id.txt);
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
stringPresenter.SetUrl("实际应用传入一个URL请求网络数据即可");
}
});
}
@Override
public void ShowStringSuccess(String result) {
txt.setText(result);
Toast.makeText(getApplicationContext(),result,Toast.LENGTH_SHORT).show();
}
@Override
public void ShowStringFail(String message) {
Toast.makeText(getApplicationContext(),message,Toast.LENGTH_SHORT).show();
}
}
至此,使用MVP模式实现了一个简单的业务逻辑。MVP模式层级分级明显,高度可复用,耦合度低,很适合今后代码的维护与更新。
之后主管让我去了解MVC,MVP ,MVVM,Data Binding相关的知识,于是便有了这篇博客的下文。参考资料:
选择恐惧症的福音!教你认清MVC,MVP和MVVM
完全掌握Android Data Binding
这里很感谢这两篇文章的理论与技术支持,感谢这些大神。
OK,开始今天的主题。
关于MVP模式与MVVM模式孰是孰非,我想不是我一个初学者短短几句就能够说清楚的,我们也不用纠结于此。引用大神的话“真正的最佳实践都是人想出来的”。我们为何不结合一下MVP和MVVM的特点:MVP+Data Binding,依旧使用presenter去做和model层的通信,同时使用data binding去轻松的bind data。
Data Binding—-2015年的Google IO 大会上,Android 团队发布的一个 数据绑定框架。以后可以直接在 layout 布局 xml 文件中绑定数据,无需再通过findViewById或者注解框架去设置数据。
具体看看MVP+Data Binding在项目中的应用:
点击button,请求加载网络数据,两秒过后,将模拟的数据显示在textView中。
1.准备工作
新建一个 Project,确保 Android 的 Gradle 插件版本不低于 1.5.0-alpha1:
classpath 'com.android.tools.build:gradle:1.5.0'
然后修改对应模块(Module)的 build.gradle:
dataBinding {
enabled true
}
2.数据对象
这里的数据对象有两种情况,一种是普通的数据对象,一种是绑定的数据对象,能够自动更新数据。
先看第一种:
/**
* Created by tangyangkai on 16/4/27.
*/
public class User{
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
一个简单的User,两个属性以及它的 getter 和 setter。
3.布局文件
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.example.tangyangkai.myapplication.User"></variable>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="getWebMsg"
android:text="模拟请求网络数据" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="姓氏:" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="名字:" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}" />
</LinearLayout>
</layout>
(1.)data节点:使用 Data Binding 之后,xml 的布局文件就不再用于单纯地展示 UI 元素,还需要定义 UI 元素用到的变量。所以,它的根节点不再是一个 ViewGroup,而是变成了 layout,并且新增了一个节点 data。data 节点的作用就像一个桥梁, 把数据(Model)与 UI(View) 进行绑定,搭建了 View 和 Model 之间的通路。
(2.)申明variable:
<data>
<variable
name="user"
type="com.example.tangyangkai.myapplication.User"> </variable>
</data>
在 xml 布局文件的 data 节点中声明一个 variable,这个变量会为 UI 元素提供数据(例如 TextView 的 android:text),然后在 Java 代码中把『后台』获取的数据与这个 variable 进行绑定。其中 type 属性就是我们在 Java 文件中定义的 User 类。
(3.)使用variable:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}" />
数据与 Variable 绑定之后,xml 的 UI 元素就可以直接使用了。
4.Activity实现
public class FiveActivity extends AppCompatActivity implements StringView {
private StringPresenter mStringPresenter;
ActivityFiveBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mStringPresenter = new StringPresenter(this);
binding = DataBindingUtil.setContentView(
this, R.layout.activity_five);
}
//请求网络数据
public void getWebMsg(View view) {
mStringPresenter.SetUrl();
}
@Override
public void ShowString(String firstName, String lastName) {
User user = new User();
user.setFirstName(firstName);
user.setLastName(lastName);
binding.setUser(user);
}
}
(1.)ActivityFiveBinding是框架自动生成的, 其中的set 方法也是根据 variable 名称而生成的。
(2.)修改 FiveActivity 的 onCreate 方法,用 DatabindingUtil.setContentView() 来替换掉 setContentView()
(3.)创建一个 User 对象,将从网络获取的数据通过set方法传递给User,通过 binding.setUser(user) 与 variable 进行绑定。
至此,使用第一种数据对象的data binding流程已经过了一遍,其实核心就是申明variable,绑定variable,使用variable。第二种数据对象也一样,只不过是绑定variable时候有些不一样。
5.第二种数据类型
/**
* Created by tangyangkai on 16/4/27.
*/
public class User extends BaseObservable{
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
@Bindable
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}
(1.)要实现 数据对象的绑定,Android 原生提供了已经封装好的一个类 - -BaseObservable,并且实现了监听器的注册机制,我们只需要继承 BaseObservable。
(2.)BR 是编译阶段生成的一个类,功能与 R.java 类似,用 @Bindable 标记过 getter 方法会在 BR 中自动生成一个 entry。
(3.)当数据发生变化时会调用 notifyPropertyChanged(BR.firstName) 方法,通知系统 BR.firstName 这个 entry 的数据已经发生变化,再去更新 UI。
public class FiveActivity extends AppCompatActivity implements StringView {
private StringPresenter mStringPresenter;
User user = new User();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mStringPresenter = new StringPresenter(this);
ActivityFiveBinding binding = DataBindingUtil.setContentView(
this, R.layout.activity_five);
binding.setUser(user);
}
//请求网络数据
public void getWebMsg(View view) {
mStringPresenter.SetUrl();
}
@Override
public void ShowString(String firstName, String lastName) {
user.setFirstName(firstName);
user.setLastName(lastName);
}
}
与第一种数据对象不同的是,variable的数据绑定会在初始化的时候就完成。获取网络数据成功以后,只要直接传递给user即可,数据以及UI的更新会在内部进行。
6.MVP的代码
Presenter层
/**
* Created by tangyangkai on 16/4/11.
* Presenter作为中间层,持有View和Model的引用,对model层的数据进行处理,控制View层的展示
*/
public class StringPresenter implements StringModel.getMsgListener {
private StringView stringview;
private StringModel stringmodel;
public StringPresenter(StringView stringview) {
this.stringview = stringview;
stringmodel = new StringModel();
}
//对view层提供请求数据方法
public void SetUrl() {
stringmodel.getWebMsg(this);
}
//将model层返回的数据传递给view
@Override
public void getMsgSuccess(String firstName, String lastName) {
stringview.ShowString(firstName, lastName);
}
}
Model层
/**
* Created by tangyangkai on 16/4/11.
* 模拟请求数据,具体实战根据返回结果处理
*/
public class StringModel {
public interface getMsgListener {
void getMsgSuccess(String firstName, String lastName);
}
public void getWebMsg(getMsgListener listener) {
//模拟网络数据请求
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
listener.getMsgSuccess("tang", "yangkai");
}
}
关于Data Binding的高级用法以及注意事项,那两篇文章介绍的很详细,大家可以细细品尝。
关于这些模式与框架,仁者见仁,智者见智,其实都是为了尽量降低程序的耦合性和提高代码的复用性。以上是记录自己最近学习这些框架模式的一些心得,有不当之处欢迎大家指出,一起进步。