博客主页

1. MVC架构设计与经典的三层模型

MVC:Model-View-Controller,经典模式,很容易理。

  • Model:业务层和模型层,实体模型和业务相关的代码
  • View:视图层,android中对应于layout布局文件
  • Controller:控制层,android中的UI操作,对应于Activity

但是在Android实际开发中,这个View层对应于布局文件,其实能做的事情特别少,实际上关于该布局文件中的数据绑定操作,事件处理的代码都在Activity中;我们往往也会把具体的业务相关代码放到了Activity中;再加上Activity本身又承担着控制层的责任,这样导致Contrlller层越来越臃肿。

所以说,MVC真实存在的是MC(V),Controller与Model根本就分不开,View和Model严重耦合。

从图中可以看出,Controller是作为媒介,处于Model和View之间。Model和View之间有紧密的联系,耦合性偏强。

MVC主要缺点有两个:

  • Model层与View层之间耦合度强,导致难以维护
  • Controller层会变得复杂,代码臃肿

优点:

  • Controller层和View层都在Activity中操作,数据操作方便
  • 模块职责划分明确,主要划分层M-V-C三个模块

举一个简单的例子:获取网络图片并展示在界面上

  • Model层:获取网络图片
public interface ImageModel {    // 从网络加载图片    void loadImage(String imagePath, OnImageListener listener);    interface OnImageListener {        void shopImage(Bitmap bitmap);    }}public class ImageModelImpl implements ImageModel {    @Override    public void loadImage(String imagePath, OnImageListener listener) {        if (listener != null && !TextUtils.isEmpty(imagePath)) {            // 模拟网络获取图片            if (!TextUtils.isEmpty(imagePath)) {                listener.shopImage(BitmapFactory.decodeFile(imagePath));            } else {                listener.shopImage(null);            }        }    }}

  • Controller层
public class MainActivity extends AppCompatActivity implements ImageModel.OnImageListener {    private static final String TAG = "===>";    // View层    private ImageView mShowImageView;    // Model层    private ImageModel mImageModel;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mShowImageView = findViewById(R.id.showImageView);        findViewById(R.id.loadImageBtn).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                // 加载图片                mImageModel.loadImage("/mnt/sdcard/Pictures/2.png", MainActivity.this);            }        });        mImageModel = new ImageModelImpl();    }    @Override    public void shopImage(Bitmap bitmap) {        if (bitmap != null) {            // 展示图片            mShowImageView.setImageBitmap(bitmap);        }    }}

2. MVP思想精髓与巧妙解耦View和Model

MVP:Model-View-Presenter,MVC的一个演变模式,将Controller换成了Presenter,主要为了解决上述第一个缺点,将View和Model解耦,不过第二个缺点依然没有解决。

  • View:对应于Activity,负责View的绘制以及用户交互
  • Model:业务逻辑和实体模型
  • Persenter:负责完成View于Model间的交互

缺点

1、 MVP接口过多
2、 每一个功能,相对于MVC要多写好几个文件
3、 如果某一个界面中需要请求多个服务器接口,这个界面文件中会实现很多的回调接口,导致代码繁杂
4、 如果更改了数据源和请求中参数,会导致更多的代码修改
5、 额外的代码复杂度及学习成本

MVP相对于MVC优点

1、 减少了Activity的职责,简化了Activity中的代码,将复杂的逻辑代码提取到了Presenter中进行处理。与之对应的好处就是:耦合度更低

2、 Activity代码变得更加简洁:使用MVP之后,Activity就能瘦身许多了,基本上只有findView、setListener以及init的代码。其他的就是对Presenter的调用,还有对View接口的实现。这种情形下阅读代码就容易多了,而且你只要看Presenter的接口,就能明白这个模块都有哪些业务,很快就能定位到具体代码。Activity变得容易看懂,容易维护,以后要调整业务。删减功能也就变得简单许多。

3、 方便进行单元测试

  • 一般单元测试都是用来测试某些新加的业务逻辑有没有问题,如果采用传统的代码风格(习惯性上叫做MV模式,少了P),我们可能要先在Activity里写一段测试代码,测试完了再把测试代码删掉换成正式代码,这时如果发现业务有问题又得换回测试代码,咦,测试代码已经删掉了!好吧重新写吧……
  • MVP中,由于业务逻辑都在Presenter里,我们完全可以写一个PresenterTest的实现类继承Presenter的接口,现在只要在Activity里把Presenter的创建换成PresenterTest,就能进行单元测试了,测试完再换回来即可。万一发现还得进行测试,那就再换成PresenterTest吧。

4、 避免Activity的内存泄露

