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 {
-
- IUserView mUserView;
-
- IUserModel mUserModel = new UserModelImpl();
-
- public Presenter1(IUserView mUserView) {
- super();
- this.mUserView = mUserView;
- }
-
- public void load() {
-
- mUserView.showLoading();
-
- if(mUserModel != null){
- mUserModel.loadUser(new UserLoadListenner() {
-
- @Override
- public void complete(List<User> users) {
-
- 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() {
-
- return users.size();
- }
-
- @Override
- public Object getItem(int position) {
-
- return users.get(position);
- }
-
- @Override
- public long getItemId(int position) {
-
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- LayoutInflater inflater = LayoutInflater.from(context);
- ViewHolder viewHolder = null;
-
- 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) {
-
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_grid);
- mGridView = (GridView) findViewById(R.id.gv);
- mPresenter.load();
- }
-
- @Override
- public void showLoading() {
-
- Toast.makeText(this, "正在拼命加载中", Toast.LENGTH_SHORT).show();
- }
-
- @Override
- public void showUser(List<User> users) {
-
- mGridView.setAdapter(new UserAdapter(this,users));
- }
-
- @Override
- protected GridPresenter createPresenter() {
-
-
- return new GridPresenter();
- }
-
- }
- public class Presenter2 {
-
- IUserView mUserView;
-
- IUserModel mUserModel = new UserModelImpl2();
-
- public Presenter2(IUserView mUserView) {
- super();
- this.mUserView = mUserView;
- }
-
- public void load() {
-
- mUserView.showLoading();
-
- if(mUserModel != null){
- mUserModel.loadUser(new UserLoadListenner() {
-
- @Override
- public void complete(List<User> users) {
-
- 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) {
-
- super.onCreate(savedInstanceState);
-
- mPresenter = createPresenter();
-
-
- 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>{
-
-
-
- IUserModel mUserModel = new UserModelImpl();
-
-
-
-
-
-
- public void load() {
-
-
- getView().showLoading();
-
- if(mUserModel != null){
- mUserModel.loadUser(new UserLoadListenner() {
-
- @Override
- public void complete(List<User> 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) {
-
- super.onCreate(savedInstanceState);
-
- mPresenter = createPresenter();
-
-
- mPresenter.attachView((V) this);
- }
-
- protected abstract T createPresenter();
- @Override
- protected void onDestroy() {
-
- super.onDestroy();
- mPresenter.detachView();
- }
- }
oncreate方法中关联view,onDestroy方法中对关联进行清除,所有关于内存泄露的逻辑就完成了,好了,对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 {
-
- IUserView mUserView;
-
- IUserModel mUserModel = new UserModelImpl();
-
- public Presenter1(IUserView mUserView) {
- super();
- this.mUserView = mUserView;
- }
-
- public void load() {
-
- mUserView.showLoading();
-
- if(mUserModel != null){
- mUserModel.loadUser(new UserLoadListenner() {
-
- @Override
- public void complete(List<User> users) {
-
- 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() {
-
- return users.size();
- }
-
- @Override
- public Object getItem(int position) {
-
- return users.get(position);
- }
-
- @Override
- public long getItemId(int position) {
-
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- LayoutInflater inflater = LayoutInflater.from(context);
- ViewHolder viewHolder = null;
-
- 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) {
-
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_grid);
- mGridView = (GridView) findViewById(R.id.gv);
- mPresenter.load();
- }
-
- @Override
- public void showLoading() {
-
- Toast.makeText(this, "正在拼命加载中", Toast.LENGTH_SHORT).show();
- }
-
- @Override
- public void showUser(List<User> users) {
-
- mGridView.setAdapter(new UserAdapter(this,users));
- }
-
- @Override
- protected GridPresenter createPresenter() {
-
-
- return new GridPresenter();
- }
-
- }
- public class Presenter2 {
-
- IUserView mUserView;
-
- IUserModel mUserModel = new UserModelImpl2();
-
- public Presenter2(IUserView mUserView) {
- super();
- this.mUserView = mUserView;
- }
-
- public void load() {
-
- mUserView.showLoading();
-
- if(mUserModel != null){
- mUserModel.loadUser(new UserLoadListenner() {
-
- @Override
- public void complete(List<User> users) {
-
- 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) {
-
- super.onCreate(savedInstanceState);
-
- mPresenter = createPresenter();
-
-
- 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>{
-
-
-
- IUserModel mUserModel = new UserModelImpl();
-
-
-
-
-
-
- public void load() {
-
-
- getView().showLoading();
-
- if(mUserModel != null){
- mUserModel.loadUser(new UserLoadListenner() {
-
- @Override
- public void complete(List<User> 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) {
-
- super.onCreate(savedInstanceState);
-
- mPresenter = createPresenter();
-
-
- mPresenter.attachView((V) this);
- }
-
- protected abstract T createPresenter();
- @Override
- protected void onDestroy() {
-
- super.onDestroy();
- mPresenter.detachView();
- }
- }
oncreate方法中关联view,onDestroy方法中对关联进行清除,所有关于内存泄露的逻辑就完成了,好了,对MVP模式的分析到此就结束了,更多的应用得大家自己在项目中对该模式进行运用,并不断进行总结。