美文网首页Android 架构/模式
MVP开发框架的一次尝试——EasyMVP

MVP开发框架的一次尝试——EasyMVP

作者: Zane96 | 来源:发表于2016-05-04 22:45 被阅读522次

前言

用MVP模式开发安卓相信大家也应该不是很陌生了。各种各样的mvp开发模式,框架也是层出不穷。之前写过一篇《MVP的一种新的尝试》 ,然后我基于这种模式(activity作为presenters)做了一个可能问题还是很多的开发框架:项目地址 。并且根据这个框架在前几天结合rxjava,retrofit,dagger做了一个挺简单的demo app:githubQuery 。我在想做这套框架之后意外的发现一个大牛做的框架和我是一样的思想!都是借鉴了这篇文章:《借鉴文章》 。做的也是大同小异了,顺便也借鉴了不少,还借鉴了另一个学长把adapter和viewholder解耦的思想。

传统mvp模式的不足

  1. 如果把activity作为view,那么如果activity异常销毁,那么恢复数据就是个问题了。并且还必须要把presenter和view生命周期同步。如果使用bundle,这是就直接破坏了mvp的设计模式,因为这样难免会把v和m耦合在一起。而将activity作为view,可以很方便的使用bundle和model层的数据进行异常恢复。

  2. context以及各种安卓系统服务,使用intent等等。

  3. 开始想用dagger进一步把v-p-m解耦,后来发现不用了,,

    具体不足请看前言中的借鉴文章!

    EasyMVP

    介绍

    1.使用泛型来使view和presenter解耦,通过编译期间从子activity传递过来的view类型反射获得具体view类型。并且框架完成activity的视图渲染,并且提供同步activity生命周期的函数供开发者使用。

    2.默认使用butterknife作为view的依赖注入。

    3.adapter作为presenter,并且与viewholder完全解耦,viewholder归入view层。默认使用recycle view。(别再提listview了)

EasyMVP.png

BaseActivityPresenters

public abstract class BaseActivityPresenter<V extends IView> extends AppCompatActivity{

    protected V v;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        try {
          //通过注解获得对应的view的实例
            v = getRootViewClass().newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
      //调用view层中生成根视图的函数
        v.creatView(getLayoutInflater(), null);
      //调用view层初始化控件的函数,这里默认使用butterkinfe了。
        v.initView();
      //视图渲染
        setContentView(v.getRootView());

      //抛出跟onCreat()函数同步的函数给开发者
        inCreat(savedInstanceState);
    }
    //接触绑定
    @Override
    protected void onDestroy() {
        super.onDestroy();
        v.removeView();
        v = null;
        inDestory();
    }

    public abstract Class<V> getRootViewClass();
    public abstract void inCreat(Bundle savedInstanceState);
    public abstract void inDestory();
    public void inPause(){}
    public void inRestart(){}
    public void inStop(){}
    public void inResume(){}
    public void inStart(){}

    @Override
    protected void onStart() {
        super.onStart();
        inStart();
    }
我已经在代码里面添加了一些注释。**框架已经默认了使用butterkinfe**,要避免重复依赖。再来看view层的框架代码。这样做其实是可以看成是在抽出一个BaseActivity,把一些操作黑箱化。

IView

public interface IView{

    void creatView(LayoutInflater inflater, ViewGroup parent);

    View getRootView();

    int getRootViewId();

    void initView();

  //调用butterkinfe的unbind()方法
    void removeView();
}
确定了几个view代理层最基本的操作。

BaseViewImpl

/**
 * Created by Zane on 15/12/18.
 * 将view加载的过程写在抽象类,做到代码复用。
 */
public abstract class BaseViewImpl implements IView{

    protected View view;
    protected final SparseArray<View> mViews = new SparseArray<View>();