  • 发生OOM异常的原因:
    现内存泄露造成APP的内存不够用,而造成内存泄露的两大原因之一就是Activity泄露(Activity Leak)(另一个原因是Bitmap泄露(Bitmap Leak);

    Java一个强大的功能就是其虚拟机的内存回收机制,这个功能使得Java用户在设计代码的时候,不用像C++用户那样考虑对象的回收问题。然而,Java用户总是喜欢随便写一大堆对象,然后幻想着虚拟机能帮他们处理好内存的回收工作。可是虚拟机在回收内存的时候,只会回收那些没有被引用的对象,被引用着的对象因为还可能会被调用,所以不能回收

    Activity是有生命周期的,用户随时可能切换Activity,当APP的内存不够用的时候,系统会回收处于后台的Activity的资源以避免OOM。

  • MVC产生内存泄露异常分析
    采用传统的MVC模式,一大堆异步任务和对UI的操作都放在Activity里面,比如你可能从网络下载一张图片,在下载成功的回调里把图片加载到 Activity 的 ImageView 里面,所以异步任务保留着对Activity的引用。这样一来,即使Activity已经被切换到后台(onDestroy已经执行),这些异步任务仍然保留着对Activity实例的引用,所以系统就无法回收这个Activity实例了,结果就是Activity Leak。Android的组件中,Activity对象往往是在堆(Java Heap)里占最多内存的,所以系统会优先回收Activity对象,如果有Activity Leak,APP很容易因为内存不够而OOM。
  • MVC模式如何避免内存泄露
    只要在当前的Activity的onDestroy里,分离异步任务对Activity的引用,就能避免 Activity Leak

5、 模块职责划分明显,层次清晰,接口功能清晰
6、 Model层和View层分离,解耦;修改View而不影响Model
7、 功能复用度高,方便;一个Presenter可以复用于多个View,而不用更改Presenter的逻辑
8、 如果后台接口还未写好,但已知返回数据类型的情况下,完全可以写出此接口完整的功能

Android中MVP角色

1、View:负责绘制UI元素,与用户进行交互(在Android中体现为Activity)

  • 提供UI交互
  • 在presenter的控制下修改UI
  • 将业务事件交由presenter处理
  • View层不存储数据,不与Model层交互
  • 在Android中View层一般是Activity、Fragment、View(控件)、ViewGroup(布局)等

2、Activity interface:需要View实现的接口,View通过View interface与Presenter进行交互,降低耦合,方便进行单元测试

3、Model:负责存储、检索、操纵数据(有时也实现一个Model interface用来降低耦合)

  • 从网络、数据库、文件、第三方等数据源读取数据
  • 对外部的数据类型进行解析转换为APP内部数据交由上层处理
  • 对数据的临时存储、管理、协调上层数据请求

4、Presenter:作为View与Model交互的中间纽带,处理与用户交互的负责逻辑

3. MVP案例实现

从服务器端下拉最新的20篇文章,然后将每一篇文章的简介显示到列表上,当用户点击某项数据进入到另一个页面,该页面加载这篇文章的详细内容。

1、ArticleModel就是Model层接口

public interface ArticleModel {    // 加载文章数据    void loadArticles(OnArticleListener listener);    interface OnArticleListener {        void onLoadComplete(List data);    }}

2、ArticleModelImpl实现了ArticleModel接口,用于加载网络数据,为了代码简单,这里睡眠2秒模拟从网络获取数据

public class ArticleModelImpl implements ArticleModel {    @Override    public void loadArticles(OnArticleListener listener) {        new LoadArticleTask(listener).execute();    }    private static class LoadArticleTask extends AsyncTask {        private final OnArticleListener listener;        LoadArticleTask(OnArticleListener listener) {            this.listener = listener;        }        @Override        protected List doInBackground(Void... params) {            // 模拟网络请求            SystemClock.sleep(2000);            final List data = new ArrayList<>();            for (int i = 0; i < 40; i++) {                data.add(new Article("title-" + i, "message:" + i));            }            return data;        }        @Override        protected void onPostExecute(List data) {            if (listener != null) {                listener.onLoadComplete(data);            }        }    }}

3、 ArticleViewInterface就是主界面的逻辑接口,代表View接口角色,用于Presenter回调View的操作

public interface ArticleViewInterface {    void showProgressBar(); // 显示进度条    void hideProgressBar(); // 隐藏进度条    void showArticles(List data); // 展示数据}

4、Presenter层,作为View和Model的中间人。

public interface ArticlePresenter {    void loadArticles();}public class ArticlePresenterImpl implements ArticlePresenter {    // ArticleView的接口,代表了View角色    private ArticleViewInterface mView;    // 文章数据的Model,也就是Model角色     private ArticleModel mArticleModel;    public ArticlePresenterImpl(ArticleViewInterface view) {        this.mView = view;        mArticleModel = new ArticleModelImpl();    }    // 获取文章,也就是我们的业务逻辑    @Override    public void loadArticles() {        mView.showProgressBar();        mArticleModel.loadArticles(new ArticleModel.OnArticleListener() {            @Override            public void onLoadComplete(List data) {                // 数据加载完后,通知View层更新UI                mView.hideProgressBar();                mView.showArticles(data);            }        });    }}

5、ArticleActivity需要实现ArticleViewInterface接口,并且需要建立与Presenter的联系,ArticleActivity的业务逻辑都交给Presenter进行处理,处理结果通过ArticleViewInterface接口回调给ArticleActivity类

public class ArticleActivity extends AppCompatActivity implements ArticleViewInterface {    private ProgressBar mProgressBar;    private ArrayAdapter mAdapter;    private ArticlePresenter mArticlePresenter;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        initView();        // 构建ArticlePresenter,与ArticleActivity建立关联        mArticlePresenter = new ArticlePresenterImpl(this);    }    private void initView() {        mProgressBar = findViewById(R.id.load_progress_bar);        ListView listView = findViewById(R.id.list_view);        mAdapter = new ArrayAdapter<>(this, R.layout.item_list, R.id.item_title);        listView.setAdapter(mAdapter);    }    @Override    protected void onResume() {        super.onResume();        // 请求文章数据        mArticlePresenter.loadArticles();    }    @Override    public void showArticles(List data) {        mAdapter.setNotifyOnChange(true);         mAdapter.addAll(data); // 更新UI    }   // ....}

4. MVP与Activity、Fragment的生命周期

由于Presenter经常需要执行一些耗时操作,如请求网络数据,而Presenter持有了ArticleActivity的强引用,如果在请求结束之前Activity被销毁了,那么由于网络请求还没有返回,导致Presenter一直持有ArticleActivity对象,使得ArticleActivity对象无法被回收,此时就发生内存泄露。

如何解决这样的问题呢?
我们可以通过弱引用和Activity、Fragment的生命周期来解决这个问题。

1、 首先建立一个Presenter抽象,BasePresenter,它是一个泛型类,泛型类型为View角色要实现的接口类型

public abstract class BasePresenter {    private Reference mViewRef; // View接口类型的弱引用    public void attachView(V view) {        mViewRef = new WeakReference<>(view); // 建立关联    }    public void detachView() {        if (mViewRef != null) {            mViewRef.clear();            mViewRef = null;        }    }    public boolean isViewAttached() {        return mViewRef != null && mViewRef.get() != null;    }    protected V getView() {        return mViewRef.get();    }}

2、 创建一个MVPBaseActivity基类,通过这个基类的生命周期函数来控制它与Presenter的关系。MVPBaseActivity有两个泛型参数,第一个是View的接口类型,第二个是Presneter的具体类型

public abstract class MVPBaseActivity> extends AppCompatActivity{    protected T mPresenter; // Presenter对象    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        mPresenter = createPresenter(); // 创建Presenter        mPresenter.attachView((V) this);    }    @Override    protected void onDestroy() {        super.onDestroy();        mPresenter.detachView();    }    protected abstract T createPresenter();}

如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)

更多相关文章

  1. 使用Kotlin:让Android与JS交互的详解
  2. 在Android中用纯Java代码布局
  3. Study on Android【二】--ContentProvider数据模型概述
  4. android通过webservice连接SQL数据库(一)服务器端
  5. Android(安卓)上实现水波特效二--优化
  6. Android: AIDL --- Android中的远程接口
  7. NDK与JNI的基础与基本配置和使用
  8. android 调试利器之IDA
  9. mybatisplus的坑 insert标签insert into select无参数问题的解决

随机推荐

  1. Android 应用界面显示流程
  2. 【Android教程】Android Studio找不到连
  3. 可靠的功能测试--Espresso和Dagger2
  4. 解决 Android(安卓)中使用ListView和Chec
  5. 第一篇 入门必备 (Android学习笔记)
  6. Android进程说明
  7. Eclipse android 项目转android studio填
  8. Android中App可分配内存的大小
  9. android The project target (Android 2.
  10. Android XML布局文件优化