博客主页

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. DRM in Android
  2. Android(安卓)客户端与服务器交互
  3. Android(安卓)数据库对比
  4. android之resources资源
  5. android以后台service的方式获取GPRS数据
  6. android横竖屏切换
  7. Android(安卓)MediaPlayer的核心原理
  8. Retrofit的简单使用
  9. mybatisplus的坑 insert标签insert into select无参数问题的解决

随机推荐

  1. 一个简短的android病毒分析
  2. 【Kris专题】android Style 小结---kris
  3. Android中如何提高UI的性能
  4. Android实现加载网页,获取网页上图以及点
  5. web app和本地app之争 及其 iOS和Android
  6. Android中的基类—抽取出来公共的方法
  7. android环信昵称头像解决方法
  8. Android投屏(屏幕共享)设计需要考虑的关键
  9. Android获取本地图片缩略图终极解决方案
  10. 电脑怎么安装安卓(Android)x86 不使用U盘