Android基础架构之MVP模型

最近工作上碰到了一个Android项目需要重构,因为之前的项目是MV模式,UI和业务混杂在一起,加上最初开发这个项目的程序员离职,这个项目已经处于维护十分困难的情况,最终选择重构。考虑到本项目属于业务复杂度中等的项目,而且希望新的项目具有鲜明的UI-业务-数据层次以方便维护,而MVP架构十分适合中大型项目,所以趁这个机会再次熟悉了一下MVP架构的搭建,下面说说我的做法。

我希望M、P层是不与Activity UI体系有关系的纯java代码,与AndroidUI有关的逻辑交给V层处理,出于这些考虑,我提供了了MVP三个层次的BASE抽象类(接口):

public interface BaseView {
    void showloading();
    void hideloading();
}

V层的具体实现需要实现网络取数据时的loading图的业务逻辑,当然你们有其它的业务需要所有v都实现,也可以写在BaseView里

public abstract class BasePresenter<V extends BaseView> {
    public V view;

    public void attachView(V view) {
        this.view = view;
    }

    public V getView() {
        return view;
    }

    protected boolean isAttach() {
        return null != view;
    }

    public void onDettach() {
        if (null != view) {
            view = null;
        }
    }

    abstract protected void start();
}

我希望P层和V层是一一绑定的,这样的好处在于业务逻辑会很清晰,比如我想知道某个页面有哪些业务,我不需要从ui看起,直接找这个V层绑定的P就可以找到全部的业务,如果代码比较规范或者注释比较到位,我们甚至可以直接知道各个业务的前后关系,这样维护起来即使没有接触过这个项目的人也很快可以上手。
那么在来说说我们的BasePresenter,这个抽象类主要使用泛型实现了绑定/解绑V层,注意如果Activity/Fragment已经destory而P层未解绑的话是会内存泄漏的。另外,提供了start抽象方法供V层在创建时就请求数据,典型的业务场景:首页创建时就请求首页数据来展示。

public interface BaseModel {
    void getRemoteData(String url, String content);

    void getRemoteData(String url, String content, int type);

    // void getLocalData();

    void setHttplistner(Httplistener httplistener);
}

BaseModel就十分简单啦,实现数据请求即可,setHttplistner方法设置数据请求的回调供P层使用。

下面是MVP这个三个基类的实现:
V层的主要载体是Acitivity和Fragment,因此需要实现BaseView接口,它同时也需要绑定P层,所以也需要实现BasePresenter:

