大家好!
作为 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 层,可实际上作用还是很大的。另外,由于 MVP 是基于接口编程,所以会多了很多接口文件,来定义业务约束,虽然看上去需要新增很多文件/函数,或者理解为额外工作量,但其实这也很好地锻炼架构思维和抽象能力,因为你可以完全放下业务实现来搭建整体框架。
代码实现
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 架构。
try a demo
点击按钮,请求 wanandroid 网站的 banner 接口数据,请求成功后更新到UI上显示接口数据
MVP 架构的 Demo 是从 MVC 架构那套代码变更过来的,添加了很多文件,主要部分 涉及上图中展开的这几个文件,仔细看上图蓝框中的内容会发现,新增一个业务 Activity,共需要新增 6 个文件,其中 3 个是接口约束类、3 个是具体实现类,这也体现了上面说的文件/函数暴增的问题。
Model
请求接口,获取 banner 数据
本地持久化缓存 banner 数据
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
通过 model 获取数据
通知 UI 更新
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);
}
}
}
}
效果展示
参考:
一个小例子彻底搞懂 MVP