目录
- 架构设计的目的
- 对 MVP 的理解
- 代码
- Model
- View
- Presenter
- Android 中 MVP 的问题
- 试吃个小李子
- Model
- View
- Presenter
大家好!
作为 Android 程序猿,你有研究过 MVP 架构吗?在开始接触 Android 那一刻起,我们就开始接触 MVC 架构,可谓是用的不亦乐乎。可为什么又出现了 MVP 呢?都说它比 MVC 好,到底又好在哪里呢?
架构设计的目的
通过设计使程序模块化,模块内 高内聚、模块间 低耦合,提高开发效率,便于复用及后续维护。
对 MVP 的理解
上图是 MVP 的架构图,我们都知道,MVP架构中 M 代表 Model(模型)、V 代表 View(视图)、P 代表 Presenter(主持人/控制器)。它们的职责分别是:
- View 负责接收用户的输入事件,然后将事件传递给 Presenter;
- Presenter 收到事件后,会进行业务处理,通知 Model 获取数据;
- Model 根据获取数据方式通过不同渠道去取数据,拿到数据后返回给 Presenter;
- Presenter 进行后续处理,或者通知 View 更新 UI。
相比 MVC 架构,MVP 架构看上去就清晰了很多:事件由 View 流向 Presenter 流向 Model,然后再由 Model 流回 Presenter 流回 View。在 MVP 架构中,Activity 就全心的处理着和 View 相关的事情,Model 负责向下分发数据请求,替 Presenter 分担了很大一部分负担,这里特意新增了一个 CacheRepository 来体现提取 Model 层的用意,这样就可以在 Model 层进行不同渠道的分发,既体现了单一职责原则,又很好的提高了代码的可读性。所以看上去是多了一层 Model 层,可实际上作用还是很大的。
代码
Model
IModel.java
public interface IModel {
}
BaseModel.java
public abstract class BaseModel implements IModel {
}
View
public interface IView {
void showErr(String errMsg);
}
BaseActivity.java
public abstract class BaseActivity<P extends IPresenter> extends AppCompatActivity
implements IView {
protected P mPresenter;
public BaseActivity() {
this.mPresenter = createPresenter();
mPresenter.attachView(this);
}
public abstract P createPresenter();
@Override
protected void onDestroy() {
super.onDestroy();
// Activity 销毁时,需要调用 detachView,防止内存泄漏
if (mPresenter != null) {
mPresenter.detachView();
mPresenter.onDestroy();
mPresenter = null;
}
}
@Override
public void showErr(String errMsg) {
Toast.makeText(this, errMsg, Toast.LENGTH_SHORT);
}
}
Presenter
IPresenter.java
public interface IPresenter<V extends IView> {
void attachView(V view);
void detachView();
void onDestroy();
}
BasePresenter.java
public abstract class BasePresenter<V extends IView, M extends IModel> implements IPresenter<V> {
protected WeakReference<V> mView;
protected M mModel;
public BasePresenter() {
this.mModel = createModel();
}
protected abstract M createModel();
@Override
public void attachView(V view) {
mView = new WeakReference<>(view);
}
@Override
public void detachView() {
if (mView.get() != null) {
mView.clear();
}
}
@Override
public void onDestroy() {
if (mModel != null) {
mModel = null;
}
}
}
上述代码中可以看到,Presenter 中持有 View 引用,想象一种情况,Activity 发起一个网络请求/耗时操作,Presenter 收到需求后就分发去处理需求并等待结果了,但是还没等处理结束,Activity 就执行关闭操作了,此时 Presenter 还持有着 Activity 的强引用,导致 Activity 无法被及时回收掉,这便导致了大名鼎鼎的内存泄漏了;上述代码中通过 WeakReference 持有 View 引用,这样可以有效解决内存泄漏问题,并且在涉及到 Model/View/Presenter 的引用调用的地方,都进行了非空判断,需要规避空指针的风险出现。
Android 中 MVP 的问题
不幸的是,MVP 中 Presenter 的职责就是 MVC 中 Controller 负责的内容,只不过在 Android 中 Controller 在全职作 控制器 的同时,还需要兼职一部分 View 的职责,而 Presenter 就只是全职作 控制器。所以 Presenter 同样存在 Controller 存在的问题,随着业务的增多,Presenter 会变得越拉越 臃肿/复杂,以及 很糟糕的代码可读性。
另外,由于 MVP 模式依赖于接口,所以在新增一个业务需求时,会 爆炸式增长文件和函数,这个也很让人头疼。
其次,由于 View 会只有 Presenter 引用,Presenter 持有 View 和 Model 的引用,如果处理不当,也会存在 空指针 的风险。
最后,Presenter 会持有 View 的引用,这样就埋下了 内存泄漏 的种子,如果处理不好,问题还是蛮大的。所以就有了后来的MVVM。
试吃个小李子
点击按钮,请求 wanandroid 网站的 banner 接口数据,请求成功后更新到UI上显示接口数据
MVP 架构的 Demo 是从 MVC 架构那套代码变更过来的,添加了很多文件,主要部分 涉及上图中展开的这几个文件,仔细看上图蓝框中的内容会发现,新增一个业务 Activity,共需要新增 6 个文件,其中 3 个是接口约束类、3 个是具体实现类,这也体现了上面说的文件/函数暴增的问题。
Model
请求接口
缓存数据
IMainModel.java
public interface IMainModel extends IModel {
/**
* 请求 banner 数据
*
* @param callback
*/
void getNetworkBanner(ResponseCallback<List<Banner>> callback);
/**
* 读取 banner 本地数据
*
* @return
*/
List<Banner> getLocalBanner();
/**
* 持久化存储 banner 数据
*
* @param banners
*/
void saveBanner(List<Banner> banners);
/**
* 清空本地数据
*/
void clearLocalData();
}
MainModel.java
public class MainModel extends BaseModel implements IMainModel {
@Override
public void getNetworkBanner(ResponseCallback<List<Banner>> callback) {
// 收到需求,请求接口数据
NetworkRepository.getInstance().requestBanners(callback);
}
@Override
public List<Banner> getLocalBanner() {
// 收到需求,读取本地数据
return CacheRepository.getInstance().getBanners();
}
@Override
public void saveBanner(List<Banner> banners) {
// 收到需求,持久化存储 banner 数据
CacheRepository.getInstance().saveBanners(banners);
}
@Override
public void clearLocalData() {
// 收到需求,清空本地缓存数据
CacheRepository.getInstance().clearLocalData();
}
}
View
Button1,点击请求接口数据
Button2,获取本读缓存数据
Button3,清空本地缓存数据
TextView,用于回显数据
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:gravity="center"
android:orientation="vertical"
tools:context=".main.MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="getNetworkInfo"
android:text="@string/get_network_info" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="getLocalInfo"
android:text="@string/get_local_info" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="clearLocalInfo"
android:text="@string/clear_local_info" />
<TextView
android:id="@+id/tv_banner_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
IMainView.java
public interface IMainView extends IView {
/**
* 更新 banner 数据
*
* @param banners
*/
void updateBanner(List<Banner> banners, String from);
}
MainActivity.java
public class MainActivity extends BaseActivity<IMainPresenter> implements IMainView {
private TextView mBannerInfoTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBannerInfoTv = (TextView) findViewById(R.id.tv_banner_info);
}
@Override
public IMainPresenter createPresenter() {
return new MainPresenter();
}
/**
* 按钮点击事件
*
* @param view
*/
public void getNetworkInfo(View view) {
// 收到点击事件,交给 presenter 进行业务处理
if (mPresenter != null) {
mPresenter.getNetworkBanner();
}
}
/**
* 按钮点击事件
*
* @param view
*/
public void getLocalInfo(View view) {
// 收到点击事件,交给 presenter 进行业务处理
if (mPresenter != null) {
mPresenter.getLocalBanner();
}
}
/**
* 按钮点击事件
*
* @param view
*/
public void clearLocalInfo(View view) {
// 收到点击事件,交给 presenter 进行业务处理
if (mPresenter != null) {
mPresenter.clearLocalData();
}
}
@Override
public void updateBanner(List<Banner> banners, String from) {
// 收到更新 ui 事件,更新 ui
showBannerInfo(banners, from);
}
/**
* 更新UI
*
* @param banners
*/
private void showBannerInfo(List<Banner> banners, String from) {
StringBuilder sb = new StringBuilder();
if (banners.size() > 0) {
sb.append("wanandroid 官网\nhttps://www.wanandroid.com\n\n")
.append("data from ").append(from).append(":\n");
for (Banner item : banners) {
Log.e("banner", item.toString());
sb.append(item.getTitle()).append('\n');
}
}
mBannerInfoTv.setText(sb.toString());
}
}
Presenter
业务处理
IMainPresenter.java
public interface IMainPresenter<V extends IView> extends IPresenter<V> {
/**
* 获取 banner 网络数据
*/
void getNetworkBanner();
/**
* 获取 banner 本地数据
*/
void getLocalBanner();
/**
* 存储 banner 数据
*
* @param banners
*/
void saveBanner(List<Banner> banners);
/**
* 清空本地数据
*/
void clearLocalData();
}
MainPresenter.java
public class MainPresenter extends BasePresenter<IMainView, IMainModel>
implements IMainPresenter<IMainView> {
@Override
protected IMainModel createModel() {
return new MainModel();
}
@Override
public void getNetworkBanner() {
if (mModel == null) {
return;
}
// 收到新需求,分发给 model 处理
mModel.getNetworkBanner(new ResponseCallback<List<Banner>>() {
@Override
public void onSuccess(List<Banner> banners) {
// 数据缓存
saveBanner(banners);
// 通知更新UI
notifyUpdateBanner(banners, CommonConstant.FROM_NETWORK);
}
@Override
public void onFail(String msg) {
IMainView view = mView.get();
if (view != null) {
view.showErr(msg);
}
}
});
}
@Override
public void getLocalBanner() {
if (mModel == null) {
return;
}
// 收到新需求,分发给 model 处理
List<Banner> banners = mModel.getLocalBanner();
// 通知更新UI
notifyUpdateBanner(banners, CommonConstant.FROM_LOCAL);
}
@Override
public void saveBanner(List<Banner> banners) {
// 收到新需求,分发给 model 处理
if (mModel != null) {
mModel.saveBanner(banners);
}
}
@Override
public void clearLocalData() {
// 收到新需求,分发给 model 处理
if (mModel != null) {
mModel.clearLocalData();
}
}
/**
* 通知更新UI
*
* @param banners
*/
private void notifyUpdateBanner(List<Banner> banners, String from) {
// 获取到数据,通知更新 ui
if (mView != null) {
IMainView view = mView.get();
if (view != null) {
view.updateBanner(banners, from);
}
}
}
}
附上源码链接
致谢:
感谢 wanandroid 提供的开放API
参考:
一个小例子彻底搞懂 MVP
写在最后:
很荣幸成为一名 Android 程序猿,虽然不是一名合格的猿。一路走来磕磕绊绊,借此感谢帮助过我的人,感谢指点、感恩遇见!