Android MVP框架MVPro的使用和源码分析

最近看了两篇关于Android实现MVP的文章一种在android中实现MVP模式的新思路用MVP架构开发Android应用

两篇文章的思路都是一样的,即把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的代码大体就这些,整个流程我们也分析完了,不过在最后,我们还是要来看看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

时间: 2024-10-09 19:43:49

Android MVP框架MVPro的使用和源码分析的相关文章

android 开源工具 EventBus的使用和源码分析

由于公司之前的技术分享,与eventbus和otto相关,因此主要参考了网上的文章,本文以下内容主要参考angeldeviljy  大神的http://www.cnblogs.com/angeldevil/p/3715934.html 文章,特此对其表示感谢. 如若不同意引用和转载,还请劳烦大神联系我,我好做相应处理! ----------------做一枚健康的码农-------------- EventBus eventbus 使用greenrobot 提供的用来简化组件间通信的开源项目 g

Android Debuggerd 简要介绍和源码分析(转载)

转载: http://dylangao.com/2014/05/16/android-debuggerd-%E7%AE%80%E8%A6%81%E4%BB%8B%E7%BB%8D%E5%92%8C%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/ 码字很辛苦,转载请注明来自Dylan‘s Zone的<Android Debuggerd 简要介绍和源码分析> 本文以android4.1为基础,分析debuggerd这个工具的使用方法和源码. 1.Debuggerd 简介

java集合框架10——TreeMap和源码分析(一)

前面讨论完了HashMap和HashTable的源码,这一节我们来讨论一下TreeMap.先从整体上把握TreeMap,然后分析其源码,深入剖析TreeMap的实现. 1. TreeMap简介 TreeMap是一个有序的key-value集合,它内部是通过红-黑树实现的,如果对红-黑树不太了解,请先参考下这篇博文:红-黑树.下面我们先来看看TreeMap的继承关系: java.lang.Object ? java.util.AbstractMap<K, V> ? java.util.TreeM

Android的软件包管理服务PackageManagerService源码分析

Android系统下的apk程序都是通过名为PackageManagerService的包管理服务来管理的.PacketManagerService是安卓系统的一个重要服务,由SystemServer启动,主要实现apk程序包的解析,安装,更新,移动,卸载等服务.不管是系统apk(/system/app),还是我们手工安装上去的,系统所有的apk都是由其管理的. 以android 4.0.4的源码为例,android4.0.4/frameworks/base/services/java/com/

Android应用程序启动过程——Launcher源码分析

当我们在Launcher界面单击一个应用程序图标时就会启动一个程序,那这一个过程究竟发生了些哪样呢?让我们跟踪Launcher源码来分析一下吧. 先上流程图: step1.追踪Launcher  从源码中我们可以发现Launcher其实也是一个程序,它继承于Activity.找到该文件中的onCreate()方法,代码片段如下: protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceSta

Android 中View的绘制机制源码分析 三

到目前为止,measure过程已经讲解完了,今天开始我们就来学习layout过程,不过在学习layout过程之前,大家有没有发现我换了编辑器,哈哈,终于下定决心从Html编辑器切换为markdown编辑器,这里之所以使用"下定决心"这个词,是因为毕竟Html编辑器使用好几年了,很多习惯都已经养成了,要改变多年的习惯确实不易,相信这也是还有很多人坚持使用Html编辑器的原因.这也反应了一个现象,当人对某一事物非常熟悉时,一旦出现了新的事物想取代老的事物时,人们都有一种抵触的情绪,做技术的

Android应用层View绘制流程与源码分析

Android应用层View绘制流程与源码分析 1 背景 还记得前面<Android应用setContentView与LayoutInflater加载解析机制源码分析>这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原理,记不记得最终分析结果就是下面的关系: 看见没有,如上图中id为content的内容就是整个View树的结构,所以对每个具体View对象的操作,其实就是个递归的实现. 前面<Android触摸屏事件派发机制详解与源码分析一(View篇)>文章的3-1

MyBatis框架的使用及源码分析(十一) StatementHandler

我们回忆一下<MyBatis框架的使用及源码分析(十) CacheExecutor,SimpleExecutor,BatchExecutor ,ReuseExecutor> , 这4个Excecutor执行sql操作的最终都调用了StatementHandler 来执行,我们拿SimpleExecutor来看: public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Statement st

Android 中View的绘制机制源码分析 二

尊重原创:http://blog.csdn.net/yuanzeyao/article/details/46842891 本篇文章接着上篇文章的内容来继续讨论View的绘制机制,上篇文章中我们主要讲解了View的measure过程,今天我们就来学习ViewGroup的measure过程,由于ViewGroup只是一个抽象类,所以我们需要以一个具体的布局来分析measure过程,正如我上篇文章说的,我打算使用LinearLayout为例讲解measure过程,如果你还没有读过上篇文章,那么建议你先