浅谈安卓中的MVP模式

浅谈安卓中的MVP模式该篇博客主要对 MVP 模式进行了解析 有 MVP 简介 为什么使用 MVP 模式 MVP 模式实例 MVP 中的内存泄露问题几部分组成

端午放假,天气下雨,于是乎在家撸一下博客,本篇博客将为大家解析MVP模式在安卓中的应用。

本文将从以下几个方面对MVP模式进行讲解:

1.  MVP简介

2.  为什么使用MVP模式

3.  MVP模式实例

4.  MVP中的内存泄露问题




1.  MVP简介:


随着UI创建技术的功能日益增强,UI层也履行着越来越多的职责。为了更好地细分视图(View)与模型(Model)的功能,让View专注于处理数据的可视化以及与用户的交互,同时让Model只关系数据的处理,基于MVC概念的MVP(Model-View-Presenter)模式应运而生。

MVP模式里通常包含4个要素:

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

(2)ViewInterface:需要View实现的接口,View通过View interfacePresenter进行交互,降低耦合,方便进行单元测试;

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

(4)Presenter:作为ViewModel交互的中间纽带,处理与用户交互的负责逻辑。


2.  为什么使用MVP模式


浅谈安卓中的MVP模式


Android开发中,Activity并不是一个标准的MVC模式中的Controller的首要职责是加载应用的布局和初始化用户界面,并接受并处理来自用户的操作请求,进而作出响应。随着界面及其逻辑的复杂度不断提升,Activity类的职责不断增加,以致变得庞大臃肿。当我们将其中复杂的逻辑处理移至另外的一个类(Presneter)中时,Activity其实就是MVP模式中 View,它负责UI元素的初始化,建立UI元素与Presenter的关联(Listener之类),同时自己也会处理一些简单的逻辑(复杂的逻辑交由 Presenter处理).

 另外,回想一下你在开发Android应用时是如何对代码逻辑进行单元测试的?是否每次都要将应用部署到Android模拟器或真机上,然后通过模拟用户操作进行测试?然而由于Android平台的特性,每次部署都耗费了大量的时间,这直接导致开发效率的降低。而在MVP模式中,处理复杂逻辑的 Presenter是通过interfaceView(Activity)进行交互的,这说明了什么?说明我们可以通过自定义类实现这个 interface来模拟Activity的行为对Presenter进行单元测试,省去了大量的部署及测试的时间。


3.  MVP模式实例

好了,大致了解了MVP模式的基本概念之后,我们就使用MVP模式来写一个小例子。

包的结构如下图所示:                    效果展示:

浅谈安卓中的MVP模式                       浅谈安卓中的MVP模式

下面开始讲解mvp模式的步骤:

1) 创建view的接口类,根据业务定义抽象方法

public interface IUserView { //显示进度条 void showLoading(); //展示用户数据 void showUser(List 
   
     users); } 
   

2) 创建model的接口类,根据业务定义抽象方法


其中定一个加载数据的方法,同时设置一个加载完成的监听,监听内设置抽象方法complete,用于加载完成后进行回调

public interface IUserModel { //加载用户信息的方法 void loadUser(UserLoadListenner listener); //加载完成的回调 interface UserLoadListenner{ void complete(List 
  
    users); } } 
  

3)创建model的实现类,实现其中抽象方法,其中的user类是在bean包根据需求自行创建的

public class UserModelImpl implements IUserModel{ @Override public void loadUser(UserLoadListenner listener) { //模拟加载本地数据 List 
  
    users = new ArrayList 
   
     (); users.add(new User("姚明", "我很高", R.drawable.ic_launcher)); users.add(new User("科比", "怒砍81分", R.drawable.ic_launcher)); users.add(new User("詹姆斯", "我是宇宙第一", R.drawable.ic_launcher)); users.add(new User("库里", "三分我最强", R.drawable.ic_launcher)); users.add(new User("杜兰特", "千年老二", R.drawable.ic_launcher)); if(listener != null){ listener.complete(users); } } } 
    
  

加载完数据,回调listener中的complete方法。

4) 创建present,在构造函数传入view的实现类,同时在其中new出model的实现类,创建一个方法load,实现view与model间通信的桥梁。

public class Presenter1 { //view IUserView mUserView; //model IUserModel mUserModel = new UserModelImpl(); //ͨ通过构造函数传入view public Presenter1(IUserView mUserView) { super(); this.mUserView = mUserView; }
//加载数据 public void load() { //加载进度条 mUserView.showLoading(); //model进行数据获取 if(mUserModel != null){ mUserModel.loadUser(new UserLoadListenner() { @Override public void complete(List 
  