public abstract class BaseActivity<P extends BasePresenter> extends AppCompatActivity implements BaseView {
    public P presenter;
    protected Context mContext;
    private CustomDialog dialog;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayoutId());
        mContext = this;
        presenter = getT(this, 0);
        if (presenter != null) {
            presenter.attachView(this);
            presenter.start();
        }
        initView();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (Httphelper.handler != null) {
            Httphelper.handler.removeCallbacksAndMessages(null);
        }
        if (presenter != null) {
            presenter.onDettach();
        }
    }

    /**
     * 绑定activity的layout
     */
    public abstract int getLayoutId();

    /**
     * 初始化页面的实现
     */
    public abstract void initView();

    private <T> T getT(Object o, int i) {
        try {
            return ((Class<T>) ((ParameterizedType) (o.getClass().getGenericSuperclass())).getActualTypeArguments()[i]).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void showloading() {
        if (dialog == null) {
            dialog = CustomDialog.instance(this);
            dialog.setCancelable(false);
            dialog.show();
        } else {
            dialog.show();
        }
    }

    @Override
    public void hideloading() {
        if (dialog != null) {
            dialog.dismiss();
            dialog = null;
        }
    }
}
public abstract class BaseFragment<P extends BasePresenter> extends Fragment implements BaseView {
    protected View mRootView;
    public P presenter;
    private CustomDialog dialog;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        mRootView = inflater.inflate(getContentViewId(), container, false);
        presenter = getT(this, 0);
        if (presenter != null) {
            presenter.attachView(this);
            presenter.start();
        }
        initAllMembersView(savedInstanceState);
        return mRootView;
    }

    @Override
    public void onDetach() {
        super.onDetach();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (presenter != null) {
            presenter.onDettach();
        }
    }

    /**
     * 绑定Fragment Layout Id
     */
    public abstract int getContentViewId();

    /**
     * 初始化Fragment UI
     *
     * @param savedInstanceState
     */
    protected abstract void initAllMembersView(Bundle savedInstanceState);

    private <T> T getT(Object o, int i) {
        try {
            return ((Class<T>) ((ParameterizedType) (o.getClass().getGenericSuperclass())).getActualTypeArguments()[i]).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void showloading() {
        if (dialog == null) {
            dialog = CustomDialog.instance(getActivity());
            dialog.setCancelable(false);
            dialog.show();
        } else {
            dialog.show();
        }
    }

    @Override
    public void hideloading() {
        if (dialog != null) {
            dialog.dismiss();
            dialog = null;
        }
    }
}

BaseActivity和BaseFragment要做的事情相似,利用泛型获取P层具体的实现对象,然后绑定这个对象,showloading、hideloading方法实现loading页面的展示/隐藏,再次说明,生命周期destory中要解绑presenter,否则会导致内存泄漏。

现在我们的MVP基类都准备好了,那么具体怎么使用呢,以一个项目的首页为例:
首先首页肯定需要加载首页内容,再加一个,首页需要检查更新。
那么我们的P层就需要提供这些业务:

public class HomePresenter extends BasePresenter<HomeView> {

    @Override
    protected void start() {
        if (!isAttach()) {
            return;
        }
        getIndexData();
        getVersionInfo();
    }

    public void getIndexData() {
        if (!isAttach()) {
            return;
        }
        BaseModel model = new HomePresenterModel();
        model.setHttplistner(new Httplistener<HomeBean>() {

            @Override
            public void onFail(int status, String message) {
                if (!isAttach()) {
                    return;
                }
                view.onHttpfail(status, message);
            }

            @Override
            public void onSuccess(HomeBean bean) {
                if (!isAttach()) {
                    return;
                }
                view.onHttpsuccess(bean);
            }

            @Override
            public void onError() {
                if (!isAttach()) {
                    return;
                }
                view.onHttpError();
            }
        });
        String url = "XXXXXXXX";//首页数据的地址,这里是网络加载
        model.getRemoteData(url, "", HomePresenterModel.GETDATE);
    }
    
    public void getVersionInfo() {
        if (!isAttach()) {
            return;
        }
        String app_version = "x.x.x";//这里获取版本号
        JsonObject json = new JsonObject();
        json.addProperty("app_version", app_version);

        BaseModel model = new HomePresenterModel();
        model.setHttplistner(new Httplistener<GetVersionInfoBean>() {
            @Override
            public void onFail(int status, String message) {
                if (!isAttach()) {
                    return;
                }
                view.getVersionInfoFail(status, message);
            }

            @Override
            public void onSuccess(GetVersionInfoBean bean) {
                if (!isAttach()) {
                    return;
                }
                view.getVersionInfoSuccess(bean);
            }

            @Override
            public void onError() {
                if (!isAttach()) {
                    return;
                }
                view.getVersionInfoError();
            }
        });
        String url = "XXXXXXXX";//检查更新的地址,这里是网络加载
        model.getRemoteData(url, json.toString());
    }
}

说明一下,每个业务方法都需要调用一下isAttach方法来判断一下View是否绑定了Presenter,因为View层只要没有绑定,业务层就不需要对View层进行数据回调,还有另一种情况,在View层发起数据请求后随即destory,而Presenter层因为网络延迟还没有返回数据,所以等到数据回调的时候会发生因为View层已经解绑导致view对象为空的错误,所以数据回调后同样也需要用isAttach判断一下。另外,我是采用Gson进行json的解析,当然也可以用其它的工具,Gson组装了请求数据提供给Model层使用。设置了回调把数据返回给View层,所以View层应该提供这些回调方法:

public interface HomeView extends BaseView {
    void onHttpsuccess(HomeBean bean);

    void onHttpfail(int status, String message);

    void onHttpError();

    void getVersionInfoFail(int status, String message);

    void getVersionInfoSuccess(GetVersionInfoBean bean);

    void getVersionInfoError();
}

Model层的具体实现:

class HomePresenterModel implements BaseModel {
    public static final int GETDATE = 1;
    private Httplistener httplistener;

    @Override
    public void getRemoteData(String url, String content) {
        new Httphelper(httplistener, GetVersionInfoBean.class).getAsynHttp(url, content);
    }

    @Override
    public void getRemoteData(String url, String content, int type) {
        switch (type) {
            case GETDATE:
                new Httphelper(httplistener, HomeBean.class).getAsynHttp(url, content);
                break;
        }
    }

    @Override
    public Object getLocalData() {
        return null;
    }

    @Override
    public void setHttplistner(Httplistener httplistener) {
        this.httplistener = httplistener;
    }
}

model层要做的事情就是获取数据,可以是本地的也可以是网络上的,我封装了一个httphelper来处理,封装了Javabean来保存Gson解析出的数据,方便View层使用。

MVP的具体实现有了,现在要在android的ui上使用:

public class HomeActivity extends BaseActivity<HomePresenter> implements HomeView {
    @Override
    public int getLayoutId() {
        return R.layout.xxxx;//绑定xml
    }

    @Override
    public void initView() {
        //view初始化
    }
    
    @Override
    public void getVersionInfoFail(int status, String message) {

    }

    @Override
    public void getVersionInfoSuccess(GetVersionInfoBean bean) {
        //对返回的数据进行处理
    }
    
    @Override
    public void getVersionInfoError() {

    }

    @Override
    public void onHttpsuccess(HomeBean bean) {
        //请求首页数据成功后的处理
    }

    @Override
    public void onHttpfail(int status, String message) {
        hideloading();
        new MsgAlert().show(message);
    }

    @Override
    public void onHttpError() {
        hideloading();
    }
}

首页Activity具体实现了View接口,这样数据和UI就实现了分离,做UI的程序员可以专注于UI的实现,尤其是在开发一些非常复杂的页面的时候,可以多个程序员开发不同的层次,使得同一个复杂功能可以并行开发,大大减少开发周期。
需要说明的是,这个例子中的presenter层的业务都是在presenter的start中进行的,在实际的开发中,View层当然也可以直接调用presenter的业务方法来请求数据,这也是MVP架构所希望的:View层、Model层只和Presenter层交互,VM不直接进行交互。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值