Android 架构模式之 MVP

  1. Android 架构模式之 MVC
  2. Android 架构模式之 MVP
  3. Android 架构模式之 MVVM

大家好!

作为 Android 程序猿,你有研究过 MVP 架构吗?在开始接触 Android 那一刻起,我们就开始接触 MVC 架构,可谓是用的不亦乐乎。可为什么又出现了 MVP 呢?都说它比 MVC 好,到底又好在哪里呢?

架构设计的目的

通过设计使程序模块化,模块内 高内聚、模块间 低耦合,提高开发效率,便于复用及后续维护。

对 MVP 的理解

MVP 架构图,箭头代表事件流向

上图是 MVP 的架构图,我们都知道,MVP架构中 M 代表 Model(模型)、V 代表 View(视图)、P 代表 Presenter(主持人/控制器)。它们的职责分别是:

  1. View 负责接收用户的输入事件,然后将事件传递给 Presenter;
  2. Presenter 收到事件后,会进行业务分发,通知 Model 获取数据;
  3. Model 区分数据来源,进而通过不同渠道获取数据,拿到数据后返回给 Presenter;
  4. 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);
            }
        }
    }
}

效果展示

效果展示

附上源码链接

致谢:
感谢 wanandroid 提供的开放API

参考:
一个小例子彻底搞懂 MVP

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值