    users) { // 数据加载完后进行回调,交给view进行展示 mUserView.showUser(users); } }); } } 
  

Load中,先调用mUserView.showLoading() 显示加载进度,然后是调用mUserModel.loadUser加载数据,其中要实现Listenner的complete方法,其中的逻辑就是用view将数据显示到界面,model的最后会回调listener中的complete方法,数据就显示在界面上了。


5) MainActivity显然是用来显示数据的,其中有一个listview,创建与其相关的两个布局文件activity_main.xml与item_user.xml,令MainActivity实现IUserView接口,并实现两个抽象方法,创建listview的适配器,重写构造函数,并利用viewHolder,复用convertView对其进行优化,最后创建Presenter,并调用其load方法,完成加载所有逻辑。

public class MainActivity extends ActionBarActivity implements IUserView { private ListView mListView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mListView = (ListView) findViewById(R.id.lv); new Presenter1(this).load(); } public void showUser(List 
   
     users) { //显示所有用户列表 mListView.setAdapter(new UserAdapter(this,users)); } @Override public void showLoading() { Toast.makeText(this, "正在拼命加载中", Toast.LENGTH_SHORT).show(); } } 
   



适配器:

public class UserAdapter extends BaseAdapter { private Context context; private List 
   
     users; public UserAdapter(Context context, List 
    
      users) { this.context = context; this.users = users; } @Override public int getCount() { // TODO Auto-generated method stub return users.size(); } @Override public Object getItem(int position) { // TODO Auto-generated method stub return users.get(position); } @Override public long getItemId(int position) { // TODO Auto-generated method stub return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(context); ViewHolder viewHolder = null; //convertView if (convertView == null) { convertView = inflater.inflate(R.layout.item_user, null); viewHolder = new ViewHolder(); viewHolder.image = (ImageView) convertView .findViewById(R.id.iv_user); viewHolder.name = (TextView) convertView.findViewById(R.id.tv_name); viewHolder.content = (TextView) convertView .findViewById(R.id.tv_content); convertView.setTag(viewHolder); }else{ viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.image.setImageResource(users.get(position).getPicid()); viewHolder.name.setText(users.get(position).getName()); viewHolder.content.setText(users.get(position).getContent()); return convertView; } private static class ViewHolder { ImageView image; TextView name; TextView content; } } 
     
   

这样,我们的小例子就写完了,效果如下:

浅谈安卓中的MVP模式

体会MVP模式的优越性:

a) 假设我们不从本地获取用户数据了,改成从网络获取,只需要从新写一个model的实现类,并new 一个present,并在MainActivity中进行替换,就可以解决,我们模拟一下这种情况,发现修改十分方便,主界面建议使用MVP模式,它很好遵守了开闭原则。

b) 假设我不想用listview显示数据,想换成gridview,无需修改原来代码,只需要新建一个新的Activity来实现view,实现接口方法,同时使用gridview与新建一个与其对应的adapter即可,符合了开闭原则,不修改源码,而是进行扩展性修改。View与model解耦,可以发现我们写的Activity里面都是没有model的影子的,只有presenter.

public class GridActivity extends MvpBaseActivity 
    
      implements IUserView{ private GridView mGridView; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.activity_grid); mGridView = (GridView) findViewById(R.id.gv); mPresenter.load(); } @Override public void showLoading() { // TODO Auto-generated method stub Toast.makeText(this, "正在拼命加载中", Toast.LENGTH_SHORT).show(); } @Override public void showUser(List 
     
       users) { // TODO Auto-generated method stub mGridView.setAdapter(new UserAdapter(this,users)); } @Override protected GridPresenter createPresenter() { // TODO Auto-generated method stub return new GridPresenter(); } } 
      
    

public class Presenter2 { //view IUserView mUserView; //model IUserModel mUserModel = new UserModelImpl2(); //ͨ通过构造函数传入view public Presenter2(IUserView mUserView) { super(); this.mUserView = mUserView; } //加载数据 public void load() { //加载进度条 mUserView.showLoading(); //model进行数据获取 if(mUserModel != null){ mUserModel.loadUser(new UserLoadListenner() { @Override public void complete(List 
     
       users) { // 数据加载完后进行回调,交给view进行展示 mUserView.showUser(users); } }); } } } 
     

4)MVP中的内存泄露问题

发现我们之前写的两个Acitivty有共性的地方,就是都new 了present,我们对代码进行抽取,提高代码的复用性。

在各个Activitty中Presenter有很多类型,所以在BaseActivitty中,也需要对Presenter进行抽取成BasePresenter,MVP中Presenter是持有view的引用的,所以BasePresenter中使用泛型

public abstract class BasePresenter 
       
         { } 
       

在BaseActivitty中,Presenter的具体类型交给子类去确定,我们只提供一个生成Presenter的方法,这里多次用到了泛型,需要注意

public abstract class MvpBaseActivity 
       
         > extends ActionBarActivity { protected T mPresenter; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); //创建presenter mPresenter = createPresenter(); //内存泄露 //关联View mPresenter.attachView((V) this); } protected abstract T createPresenter(); } 
       

内存泄露分析:加入Model在请求网络加载数据,此时假设Activity由于内存不足,被GC回收,但是网络加载还未完成,则Presenter还存在,并持有Activity的引用,当网络加载数据完成,Presenter会使用Activity进行数据展现,而此时Activity已被回收,就发生了内存泄露,会报错,所以解决方法是:当view被回收,Presenter要解除与其的关联。

既然是Presenter解除与view的关联,那关联与解除的逻辑肯定是在Presenter中,使用弱引用包裹view,理由是,使用弱引用,当GC扫描到的时候,就会立即回收。所以对BasePresenter进行如下的修改:

public abstract class BasePresenter 
       
         { //当内存不足,释放内存 protected WeakReference 
        
          mViewReference; 
         
       


创建关联和解除关联的方法:

进行关联的逻辑:创建弱引用,并包裹view

解除关联的逻辑:判断,如果弱引用不为空,清空弱引用,并设置为空,彻底释放

//进行关联 public void attachView(T view) { mViewReference = new WeakReference 
       
         (view); } //解除关联 public void detachView() { if(mViewReference != null){ mViewReference.clear(); mViewReference = null; } } 
       

暴露一个方法,用于其他类从弱引用中取出view

protected T getView() { return mViewReference.get(); }

GridPresenter继承BasePresenter,进行对象抽象方法的实现

public class GridPresenter extends BasePresenter 
       
         { //view //IUserView mUserView; //model IUserModel mUserModel = new UserModelImpl(); /*public GridPresenter(IUserView mUserView) { super(); this.mUserView = mUserView; }*/ //加载数据 public void load() { //加载进度条 //mUserView.showLoading(); getView().showLoading(); //model进行数据获取 if(mUserModel != null){ mUserModel.loadUser(new UserLoadListenner() { @Override public void complete(List 
        
          users) { // 数据加载完后进行回调,交给view进行展示 //mUserView.showUser(users); getView().showUser(users); } }); } } } 
         
       

然后对BaseActivity进行修改:

public abstract class MvpBaseActivity 
       
         > extends ActionBarActivity { protected T mPresenter; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); //创建presenter mPresenter = createPresenter(); //内存泄露 //关联View mPresenter.attachView((V) this); } protected abstract T createPresenter(); @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); mPresenter.detachView(); } } 
       

oncreate方法中关联view,onDestroy方法中对关联进行清除,所有关于内存泄露的逻辑就完成了,好了,对MVP模式的分析到此就结束了,更多的应用得大家自己在项目中对该模式进行运用,并不断进行总结。




























版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/222267.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 下午4:11
下一篇 2026年3月17日 下午4:11


相关推荐

  • 如何用字节的扣子(coze)实现多轮对话

    如何用字节的扣子(coze)实现多轮对话

    2026年3月12日
    2
  • 我的博客创作之路[通俗易懂]

    我的博客创作之路[通俗易懂]我的博客创作之路。。在博客的在评论/留言中常看到有朋友在问博客的意义究竟是什么?工作这么忙那有时间写博客、有空休息一下多好……等类似信息,其实我早就想写篇这样的文章了从2008年我写博客到现在,每周都会做工作笔记,从没间断,有时我会将大家关注的话题以博文的方式在网上发布,即使现在玩微博了,我也依然如此坚持。。。在我的博客上,从分类来看主要分Linux企业应用、教学、网络管理、…

    2022年7月21日
    16
  • spark的内存模型_分布式存储的应用场景

    spark的内存模型_分布式存储的应用场景Spark内存管理模型详解

    2022年4月20日
    60
  • 压测工具Jmeter入门使用

    压测工具Jmeter入门使用一 创建一个基础的 Web 测试 1 测试内容模拟五个用户请求 2 次指定的 http 接口 并且重复 2 次 也就是总共请求有 5 2 2 20 次要构建本次内容需要用到 Jmeter 的元素 线程组 HTTP 请求 HTTP 请求默认值和图形结果 2 具体操作 2 1 创建测试计划单击左上角的新建计划按钮即可 同时可以设置测试计划的名称 注释 测试计划是一个完整测试元素的顶层容器 2 2 创建线程组线程组可以用来设置要模拟的用户数量 用户应该多久发送一次请求 以及他们应该发送

    2026年3月20日
    2
  • 大模型学习:一文读懂MCP,Agent,智能体的区别与联系!

    大模型学习:一文读懂MCP,Agent,智能体的区别与联系!

    2026年3月16日
    1
  • leetcode 颜色分类_leetcode难度

    leetcode 颜色分类_leetcode难度给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。示例 1:输入:nums = [2,0,2,1,1,0]输出:[0,0,1,1,2,2]示例 2:输入:nums = [2,0,1]输出:[0,1,2]示例 3:输入:nums = [0]输出:[0]示例 4:输入:nums = [1]输出:[1] 提示:n == num

    2022年8月9日
    6

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号