 //根据activity传递过来的参数和具体view类传递过来的id生成根视图。
    @Override
    final public void creatView(LayoutInflater inflater, ViewGroup parent) {
        int resourceId = getRootViewId();

        if (resourceId == 0){
            throw new RuntimeException("rootview's id can't be null");
        }

        view = inflater.inflate(resourceId, parent, false);
    }

 //这就是baseactivitypresenter中调用用来渲染试图的方法。
    @Override
    final public View getRootView() {
        return view;
    }
    //由子类去重写
    @Override
    public abstract int getRootViewId();

    //添加注解view方式
    @Override
    final public void initView() {
        ButterKnife.bind(this, view);
    }

    @Override
    final public void removeView() {
        ButterKnife.unbind(this);
    }

BaseListViewHolder

这个是可以和baseviewimpl对应的,是一个viewholder的抽象父类。用的同样的方法通过中间层做到view holder和adapter解耦。区别不是太大!

EasyMVP2.png
/**
 * Created by Zane on 15/12/18.
 * 这个中间的base层用来做到viewholder与adapter的解耦。
 */
public abstract class BaseListViewHolderImpl<M extends Object> extends RecyclerView.ViewHolder{

    public BaseListViewHolderImpl(View itemView) {
        super(itemView);
    }

    //生成viewholder的构造方法。
    public BaseListViewHolderImpl(ViewGroup parent, @LayoutRes int res){
        super(LayoutInflater.from(parent.getContext()).inflate(res, parent, false));
    }

    public abstract void initView();

    public abstract void setData(M data);

    protected <T extends View> T $(@IdRes int id) {
        return (T) itemView.findViewById(id);
    }

}
我继承了recycle view的view holder,并且暴露出来一个构造函数,相当于代理层的构造函数,然后函数里面调用了recycle view中viewholder的构造方法,以此来生成最终的view holder。

而M则是recycle view展示的model的数据类型。以此保证view holder和adapter数据类型的一致。方便下面的操作。

setData()方法的设计有点像接口回调,在holder的子类里面实现这个函数,在adapter里面进行回调。

BaseListAdapterPresenter

我的这个框架的启蒙文章里面也说,适配器是可以作为presenter的。这个抽象类的主要任务就是调用具体holder子类的构造函数,并且传递数据给setData()方法,形成接口回调。
/**
 * Created by Zane on 15/12/18.
 */
public abstract class BaseListAdapterPresenter<M extends Object> extends RecyclerView.Adapter<BaseListViewHolderImpl>{

    protected Context mContext;
    protected List<M> mDatas;

    public BaseListAdapterPresenter(Context mContext){
        this(mContext, null);
    }

  //根据具体adapter子类构造函数获得的数据去初始化这里的数据。
    public BaseListAdapterPresenter(Context mContext, List<M> mDatas){
        this.mContext = mContext;
        this.mDatas = mDatas;
    }

    @Override
    public BaseListViewHolderImpl onCreateViewHolder(ViewGroup parent, int viewType) {

        return OnCreatViewHolder(parent, viewType);

    }

  //给具体adapter子类去实现,在子类里面调用具体的holder构造函数
    public abstract BaseListViewHolderImpl OnCreatViewHolder(ViewGroup parent, int viewType);


    public M getItem(int position){
        return mDatas.get(position);
    }

    @Override
    public int getItemCount() {
        return mDatas.size();
    }
}

问题

刚开始这样写完了之后,发现一个问题就是presenter里面难免会需要view层的控件。比如点击一个button实现页面跳转,首先跳转功能当然要写在presenter,这就是跟传统mvp不同的一点。然后我就在想如果把view的控件一个个的暴露给presenter,但是如果需求量一大后果不堪设想。。然后我就看到那个大牛的框架用一个方法解决了这个问题:

    protected final SparseArray<View> mViews = new SparseArray<View>();

 final public <T extends View> T bindView(int id) {
         T view2 = (T) mViews.get(id);
        if (view2 == null) {
            view2 = $(id);
            mViews.put(id, view2);
        }
        return view2;
    }

