最近通过《Android 源码设计模式解析与实战》对 MVP 应用架构进行了了解, 摘其重点记录于此.
MVP 简介
MVP 模式全称 Model View Presenter. 能: 1. 有效地降低 View 复杂性, 避免业务逻辑被塞进 View 中; 2. 解除 View 与 Model 的耦合, 保证了系统的整洁性, 灵活性.
理想化的 MVP 模式可以实现同一份逻辑代码搭配不同的显示页面, 因为它们之间并不依赖于具体(class), 而是依赖于抽象(interface). MVP 并不是一个标准化的模式, 我们可根据自己的需求和自己认为对的方式去修正 MVP 的实现方式, 它可以随着 Presenter 的复杂程度变化. 只要我们是通过 Presenter 将 View 和 Model 解耦合, 降低类型复杂度, 各个模块可以独立测试, 独立变化, 这就是正确的方向.
MVP 模式的三个角色
Presenter 层: 主要作为 View 和 Model 的桥梁, 它从 Model 层检索数据后, 返回给 View 层, 使得 View 和 Model 之间没有耦合, 也将业务逻辑从 View 角色上抽离出来.
View 层: 用户界面, 通常是指 Activity,Fragment 或者某个 View 控件, 它含有一个 Presenter 成员变量. 负责页面渲染与事件分发(即调用 Presenter 方法处理事件).
Model 层: Model 角色主要是提供数据的存取功能. Presenter 需要通过 Model 层存储, 获取数据, Model 就像一个数据仓库. 更直白地说, Model 是封装了数据库 DAO 或者网络获取数据的角色, 或者两种数据获取方式的集合.
MVP 模式的应用
下面通过一个简单的客户端示例来直观地体会 MVP 在开发中的运用. 我们的 UI 原型大致如下图所示: 主界面就是一个 ListView 和进度条, 在加载数据时显示进度条, 加载完成之后隐藏.
进入应用后首先会从服务器下载最新的 20 篇文章, 然后将每一篇文章的简介显示到列表上. 因此, 我们的业务逻辑大概有下列 2 个:
向服务器请求数据, 并且在数据存储到数据库中;
从数据库中加载文章列表.
关键类如下:
本地仓库 --LinkedList: 负责提供 (输出) 本地数据, 同时负责将远程数据缓存至本地, 被仓库层持有;
远程仓库接口 --RemoteRepository: 如 Retrofit 对应的 interface 文件, 被仓库层持有;
远程仓库实现 --RemoteRepositoryImpl : 负责 Http 请求的发起及响应的接收, 被仓库层实例化;
仓库层接口 --ArticleModel: 定义了仓库层需要实现的功能, 亦即 Presenter 可调用的方法, 被 Presenter 持有;
仓库层实现 --ArticleModelImpl : 即 Model 层, 内部持有本地仓库和远程仓库的引用, 负责为 Presenter 提供数据, 被 Presenter 实例化;
Presenter 层 --ArticlePresenter : 被 View 层实现持有, 同时持有仓库层接口 (ArticleModel) 及 View 层接口(ArticleViewInterface), 通过调用仓库层及 View 层的方法实现业务逻辑;
View 层接口 --ArticleViewInterface: 定义视图层需要实现的功能, 被 Presenter 持有;
View 层实现 --MvpHomeActivity: 实现 View 层需要具备的功能(方法), 按需调用 Presenter 的业务逻辑;
- /**
- * 数据加载监听
- */
- public interface DataListener<T> {
- // 泛型是数据集合的类型
- public void onComplete(T articles);
- }
- /**
- * 远程数据获取接口(remote 仓库接口)
- */
- public interface RemoteRepository{
- /**
- * 远程抓取数据
- */
- public void fetchArticles(DataListener<List<String>> listener);
- }
- /**
- * 远程数据获取实现(remote 仓库实现)
- */
- public class RemoteRepositoryImpl implements RemoteRepository{
- @Override
- public void fetchArticles(DataListener<List<String>> listener) {
- List<String> articles = new ArrayList<>();
- for (int i = 0; i <20; i++) {
- articles.add("文章" + i);
- }
- listener.onComplete(articles);
- }
- }
- /**
- * 数据层抽象(Model 层接口, 仓库层接口)
- */
- public interface ArticleModel {
- /**
- * 调用远程接口获取文章列表
- */
- public void loadArticlesFromRemote(DataListener<List<String>> listener);
- /**
- * 缓存网络数据至本地
- */
- public void saveArticles(List<String> articles);
- /**
- * 从缓存中加载数据
- *@param listener 该参数由 ModelImpl 调用, 由 Presenter 负责实现, 用于解耦.
- */
- public void loadArticlesFromCache(DataListener<List<String>> listener);
- }
- /**
- * 数据层实现(Model 层实现, 仓库层实现)
- */
- public class ArticleModelImpl implements ArticleModel {
- /**
- * 持有 local 仓库的引用
- */
- List<String> mCacheArticles = new LinkedList<String>();
- /**
- * 持有 remote 仓库的引用
- */
- RemoteRepository remote= new RemoteRepositorympl();
- @Override
- public void loadArticlesFromRemote(DataListener<List<String>> listener) {
- remote.fetchArticles(listener);
- }
- @Override
- public void saveArticles(List<String> articles) {
- // 为了代码简单, 我们这里模拟数据库的存取操作, 将数据缓存到内存中
- mCacheArticles.addAll(articles);
- }
- @Override
- public void loadArticlesFromCache(DataListener<List<String>> listener) {
- listener.onComplete(mCacheArticles);
- }
- }
- /**
- *Presenter 层
- */
- public class ArticlePresenter {
- //Presenter 层持有 View 层的接口
- ArticleViewInterface mArticleView;
- //Presenter 层持有 Model 层的接口
- ArticleModel mArticleModel;
- // 构造器, 给持有的 View 层接口赋以具体的实现, 进而可以调用 View 层的方法
- // 同时实例化成员变量
- public ArticlePresenter(ArticleViewInterface viewInterface) {
- mArticleView = viewInterface;
- mArticleModel = new ArticleModelImpl();
- }
- /**
- * 业务逻辑 1: 获取远程数据
- */
- public void fetchArticles() {
- mArticleView.showLoading();
- mArticleModel .loadArticlesFromRemote(new DataListener<List<String>>() {
- @Override
- public void onComplete(List<String> result) {
- // 数据加载完, 调用 View 的 showArticles 函数将数据传递为 View 显示
- mArticleView.showAricles(result);
- mArticleView.hideLoading();
- // 储存到数据库
- mArticleModel.saveArticles(result);
- }
- });
- }
- /**
- * 业务逻辑 2: 获取缓存数据:
- */
- public void loadArticlesFromDB(){
- mArticleModel.loadArticlesFromCache(new DataListener<List<String>>() {
- @Override
- public void onComplete(List<String> result) {
- mArticleView.showAricles(result);
- }
- });
- }
- }
- /**
- *View 层接口
- */
- public interface ArticleViewInterface {
- /**
- * 展示数据
- */
- public void showAricles(List<String> articles);
- /**
- * 显示进度条
- */
- public void showLoading();
- /**
- * 隐藏进度条
- */
- public void hideLoading();
- }
- /**
- *View 层实现
- */
- public class MvpHomeActivity extends Activity implements ArticleViewInterface {
- private ListView lv;
- private ProgressBar pb;
- private List<String> mArticles = new LinkedList<String>();
- private BaseAdapter mAdapter;
- //View 层实现持有 Presenter 的引用
- private ArticlePresenter mPresenter;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_mvp_home);
- // 初始化各种控件
- initViews();
- // 构建 ArticlePresenter, 与 Activity 关联
- mPresenter = new ArticlePresenter(this);
- boolean netAvailable = true;
- if (netAvailable) {
- mPresenter.fetchArticles();
- }else{
- mPresenter.loadArticlesFromDB();
- }
- }
- private void initViews() {
- pb = (ProgressBar) findViewById(R.id.pb);
- lv = (ListView) findViewById(R.id.lv);
- mAdapter = new CommonAdapter<String>(this,R.layout.item_layout,mArticles) {
- @Override
- protected void convert(ViewHolder viewHolder, String item, int position) {
- viewHolder.setText(R.id.text1,item);
- }
- };
- lv.setAdapter(mAdapter);
- }
- @Override
- public void showAricles(List<String> articles) {
- mArticles.addAll(articles);
- mAdapter.notifyDataSetChanged();
- }
- @Override
- public void showLoading() {
- pb.setVisibility(View.VISIBLE);
- }
- @Override
- public void hideLoading() {
- pb.setVisibility(View.GONE);
- }
- }
延伸
需要注意的是, 上述代码中对于 ArticlePresenter 并没有进行接口抽象, 而是使用了具体类, 因为业务逻辑相对稳定, 在此我们直接使用具体类即可. 当然, 如果业务逻辑相对来说易于变化, 使用 Presenter 接口来应对更是最好不过了.
同时, 由于 Presenter 经常性地需要执行一些耗时操作, 如网络请求. 而 Presenter 持有了 MvpHomeActivity 的强引用, 如果在请求结束之前 Activity 被销毁了, 那么由于网络请求还没有返回, 导致 Presenter 一直持有 MvpHomeActivity 对象, 使得 MvpHomeActivity 对象无法被回收, 此时就发生了内存泄漏.
为了解决上述问题, 可以通过弱引用和 Activity,Fragment 的生命周期来解决这个问题. 首先建立一个 Presenter 抽象, 它是一个泛型类, 泛型类型为 View 角色要实现的接口类型, 代码如下:
- public abstract class BasePresenter<T> {
- protected Reference<T> mViewRef;//View 接口类型的弱引用
- public void attachView(T view){
- mViewRef = new WeakReference<T>(view);// 建立关联
- }
- // 获取 View
- protected T getView(){
- return mViewRef.get();
- }
- // 判断是否与 View 建立了关联
- public boolean isViewAttached(){
- return mViewRef!=null&&mViewRef.get()!=null;
- }
- // 解除关联
- public void detachView(){
- if(mViewRef!=null){
- mViewRef.clear();
- mViewRef = null;
- }
- }
- }
然后, 创建一个 MVPBaseActivity 基类, 通过这个基类的生命周期来控制它和 Presenter 的关系, 代码如下:
- public abstract class MVPBaseActivity<V, P extends BasePresenter<V>> extends Activity {
- /**
- *Presenter 对象
- */
- protected P mPresenter;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- // 创建 Presenter
- mPresenter = createPresenter();
- mPresenter.attachView((V) this);
- }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- /**
- * 弱引用防止内存溢出
- */
- mPresenter.detachView();
- }
- /**
- * 子类负责提供具体的 Presenter 对象
- */
- protected abstract P createPresenter();
- }
MVPBaseActivity 含有两个泛型参数, 第一个是 View 层接口, 第二个是 Presenter 的具体类型. 通过泛型参数, 使得一些通用的逻辑可以被抽象到 MVPBaseActivity 类中.
它山之石
理解 Java 中的弱引用
来源: https://juejin.im/post/5c878bcf6fb9a04a0d57b138