一、理论
对于Android项目来说,一个好的架构模式对于后期新的需求的提出、维护、更新代码等各个方面都是十分有利的。
那么对于Android项目来说,有哪些可选的架构模式呢,传统的MVC模式,微软的MVVM模式和MVP模式。
MVC模式:在Android方面,View层与Controller高度耦合了,基本上都是Activity充当了,软件工程的软件设计思想就是得高内聚,低耦合,所以这个Activity几百直至上千行是非常繁琐的,维护起来难度是加大了,虽然说考验个人代码能力,但是软件设计的思想不就是让写代码、维护、更新、添加模块更加地方便嘛,所以这个就相对来说缺点是比较大了。
MVVM模式:MVVM模式最早是微软公司提出,并且了大量使用在.NET的WPF和Sliverlight中。2005年微软工程师John Gossman在自己的博客上首次公布了MVVM模式,对于大型的图形应用程序,视图状态较多,ViewModel的构建和维护的成本都会比较高,所以Android方面选择MVVM考虑得不是很多。
MVP模式:它的核心就是隔离了Model层与View层,Model层与View不直接接触,他们通过Presenter层进行数据交互,Android基本都是靠页面与用户交互,所以这就在一定程度上相对于MVC和MVVM要优越很多,所以这里就剖析一下MVP的一个小Demo。
具体想详细了解MVC、MVVM、MVP三种模式的区别可以看这篇博客(个人觉得还不错):
二、实战
MVP模式的思想如下图:
View层(Activity)界面需要数据,请求数据,到Presenter里面去找,Presenter层本身不生产数据,它只是数据的搬运工,所以它到Model层里面去找,
Model层里面,从数据库拉数据,从网络请求数据,解析数据等等无论哪种方式,得到数据,然后交给Presenter,最后再显示到View层(Activity)界面上。
好,理论太枯燥,看Demo:(Demo地址)
首先是包结构:
包结构看完,然后就是一条主线思路:View -> Presenter -> Model -> Presenter -> View
所以,先看ProfileActivity类与IProfileView,本着面向对象六大原则之一的开闭原则,对扩展开放,对修改关闭,所以用接口来写具体要实现的方法,对于具体的操作在实现类里面实现,所以ProfileActivity类与IProfileView应该是这样的:
public class ProfileActivity extends AppCompatActivity implements IProfileView {
private IProfilePresenter iProfilePresenter;
private CircleImageView mIvAvatar;
private TextView mTvNickname;
private TextView mTvResume;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_profile);
initView();
}
/**
* 初始化视图以及事件
*/
private void initView() {
//把当前对象传进去,以免Activity被销毁
iProfilePresenter = new ProfilePresenterImpl(this);
mIvAvatar = (CircleImageView) findViewById(R.id.ivAvatar);
mTvNickname = (TextView) findViewById(R.id.tvNickname);
mTvResume = (TextView) findViewById(R.id.tvResume);
findViewById(R.id.btnRequest).setOnClickListener((v -> requestData(Contants.getExampleUrl)));
findViewById(R.id.btnReset).setOnClickListener(v -> {
mIvAvatar.setImageResource(R.mipmap.ic_launcher);
mTvNickname.setText("Google第1号员工");
mTvResume.setText("这个人很懒,什么都没有留下");
});
}
@Override
public void requestData(String url) {
iProfilePresenter.requestData(url);
}
@Override
public void displayData(Profile profile) {
Glide.with(this)
.load(R.mipmap.plnus)
.into(mIvAvatar);
mTvNickname.setText(profile.getNickName());
mTvResume.setText(profile.getResume());
}
@Override
protected void onDestroy() {
super.onDestroy();
//解除绑定
((ProfilePresenterImpl) iProfilePresenter).unBind();
}
}
这个Activity的布局activity_profile.xml是这样的:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".test.profile.view.ProfileActivity">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/ivAvatar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.2" />
<TextView
android:id="@+id/tvNickname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="Google第1号员工"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/ivAvatar" />
<TextView
android:id="@+id/tvResume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="这个人很懒,什么都没有留下"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvNickname" />
<Button
android:id="@+id/btnRequest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="request"
android:textAllCaps="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btnReset"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="reset"
android:textAllCaps="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnRequest" />
</android.support.constraint.ConstraintLayout>
一个View界面,也就是一个Activity,在整个Android项目中承担着View的角色,跟用户交互的一个界面,当然Fragment也是可以的,只是这里用Activity来举例子,Fragment的话,换成具体实现IProfileView接口就好了,所以在这里,Activity就是具体的View接口的实现类,IProfileView是怎样的呢,它里面只有一个方法,就是需要实现的具体的业务逻辑方法,代码就是这样:
public interface IProfileView {
/**
* 请求数据
*
* @param url
*/
void requestData(String url);
/**
* 显示数据
*
* @param profile
*/
void displayData(Profile profile);
}
这就是整个View层的东西,从Activity里面可以看出来,直接从requestData来发起请求数据,然后直接用Presenter的接口调用ProfilePresenterImpl里面的方法,也就是IProfilePresenter接口里面的待实现的方法,IProfilePresenter与ProfilePresenterImpl代码如下,先看ProfilePresenterImpl类:
public class ProfilePresenterImpl implements IProfilePresenter, ProfileBizImpl.OnDataChangeListener {
private IProfileView iProfileView;
private IProfileBiz iProfileBiz;
/**
* 创建对象的时候用接口的实现类
* 实例化View层与Model层的接口
*
* @param iProfileView
*/
public ProfilePresenterImpl(IProfileView iProfileView) {
this.iProfileView = iProfileView;
this.iProfileBiz = new ProfileBizImpl(this, this);
}
@Override
public void refresh(Profile profile) {
iProfileView.displayData(profile);
}
@Override
public void requestData(String url) {
iProfileBiz.getRequest(url);
}
/**
* 解除Presenter与View、Model
* 两者之间的绑定
*/
public void unBind() {
iProfileView = null;
iProfileBiz = null;
}
}
然后再是IProfilePresenter接口代码:
public interface IProfilePresenter {
/**
* View层通过Presenter层的该方法
* 到Model层去请求数据
*
* @param url
*/
void requestData(String url);
}
从Presenter层的接口以及其实现类来看,ProfilePresenterImpl类实现了IProfilePresenter接口,并重写了方法,然后直接被View层调用,请求从View层已经传到了Presenter里面,然后现在Presenter层需要做的就是把请求继续往Model层传,现在看Model层里面的具体实现,在刚刚包结构里面可以看到,有一个bean包和一个biz包,bean包,实体类包嘛,放实体类数据的,biz(business)包就是具体的业务逻辑类的包,好,先看biz包的类,也就一个IProfileBiz接口和一个ProfileBizImpl接口实现类,先看ProfileBizImpl接口实现类:
public class ProfileBizImpl implements IProfileBiz {
private IProfilePresenter iProfilePresenter;
private OnDataChangeListener onDataChangeListener;
public ProfileBizImpl(OnDataChangeListener onDataChangeListener, IProfilePresenter iProfilePresenter) {
this.iProfilePresenter = iProfilePresenter;
this.onDataChangeListener = onDataChangeListener;
}
@Override
public void getRequest(String url) {
String response = HttpUtil.getRequest(url);
Profile profile = parseResponseToBean(response);
onDataChangeListener.refresh(profile);
}
/**
* 解析请求下来的json数据
*
* @param response
* @return
*/
private Profile parseResponseToBean(String response) {
//这里执行具体的数据解析操作
//用Gson或者fastjson或者手动解析都可以
//这里为了方便就直接随便返回一个不报错的值就行了
//这里假装返回一个解析好的Profile实体类
return new Profile("https://avatars3.githubusercontent.com/u/16745807?s=460&v=4", "Plnus", "没有梦想跟咸鱼有什么区别");
}
/**
* Presenter层请求的数据回调
*/
public interface OnDataChangeListener {
void refresh(Profile profile);
}
}
然后再是IProfileBiz的接口,里面就业务逻辑空方法:
public interface IProfileBiz {
/**
* 请求数据
*
* @param url
*/
void getRequest(String url);
}
这就是Model层,等等,忘了一个bean包好像,bean包里面就一个实体类,就这个:
public class Profile {
private String avatarUrl;
private String nickName;
private String resume;
public Profile(String avatarUrl, String nickName, String resume) {
this.avatarUrl = avatarUrl;
this.nickName = nickName;
this.resume = resume;
}
public String getAvatarUrl() {
return avatarUrl;
}
public void setAvatarUrl(String avatarUrl) {
this.avatarUrl = avatarUrl;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getResume() {
return resume;
}
public void setResume(String resume) {
this.resume = resume;
}
}
好了,这就是Model层,从Model层来看,从Presenter传过来的请求到了Model里面了,然后就直接Model层具体去执行具体的业务逻辑,比如网络请求数据等等,但是网络请求完的数据怎么返回View层呢,开始的思路是从Model层还会往回传的,所以这里面就给ProfileBizImpl里面定义了一个回调接口OnDataChangeListener,在Presenter层里面实现了,只要数据请求到,立马调用refresh(Profile profile)方法,把Profile实体类塞进去,然后Presenter就接收到了,就在ProfilePresenterImpl重写的refresh(Profile profile)方法里面,这个时候直接调用View层的接口,然后直接把这个Profile实体类数据传出去就好,这里就用到了View层的display(Profile profile)方法,直接在界面进行显示。
三、总结
总结看一下:
View层做了什么,请求数据,显示数据,就两件事,跟界面显示息息相关;
Presenter层做了什么,把View层的请求数据往里传,把Model层的数据往外传;
Model层坐了什么,具体的数据逻辑操作,数据业务操作;
由此可见,这种模式将各个层的任务解耦得非常充分,基本每个类的代码也不多,看起来不累,对于阅读代码是非常有利的,只是接口调用,优点难以理清思路,当把思路理清楚了之后,发现这种模式,用得恰到好处,只要调用完数据,然后把显示数据写完,其他的都不用管,都交给Model层和Presenter来操作,所以对于这种解耦思路,个人觉得是非常好的。
对于Android的MVP模式的理解以及小Demo的认识就到这里了,如有不对之处望大家指出,谢谢。
Demo项目的地址在文中了,如有需要,可自行下载。