最近看了两篇关于Android实现MVP的文章[一种在android中实现MVP模式的新思路](https://github.com/bboyfeiyu/android-tech-frontier/tree/master/androidweekly/%E4%B8%80%E7%A7%8D%E5%9C%A8android%E4%B8%AD%E5%AE%9E%E7%8E%B0MVP%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%96%B0%E6%80%9D%E8%B7%AF)和[用MVP架构开发Android应用](http://kymjs.com/code/2015/11/09/01/)。
两篇文章的思路都是一样的,即把Activity、Fragment作为Presenter,这种方式不同于现在主流的MVP方式,不过它很好的解决了Activity生命周期带来的问题,而且我认为它让MVP的实现更加轻松了。
那么问题来了,这么好的思路,我们怎么可以不去实现一下自己的MVP呢? 于是我花了一晚上的时间整出了一个小小的MVP框架——MVPro
### MVPro介绍
MVPro的实现很简单,思想和上面两篇文章介绍的一样,都是将Activity和Fragment作为Presenter,首先一张图来解释一下MVPro的原理,
Created with Raphaël 2.1.0PresenterPresenterViewViewonCreatecreatebindPresentercreatecreatedsetContentViewbindEventcreated
Presenter即我们的Activity或者Fragment, View呢?说白了就是我们从Activity和Fragment中提取出来的和View操作相关的代码,思路很简单也很清晰,下面我们就以一个简单的demo详细学习一下MVPro的使用吧。
### MVPro使用
因为MVPro是将Activity视为Presenter,所以我们代码的主线应该是Presenter了,首先上一个Presenter的代码,
~~~
public class MainActivity extends ActivityPresenterImpl<DataListView>
implements AdapterView.OnItemClickListener, View.OnClickListener {
@Override
public void create(Bundle savedInstance) {
}
@Override
public void created(Bundle savedInstance) {
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mView.toast(position);
}
@Override
public void onClick(View v) {
if(v.getId() == R.id.newdata) newData();
else getData();
}
private void newData() {
new MainBiz().data(new MainBiz.Callback<ArrayList<String>>() {
@Override
public void callback(ArrayList<String> data) {
mView.setData(data);
}
});
}
private void getData() {
new MainBiz().data(new MainBiz.Callback<ArrayList<String>>() {
@Override
public void callback(ArrayList<String> data) {
mView.addData(data);
}
});
}
}
~~~
MainActivity继承自ActivityPresenterImpl类,而且在代码中并没有任何和Activity生命周期相关的代码,两个方法create和created是我们唯一关心的重点,但是也是非必须重写的,这两个方法都是Presenter提供的方法,他们两个的区别就是create在setContentView之前调用,而created是在setContentView之后调用。
剩下的几个方法我们可以猜测到都是从View层发起的,那么接下来我们就来看看View层该如何去写。从MainActivity实现的泛型中我们可以看出,这里我们对应的View层代码在DataListView中。
~~~
/**
* Created by qibin on 2015/11/15.
*/
public class DataListView extends ViewImpl {
private Button mButton1, mButton2;
private ListView mListView;
private ArrayAdapter<String> mAdapter;
@Override
public void created() {
mButton1 = findViewById(R.id.newdata);
mButton2 = findViewById(R.id.adddata);
mListView = findViewById(R.id.list);
}
@Override
public void bindEvent() {
EventHelper.click(mPresenter, mButton1, mButton2);
EventHelper.itemClick(mPresenter, mListView);
}
@Override
public int getLayoutId() {
return R.layout.list_layout;
}
public void setData(ArrayList<String> datas) {
mAdapter = new ArrayAdapter<String>(mRootView.getContext(),
android.R.layout.simple_list_item_1, datas);
mListView.setAdapter(mAdapter);
}
public void addData(ArrayList<String> datas) {
mAdapter.addAll(datas);
}
public void toast(int position) {
Toast.makeText(mRootView.getContext(),
mAdapter.getItem(position), Toast.LENGTH_SHORT).show();
}
}
~~~
在View层中,我们并不关心布局文件是怎么设置到Activity上的,我们仅仅在getLayoutId中返回我们要使用的布局文件,和created中去获取我们需要的控件即可,
不过我们还重写一个bindEvent方法,在这个方法中我们为控件绑定了事件,这里需要注意一点就是MVPro希望各种事件都在Presenter上implements,因为EventHelper
提供了基于Presenter的事件监听。
ok, MVPro的使用就这么简单,不过相信很多人还是摸不着头脑,所以下面我们将会深入到源码中,来了解一下MVPro的实现流程。
### MVPro源码
首先我们还是要从Presenter入手,Presenter的源头是一个接口,这里面定义了Presenter的必须需要的几个方法,
~~~
/**
* Presenter的根接口<br />
* Created by qibin on 2015/11/15.
*/
public interface IPresenter<T> {
/**
* 获取当前presenter泛型的类型
* @return
*/
Class<T> getViewClass();
/**
* View初始化之前可以在此方法做一些操作
*/
void create(Bundle savedInstance);
/**
* View初始化完毕后调用
*/
void created(Bundle savedInstance);
}
~~~
只有三个方法,其中getViewClass是获取我们对应的View层那个类的class, 例如上面的demo中对应的就是DataListView,create和created是两个扩展方法分别在onCreate的setContentView之前和之后调用。接下来,我们就来看看我们上面MainActivity继承的那个ActivityPresenterImpl的代码,
~~~
/**
* 将Activity作为Presenter的基类 <br />
* Created by qibin on 2015/11/15.
*/
public class ActivityPresenterImpl<T extends IView> extends Activity implements IPresenter<T> {
protected T mView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
create(savedInstanceState);
try {
mView = getViewClass().newInstance();
mView.bindPresenter(this);
setContentView(mView.create(getLayoutInflater(), null));
mView.bindEvent();
created(savedInstanceState);
} catch(Exception e) {
throw new RuntimeException(e.getMessage());
}
}
@Override
public Class<T> getViewClass() {
return GenericHelper.getViewClass(getClass());
}
@Override
public void create(Bundle savedInstance) {
}
@Override
public void created(Bundle savedInstance) {
}
}
~~~
我估计很多人在没看到ActivityPresenterImpl源码之前都会认为它应该很复杂,不过在看后大概都会忍不住吐槽一句:尼玛,这么少! 对代码确实少,而且基本都集中在了onCreate中,不过,在看onCreate之前,我们首先来看看getViewClass方法, 这个方法实现自IPresenter,而且仅仅有一行代码,它的作用就是获取当前Presenter泛型指定那个View层类,对应上面的demo中就是DataListView了。
接下来回到onCreate中,在onCreate中,我们首先调用了create方法,以便于我们执行一些在setContentView之前的代码,例如设置无标题啥的。
然后通过getViewClass获取到了View层的类,并且利用反射得到他的对象mView,接下的几步都和这个mView相关。
调用mView.bindPresenter方法,将当前Presenter关联到当前View层上。
调用mView.create方法生成我们布局文件对应的View对象,并且通过setContentView设置布局文件。
调用mView.bindEvent方法,在这个方法中我们可以对一些控件设置事件监听。
最后我们调用了created方法,以便在开发中书写初始化控件完毕后的代码。
ok, Presenter的代码很简单,主要是一些流程性的东西,解析来我们来看看View层的代码是怎么实现的。还是从接口——IView开始,
~~~
/**
* View层的根接口 <br />
* Created by qibin on 2015/11/15.
*/
public interface IView {
/**
* 根据 {@link getLayoutId}方法生成生成setContentView需要的根布局
* @param inflater
* @param container
* @return
*/
View create(LayoutInflater inflater, ViewGroup container);
/**
* 当Activity的onCreate完毕后调用
*/
void created();
/**
* 返回当前视图需要的layout的id
* @return
*/
int getLayoutId();
/**
* 根据id获取view
* @param id
* @param <V>
* @return
*/
<V extends View> V findViewById(int id);
/**
* 绑定Presenter
* @param presenter
*/
void bindPresenter(IPresenter presenter);
/**
* {@link created}后调用,可以调用{@link org.loader.helper.EventHelper.click}
* 等方法为控件设置点击事件,一般推荐使用{@link org.loader.helper.EventHelper.click(IPresenter presenter, View ...views)}
* 方法并且让你的Presenter实现相应接口。
*/
void bindEvent();
}
~~~
IView接口中定义的方法就相对多了一些,我们一个个的来说一下这些方法存在的理由。
create方法根据提供的layout的id来生成布局对象,上面Presenter的setContentView的参数就是他的返回值。
created会在inflater布局完成后调用,以便我们一些操作。
getLayoutId需要我们去实现,返回需要的布局文件的id。
findViewById提供了一个泛型的查找控件方法,有了它我们再也不用类型转换了。
bindPresenter绑定对应的Presenter。
bindEvent我们需要实现这个方法来为控件设置监听。
ok, 在介绍完这些方法后,我们就来看看我们View层的基类都是做了什么工作。
~~~
/**
* View层的基类
* Created by qibin on 2015/11/15.
*/
public abstract class ViewImpl implements IView {
/**
* create方法生成的view
*/
protected View mRootView;
/**
* 绑定的presenter
*/
protected IPresenter mPresenter;
@Override
public View create(LayoutInflater inflater, ViewGroup container) {
mRootView = inflater.inflate(getLayoutId(), container, false);
created();
return mRootView;
}
@Override
public <V extends View> V findViewById(int id) {
return (V) mRootView.findViewById(id);
}
@Override
public void created() {
}
@Override
public void bindPresenter(IPresenter presenter) {
mPresenter = presenter;
}
@Override
public void bindEvent() {
}
}
~~~
在create方法中,我们使用LayoutInflater生成了View对象,并且返回给Presenter用来setContentView,在返回之前我们还调用了created方法,在项目中我们可以在这个方法中查找我们需要的控件。
created和bindEvent方法在这里都是空实现,这样做的目的就是在我们自己的代码中不需要任何时候都要实现这两个方法。
ok, 很简单,MVPro的代码大体就这些,整个流程我们也分析完了,那Model层呢?还没有看到关于Model层的代码呢!在MVPro中并不关心你业务层是怎么实现,你完全可以使用你现在正在使用的业务层代码的架构,而不会对MVP产生任何影响。
最后,我们还是要来看看EventHelper是怎么设置事件监听的。
~~~
public class EventHelper {
public static void click(IPresenter li, View ...views) {
if(!(li instanceof View.OnClickListener)) return;
click((View.OnClickListener) li, views);
}
public static void click(View.OnClickListener li, View ...views) {
if(views == null || views.length == 0) return;
for(View v : views) v.setOnClickListener(li);
}
...
}
~~~
这里拿click事件为例,可以看到我们的参数是一个IPresenter类型, 而且是判断了你传递的这个Presenter是不是实现了View.OnClickListener接口,如果实现了,就进行强制类型转换使用,从这里我们也可以看到,为了尽量减少我们的Presenter和View层的代码耦合,MVPro并没有使用泛型,也不建议从Presenter传递OnClickListener对象,而是建议我们的Presenter直接实现OnClickListener接口,这样做,EventHelper会自动帮我们完成类型的检查和监听的设置。
恩,到这里,我们还有一个helper没有看实现,就是那个获取泛型类型的帮助类。
~~~
/**
* Created by qibin on 2015/11/15.
*/
public class GenericHelper {
public static <T> Class<T> getViewClass(Class<?> klass) {
Type type = klass.getGenericSuperclass();
if(type == null || !(type instanceof ParameterizedType)) return null;
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] types = parameterizedType.getActualTypeArguments();
if(types == null || types.length == 0) return null;
return (Class<T>) types[0];
}
}
~~~
这个类中仅仅一个方法,不过可能大部分人对这里面的代码非常陌生,这里做的事很直接,就是根据我们提供的class,来获取这个类实现的那个泛型的类型,打个比方,下面的代码获取到的就是java.lang.String,
~~~
class Child extends Super<String> {}
~~~
ok, 文章到这里就要结束了,MVPro的总体实现还是非常简单的,如果大家有什么疑问或者感觉MVPro需要什么改进的地方,大家可以在下面为我留言,我会不断去完善这个小框架的。
最后是MVPro的下载地址,这里提供了MVPro的源码(AS版)、jar包、实例代码,[https://github.com/qibin0506/MVPro](https://github.com/qibin0506/MVPro)