`
小麦麦子
  • 浏览: 28482 次
文章分类
社区版块
存档分类
最新评论

如何开发自己的Android MVP小框架?

阅读更多

 

       相信熟悉android开发的童鞋对MVP框架应该都不陌生吧,网上很多关于android中实现MVP的文章,大家可以直接搜索学习。这些文章中,MVP的实现思路基本都是把ActivityFragment作为Presenter,这种方式不同于现在主流的MVP方式,不过却好的解决了Activity生命周期带来的问题,且让MVP的实现更加轻松了。

 

      小编觉得很多关于android MVP框架的实现的文章都写的非常不错,且今天在网上看到一位童鞋利用上面所说的思路实现自己的MVP框架 MVPro,更是让人眼前一亮。下面小编和大家分享分享这位牛叉叉的童鞋是怎么实现他的MVPro的,一起来围观吧。

 

MVPro的原理

       和以往大家实现MVP的思想类似,将ActivityFragment作为Presenter,为了形象直观,我们通过下面这张图,来展示MVPro的原理:

 

MVPro的实现很简单,思想和上面两篇文章介绍的一样,都是将ActivityFragment作为Presenter,首先一张图来解释一下MVPro的原理,

(图片没传上来,这部分只能靠大家想想了。。。。天真

 

Presenter即我们的Activity或者Fragment, View呢?说白了就是我们从ActivityFragment中提取出来的和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生命周期相关的代码,两个方法createcreated是我们唯一关心的重点,但是也是非必须重写的,这两个方法都是Presenter提供的方法,他们两个的区别就是createsetContentView之前调用,而created是在setContentView之后调用。

剩下的几个方法我们可以猜测到都是从View层发起的,那么接下来我们就来看看View层该如何去写。从MainActivity实现的泛型中我们可以看出,这里我们对应的View层代码在DataListView中。

 

/**

 * Created by www.maiziedu.com 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希望各种事件都在Presenterimplements,因为EventHelper 提供了基于Presenter的事件监听。

 

ok MVPro的使用就这么简单,不过相信很多人还是摸不着头脑,所以下面我们将会深入到源码中,来了解一下MVPro的实现流程。

 

MVPro源码

首先我们还是要从Presenter入手,Presenter的源头是一个接口,这里面定义了Presenter的必须需要的几个方法,

 

/**

 * Presenter的根接口<br />

 * Created by www.maiziedu.com 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中对应的就是DataListViewcreatecreated是两个扩展方法分别在onCreatesetContentView之前和之后调用。接下来,我们就来看看我们上面MainActivity继承的那个ActivityPresenterImpl的代码,

 

/**

 * Activity作为Presenter的基类 <br />

 * Created by www.maiziedu.com 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方法,以便在开发中书写初始化控件完毕后的代码。

 

Presenter的代码很简单,主要是一些流程性的东西,接下来我们来看看View层的代码是怎么实现的。还是从接口——IView开始,

 

/**

 * View层的根接口 <br />

 * Created by www.maiziedu.com on 2015/11/15.

 */

public interface IView {

    /**

     * 根据 {@link getLayoutId}方法生成生成setContentView需要的根布局

     * @param inflater

     * @param container

     * @return

     */

    View create(LayoutInflater inflater, ViewGroup container);

 

    /**

     * ActivityonCreate完毕后调用

     */

    void created();

 

    /**

     * 返回当前视图需要的layoutid

     * @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方法根据提供的layoutid来生成布局对象,上面PresentersetContentView的参数就是他的返回值。

created会在inflater布局完成后调用,以便我们一些操作。

getLayoutId需要我们去实现,返回需要的布局文件的id

findViewById提供了一个泛型的查找控件方法,有了它我们再也不用类型转换了。

bindPresenter绑定对应的Presenter

bindEvent我们需要实现这个方法来为控件设置监听。

 

 在介绍完这些方法后,我们就来看看我们View层的基类都是做了什么工作。

 

/**

 * View层的基类

 * Created by www.maiziedu.com 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方法,在项目中我们可以在这个方法中查找我们需要的控件。

createdbindEvent方法在这里都是空实现,这样做的目的就是在我们自己的代码中不需要任何时候都要实现这两个方法。

 

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接口,如果实现了,就进行强制类型转换使用,从这里我们也可以看到,为了尽量减少我们的PresenterView层的代码耦合,MVPro并没有使用泛型,也不建议从Presenter传递OnClickListener对象,而是建议我们的Presenter直接实现OnClickListener接口,这样做,EventHelper会自动帮我们完成类型的检查和监听的设置。

 

到这里,我们还有一个helper没有看实现,就是那个获取泛型类型的帮助类。

 

/**

 * Created by www.maiziedu.com 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> {}

 

以上就是利用Android MVP框架的实现思想,实现自己MVP框架的具体方法和源码,总的来说,掌握了思路和方法后,实现起来还是不难的。如果大家在自己的MVP框架实现过程中,有更好的思路或方法,也欢迎补充分享。

 

 

 

相关文章:《Android React Native中如何封装使用原生UI组件

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics