浅谈安卓中的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
interface与Presenter进行交互,降低耦合,方便进行单元测试;

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

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

2.  为什么使用MVP模式

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

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

3.  MVP模式实例

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

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

 
                     

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

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

<span style="font-size:18px;">public interface IUserView {
	//显示进度条
	void showLoading();
	//展示用户数据
	void showUser(List<User> users);

}</span>

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

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

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

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

public class UserModelImpl implements IUserModel{

	@Override
	public void loadUser(UserLoadListenner listener) {
		//模拟加载本地数据
		List<User> users = new ArrayList<User>();
		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<User> 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方法,完成加载所有逻辑。

<pre name="code" class="java">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<User> 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<User> users;

	public UserAdapter(Context context, List<User> 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模式的优越性:

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

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

public class GridActivity extends MvpBaseActivity<IUserView, GridPresenter> 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<User> 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<User> users) {
					// 数据加载完后进行回调,交给view进行展示
					mUserView.showUser(users);

				}
			});
		}

	}

}

4)MVP中的内存泄露问题

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

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

public abstract class BasePresenter<T> {

}

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

public abstract class MvpBaseActivity<V,T extends BasePresenter<V>> 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<T> {
	//当内存不足,释放内存
	protected WeakReference<T> mViewReference;

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

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

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

//进行关联
	public void attachView(T view) {
		mViewReference = new WeakReference<T>(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<IUserView>{
	//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<User> users) {
					// 数据加载完后进行回调,交给view进行展示
					//mUserView.showUser(users);
					getView().showUser(users);

				}
			});
		}

	}

}

然后对BaseActivity进行修改:

public abstract class MvpBaseActivity<V,T extends BasePresenter<V>> 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模式的分析到此就结束了,更多的应用得大家自己在项目中对该模式进行运用,并不断进行总结。

时间: 2024-10-10 00:25:45

浅谈安卓中的MVP模式的相关文章

浅谈JavaScript中的原型模式

在JavaScript中创建对象由很多种方式,如工厂模式.构造函数模式.原型模式等: <pre name="code" class="html">//工厂模式 function createPerson(name,age,job) { var o = new Object; o.name = name; o.age = age; o.job = job; o.sayName = function() { alert(this.name); } retur

浅谈Android中的MVP

转载请标明出处: http://blog.csdn.net/hai_qing_xu_kong/article/details/51745798 本文出自:[顾林海的博客] 前言 为什么使用MVP,网上有很多说法,最主要就是减轻了Activity的责任,相比于MVC中的Activity承担的责任太多,因此有必要讲讲MVP. MVP入门 在MVC框架中,View是可以直接读取Model模型中的数据的,Model模型数据发生改变是会通知View数据显示发生相应的改变.而在MVP中Model和View之

浅谈android中的mvc模式

mvc是model.view.controller的缩写.android 鼓励弱耦合和组件的重用,android 中mvc的具体体现如下: 模型(model):是应用程序的主题部分,所有的业务逻辑都应在该层(对数据库的操作.对网络等的操作都应该在model里面处理,当然对计算等操作也是必须放在该层的). 视图层(view):是应用程序中负责生成用户界面的部分.也是整个mvc架构中用户唯一可以看到的一层,接收用户的输入,显示用户的处理结果.一般用xml文件进行界面的描述,使用的时候可以非常方便的引

浅谈php设计模式(1)---工厂模式

一.接口继承直接调用 先看看这样一段代码: 1 <?php 2 3 interface db{ 4 function conn(); 5 } 6 7 class dbmysql implements db { 8 public function conn(){ 9 echo "连接到了mysql"; 10 } 11 } 12 13 class dbsqlite implements db{ 14 public function conn(){ 15 echo "连接到了

浅谈设计模式3-模板方法模式

模版方法模式,个人认为还是用处比较多的一个设计模式,而且也是比较好学和理解的一个.依然来通过模拟一个场景来慢慢了解. 现在我们来实现一下泡茶这个过程.首先我们需要烧开一壶水,然后往茶壶中放茶叶,加入开水,等待茶泡好. 经过前两次的分享,大家应该具备了基本的面向对象的思想了,这里就不再用面向过程的方式演示了. 首先,有一种普通人,他泡茶的方式是这样的 public class Common     { public void MakeTea()         {             Heat

浅谈数据库系统中的cache(转)

http://www.cnblogs.com/benshan/archive/2013/05/26/3099719.html 浅谈数据库系统中的cache(转) Cache和Buffer是两个不同的概念,简单的说,Cache是加速"读",而buffer是缓冲"写",前者解决读的问题,保存从磁盘上读出 的数据,后者是解决写的问题,保存即将要写入到磁盘上的数据.在很多情况下,这两个名词并没有严格区分,常常把读写混合类型称为buffer cache,本文后续的论述中,统一

【转】浅谈Java中的equals和==

浅谈Java中的equals和== 在初学Java时,可能会经常碰到下面的代码: 1 String str1 = new String("hello"); 2 String str2 = new String("hello"); 3 4 System.out.println(str1==str2); 5 System.out.println(str1.equals(str2)); 为什么第4行和第5行的输出结果不一样?==和equals方法之间的区别是什么?如果在初

浅谈oracle中rowid和rownum

[ 概要 ] 刚刚接触oracle的同学可能常常会被rowid和rownum这两个词弄混, 弄清楚这两个家伙对于我们写sql会有很大的帮助, 下面偶就抛砖引玉, 简单地谈谈他们之间的区别吧. [ 比较 ] rowid和rownum都是oracle中的伪列, 但他们还是存在本质区别: rowid: 是物理地址, 用于定位数据表中数据的位置, 它是唯一的且不会改变. rownum: 是根据查询的结果集给每行分配的一个逻辑编号, 查询结果不同, rownum自然不同. 对于同一条记录, 查询条件不同,

转 浅谈C++中指针和引用的区别

浅谈C++中指针和引用的区别 浅谈C++中指针和引用的区别 指针和引用在C++中很常用,但是对于它们之间的区别很多初学者都不是太熟悉,下面来谈谈他们2者之间的区别和用法. 1.指针和引用的定义和性质区别: (1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元:而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已.如: int a=1;int *p=&a; int a=1;int &b=a; 上面定义了一个整形变量和一个指针变量p,该指针变量指向a