    final public <T extends View> T get(int id) {
        return (T) bindView(id);
    }

    final protected <T extends View> T $(@IdRes int id) {
        return (T) view.findViewById(id);
    }

 //暴露监听函数
    final public void setOnClickListener(View.OnClickListener listener, int... ids) {
        if (ids == null) {
            return;
        }
        for (int id : ids) {
            get(id).setOnClickListener(listener);
        }
    }
他用了一个可变长度的数组来存储控件。首先你可以用get(int id)方法或者$(int id)方法(建议就用get(),因为不必重复去findview)去在presenter中获得控件,但是不到万不得已别这样做,不然就破坏了mvp的设计模式。再就是很佩服他的setOnClickListener(View.OnClickListener listener, int... ids)方法。完美的解决了在presenter不获得控件还能完成监听这个问题。

使用

简单demo:

/**
 * Created by Zane on 16/1/27.
 */
public class MainView2 extends BaseViewImpl {

    @Bind(R.id.button)
    Button button;
    @Bind(R.id.edit)
    EditText edit;

    @Override
    public int getRootViewId() {
        return R.layout.activity_2;
    }

    public void setText(String test){
        edit.setText(test);
    }
}

这是一个view,看到了,由于框架做了butterkinfe绑定,所以直接@Bind()!然后返回自己对应的xml文件id。

/**
 * Created by Zane on 16/1/27.
 */
public class MainActivity2 extends BaseActivityPresenter<MainView2>{

    @Override
    public Class<MainView2> getRootViewClass() {
        return MainView2.class;
    }

    @Override
    public void inCreat(Bundle bundle) {

        v.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity2.this, MainActivity.class));
            }
        }, R.id.button);
    }

    @Override
    public void inDestory() {

    }
}

返回对应view的class,并且完成了一次点击button跳转页面。

/**
 * Created by Zane on 15/12/20.
 */
public class MainListViewHolder extends BaseListViewHolderImpl<data> {

    TextView mTextView;

    public MainListViewHolder(ViewGroup parent) {
        super(parent, R.layout.listview_item_layout);
        initView();
    }

    @Override
    public void initView() {
        mTextView = $(R.id.item_text);
    }

    @Override
    public void setData(data data) {
        mTextView.setText(data.getDatas());
    }
}

viewholer中不能使用butterknife默认绑定,这个问题我还没想到办法怎么解决。。不过直接用$()函数也是不错的。看到重写了父类的抽象方法,而这个函数在adapter里面被回调。

/**
 * Created by Zane on 15/12/20.
 */
public class MyRecycleviewAdapter extends BaseListAdapterPresenter<data>{

    public MyRecycleviewAdapter(Context mContext, List<data> datas){
        super(mContext, datas);
    }

    @Override
    public BaseListViewHolderImpl OnCreatViewHolder(ViewGroup parent, int viewType) {
        return new MainListViewHolder(parent);
    }

    @Override
    public void onBindViewHolder(BaseListViewHolderImpl holder, int position) {
        holder.setData(getItem(position));
    }
}

调用了父类的构造函数,返回了具体holder子类。大家会发现我并没有把holder和adapter完全解耦。对,我只是把item和adapter解耦了。使item的逻辑由自己管理,并且将holder分离出来并入view层。这块是借鉴了学长一个库的经验:项目地址:EasyRecycleView

总结

这是我第一次去尝试写一个框架出来,并且借鉴了很多别人的思想。而且实践的次数不多,算是自己迈出的第一步吧,以后肯定会遇到越来越多的问题,但是很值得。突然发现我这么一个小小的框架都需要想很多问题,那些牛逼框架能设计出来真是不容易。

未经博主同意,不得转载该篇文章

相关文章

网友评论

    本文标题:MVP开发框架的一次尝试——EasyMVP

    本文链接:https://www.haomeiwen.com/subject/nwfdrttx.html