Android:MVP架构的简单使用

分享一个简单的MVP架构,适合初学者,这个MVP框架,没有那么严格。前两年刚开始步入工作的阶段看到了他们代码架构,就学会了,比较明了。不过这个框架有个缺点model是没有的,或者不够明显,直接在Presenter中使用网络框架获取数据,我会稍微修改一下这个框架,不过在代码上就不够结构化,有需要的小伙伴自己优化一下。想通过MVP的思想,从而引出后面的MVVM,也算对自己所学,整理一下。这些都是属于自己的理解,有错误的地方欢迎指正。谷歌官方也有发布一套MVP架构Demo,也可以使用它的,更加的严谨。

概念:M->Model 数据提供者     V->View 界面显示者     P->Presenter 控制和M与V的数据流通中枢。用户刚开始看到的界面View(activity或者fragment)通过点击或者刚开始初始化,调用Presenter的方法,Presenter对界面传过来的数据处理一下或者直接给Model调取获取数据的方法,Presenter获取到数据后,再使用View的回调将数据给到View层。

 

为什么要使用MVP架构?

1.普通不使用架构的代码找代码很麻烦,一个页面,比如表单填报东西以及选择项,可能会有五六个网络接口,每个网络接口设置几个TextView显示。网络框架一般都会有个成功回调,失败回调,再加上本身其自己的代码量,确实比较臃肿。使用了MVP的话,对于View(activity或者fragment)层来说,他的代码就只包含基础的onCreate之类,以及界面点击事件,调取Presenter的方法,和数据回调。其中界面显示就在数据回调中操作

2.解耦,每个模块负责各自的功能,在修改或者查找bug的时候能很快定位。比方说,某些选择项十来个,是本地代码写死的。现在要改成从后台获取,在数据结构不变的情况下,只需要修改Model层,把写死的数据删除,改成后台接口。即使数据结构和后台不一致,也可以再修改Presenter层将数据转化一下。

3.多人工作的可实现性。比如有人负责就对接口,写Model层;有人就写界面,并且定义好数据回调;再有一人可以负责Presenter层。虽然,,,,都是我一个人,哈哈哈,这是我想象的。

 

如何使用?其实MVP原理比较的简单,从代码难度上看,是对java基础的使用。

首先定义一个数据回调接口类,而View(activity或者fragment)实现这个接口类,在Presenter中持有接口类实体以及Model数据获取类的实体。从View层触发Presenter的获取数据的方法,从而调用了Model的网络接口或者本地数据,Presenter在网络接口回调中,调用View接口类实体的方法,从而改变界面。看下面代码

//这里简单的手打一下架构的实现方式,具体的后面架构应该怎么写后面再说,这里非常的简单,
//理解一下就行了,也不是通用的框架。


//View层需要的接口定义
public interface ITestView {
    void getListSuccess(List<Data> dataList);
    void getFail();
}

//我就直接写我的网络框架了,这不是重点
public class TestModel {
    private RetrofitApi api = RetrofitUtil.getApi();
    public Observable<List<Data>> getDataList() {
        return api.getDataList().compose(new ObjTransform<>(null));
    }
}


//Presenter层
public class TestPresenter {

    private ITestView viewImp;//接口的实体类
    private TestModel model;

    public TestPresenter() {
        model = new TestModel;
    }

    public void setView(ITestView view) {
        viewImp = view;
    }
    public void getData() {
        model.subscribe(new Observer<List<Data>>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                        //这里一般是让Presenter跟随activity的生命周期
                        //假如activity销毁了,就解绑
                    }

                    @Override
                    public void onNext(List<Data> value) {
                        viewImp.getListSuccess(value);
                    }

                    @Override
                    public void onError(Throwable e) {
                        viewImp.getFail();
                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }
}


//View层,就用个activity来示意吧
public class TestActivity extend AppCompatActivity implements ITestView {

    private TestPresenter mPresenter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        mPresenter = new TestPresenter();
        mPresenter.setView(this);
        //调用Presenter获取数据
        mPresenter.getData();
    }
    
    @Override
    public void getListSuccess(List<Data> dataList) {
        //这里可以设置界面显示,比如把数据设置到recyclerView上啊,或者取第几个显示之类的
    }

    @Override
    public void getFail() {
        //这里获取数据失败了,可以停止刷新之类的
    }

}

基本手打,跑不起来我不负责任啊,就如上面的代码,分成几个模块:接口定义模块ITestView,Model模块TestModel,Presenter模块TestPresenter,View模块TestActivity。非常简单,现在MVP原理基本就是上面这样,如何实现,以及架构化,通用化,就看后面的封装了。

从代码可以看出,View层是不关心这数据从哪里来的,是从后台获取的,本地数据库中的,还是自己写的假数据,它都不在意。我们只需要实现数据过来时,我们应该做出什么操作就行了。

对于Model层,其实假如都是根据后台获取数据的,或者本地获取数据的,不经常改动接口,model层有时候不写,这的确不太严谨,不过能节省一些时间,并且代码结构看着差别不大,比如Presenter中调取Model的方法,也可以直接写成:

RetrofitUtil.getApi().getDataList().compose(new ObjTransform<>(null))
    .subscribe(new Observer() {});

Presenter层则是不可缺少的,虽然上面代码很少,但是实际使用中,六七个网络接口的实现,以及数据处理(说几种情况,比如后台存的84坐标系,而我们使用高德腾讯地图,需要坐标系转换一下,比如后台视频列表,获取第一帧图片显示,再比如数据结构的转换)它是Model和View的控制器,通过在数据获取的回调中,调取view的方法,从而改变界面显示。

 

比方说这个时候,后台接口变了,这时候你就改model层就行了;假如说数据客户端自己处理一下,把中间某些数据删除,那么改动Presenter层就行了;假如说,产品不满意,需要在界面上再增加几个字段,或者自定义一下控件,那么你改动View层就好了。这是我认为MVP的好处,解耦以及结构化。

 

那么再来看看这个通用架构怎么写,Model就不说了,数据获取类,在Model基类中可以放网络框架的实体,比如上面的api,也可以放入SharedPreferences插入数据和获取数据。

Presenter:

/**
 * @desc  BasePresenter
 */

public abstract class BasePresenter<V> {

    protected Reference<V> mViewRef;
    protected V viewImp;

    public BasePresenter() {

    }

    public void attachView(V view){
        mViewRef = new WeakReference<V>(view);
        viewImp = getView();
    }

    protected V getView(){
        return mViewRef.get();
    }

    public boolean isViewAttached(){
        return mViewRef != null&&mViewRef.get()!=null;
    }

    public void detachView() {
        if(mViewRef!=null) {
            mViewRef.clear();
            mViewRef = null;
        }
    }

}

如上图,也没什么代码量,attachView和之前写的setView作用相同,使用了弱引用,防止过久持有activity无法回收。

再看看View层:

import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import com.xx.utils.DialogUtil;

import butterknife.ButterKnife;
import pub.devrel.easypermissions.EasyPermissions;


public abstract class BaseActivity<V, T extends BasePresenter<V>> extends AppCompatActivity {


    protected T mPresenter;
    private DialogUtil mDialogWaiting;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        init();
        //判断是否使用MVP模式
        mPresenter = createPresenter();
        if (mPresenter != null) {
            mPresenter.attachView((V) this);//因为之后所有的子类都要实现对应的View接口
        }

        //子类不再需要设置布局ID,也不再需要使用ButterKnife.bind()
        if (!needDataBinding()) {
            setContentView(provideContentViewId());
        }
        ButterKnife.bind(this);

        initByBundle(savedInstanceState);
        initView();
        initData();
        initListener();
    }

    protected void initByBundle(Bundle savedInstanceState) {

    }

    protected boolean needDataBinding() {
        return false;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mPresenter != null) {
            mPresenter.detachView();
        }
    }

    protected void init() {
    }

    protected void initView() {
    }

    protected void initData() {
    }

    protected void initListener() {
    }

    //用于创建Presenter和判断是否使用MVP模式(由子类实现)
    protected abstract T createPresenter();

    //得到当前界面的布局文件id(由子类实现)
    protected abstract int provideContentViewId();

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }

    /**
     * 显示等待提示框
     */
    protected void showWaitingDialog() {
        DialogUtil.showWaittingDialog(this);
    }

    /**
     * 隐藏等待提示框
     */
    protected void hideWaitingDialog() {
        DialogUtil.closeWaittingDialog();
    }

}
public abstract class BaseFragment<V, T extends BasePresenter<V>> extends Fragment {

    protected T mPresenter;
    protected boolean alive = false;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        init();

        //判断是否使用MVP模式
        mPresenter = createPresenter();
        if (mPresenter != null) {
            mPresenter.attachView((V) this);//因为之后所有的子类都要实现对应的View接口
        }
        alive = true;
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View rootView = inflater.inflate(provideContentViewId(), container, false);
        ButterKnife.bind(this, rootView);
        initView(rootView);
        initByBundle(savedInstanceState);
        return rootView;
    }

    protected void initByBundle(Bundle savedInstanceState) {

    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        initData();
        initListener();
    }

    public void showWaitDialog() {
        DialogUtil.showWaittingDialog(getContext());
    }


    /**
     * 隐藏等待提示框
     */
    protected void hideWaitingDialog() {
        DialogUtil.closeWaittingDialog();
    }

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

    @Override
    public void onDestroyView() {
        alive = false;
        super.onDestroyView();
        ButterKnife.unbind(this);
    }

    protected void init() {

    }

    protected void initView(View rootView) {
    }

    protected void initData() {

    }

    protected void initListener() {

    }


    //用于创建Presenter和判断是否使用MVP模式(由子类实现)
    protected abstract T createPresenter();

    //得到当前界面的布局文件id(由子类实现)
    protected abstract int provideContentViewId();
}

 

写一个抽象基类BaseActivity,泛型指定的是,接口定义类IView和Presenter类,有重写的那些initData和initListener就不再赘述了,展示等待弹窗和隐藏的话,可以按照自己的需求写。

定义了一个mPresenter实体,从onCreate看,mPresenter被抽象方法赋值,不为空的话绑定当前的View,需要实现该泛型接口定义类。再往下还用了ButterKnife,比较简单的架构,看看实际使用中是什么样子的:

放几个不暴露公司业务的代码,界面是一个关键字输入框,一个取消关键字按钮,一个列表,列表项如下图拥有选择功能,可以多选

 

功能是一个页面能选择一些列表项并返回给前页,实现方法用的是startActivityForResult:

先是view的接口定义类,只定义了一个获取列表数据的方法,写了个BaseView,用来放置Toast提醒

public interface BaseView {
    void showToast(String str);
}


import java.util.List;

public interface IChooseCompanyView extends BaseView {
    void getListSuccess(List<IdAndName> companyList);
}

然后是Presenter层:

public class ChooseCompanyPresenter extends BasePresenter<IChooseCompanyView> {
    private String keyword;
    private ArrayList<IdAndName> chooseCompany = new ArrayList<>();

    public ArrayList<IdAndName> getChooseCompany() {
        return chooseCompany;
    }

    public void setChooseCompany(ArrayList<IdAndName> chooseCompany) {
        this.chooseCompany = chooseCompany;
    }

    public boolean isChooseCompany(IdAndName data) {
        for (IdAndName pollutantData : chooseCompany) {
            if (pollutantData.id == data.id) {
                return true;
            }
        }
        return false;
    }


    public void removeChoose(IdAndName data) {
        int position = -1;
        for (int i = 0; i < chooseCompany.size(); i++) {
            if (chooseCompany.get(i).id == data.id) {
                position = i;
                break;
            }
        }
        if (position != -1) {
            chooseCompany.remove(position);
        }
    }

    public void addChooseData(IdAndName data) {
        chooseCompany.add(data);
    }

    public void setKeyword(String keyword) {
        this.keyword = keyword;
    }

    public void getList() {
        RetrofitUtil.getApi().getCompanyList(keyword)
                .compose(new ObjTransform<>(null))
                .subscribe(new HttpObserver<List<IdAndName>>() {
                    @Override
                    public void onNext(List<IdAndName> idAndNames) {
                        super.onNext(idAndNames);
                        viewImp.getListSuccess(idAndNames);
                    }

                    @Override
                    public void onError(Throwable t) {
                        super.onError(t);
                        viewImp.showToast(t.getMessage());
                    }
                });
    }

}

代码很简单,上面还有防止重复选择,以及去除列表项的功能,有需要的小伙伴可以看看。

再看看activity

public class ChooseCompanyActivity extends BaseActivity<IChooseCompanyView, ChooseCompanyPresenter> implements IChooseCompanyView {

    @Bind(R.id.head_view)
    StatusBarHeadView headView;
    @Bind(R.id.et_search)
    EditText etSearch;
    @Bind(R.id.tv_cancel)
    TextView tvCancel;
    @Bind(R.id.recycler_view)
    RecyclerView recyclerView;
    @Bind(R.id.bt_commit)
    Button btCommit;

    private CompanyAdapter companyAdapter;

    public static Intent instance(Context context, ArrayList<IdAndName> companyList) {
        return new Intent(context, ChooseCompanyActivity.class)
                .putExtra("companyList", companyList);
    }

    @Override
    protected void initData() {
        ArrayList<IdAndName> dataArrayList = (ArrayList<IdAndName>) getIntent().getSerializableExtra("companyList");
        if (dataArrayList != null) {
            mPresenter.setChooseCompany(dataArrayList);
        }
        AppUtils.rvLinearManager(recyclerView, this);
        companyAdapter = new CompanyAdapter();
        companyAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
                IdAndName company = companyAdapter.getItem(position);
                if (company != null) {
                    if (mPresenter.isChooseCompany(company)) {
                        mPresenter.removeChoose(company);
                    } else {
                        mPresenter.addChooseData(company);
                    }
                }
                companyAdapter.notifyDataSetChanged();
            }
        });
        recyclerView.setAdapter(companyAdapter);
        mPresenter.getList();
    }

    @Override
    protected void initListener() {
        headView.setIvLeftOnClickListening(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
        btCommit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mPresenter.getChooseCompany().size() == 0) {
                    ToastInstance.ShowText("请选择");
                    return;
                }
                Intent intent = new Intent();
                intent.putExtra("chooseCompany", mPresenter.getChooseCompany());
                setResult(Activity.RESULT_OK, intent);
                finish();
            }
        });

        etSearch.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                    mPresenter.setKeyword(etSearch.getText().toString());
                    companyAdapter.setNewData(null);
                    mPresenter.getList();
                    return true;
                }
                return false;
            }
        });
        tvCancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                etSearch.setText("");
                mPresenter.setKeyword(null);
                companyAdapter.setNewData(null);
                mPresenter.getList();
            }
        });
    }

    @Override
    public void getListSuccess(List<IdAndName> companyList) {
        companyAdapter.setNewData(companyList);
    }

    @Override
    public void showToast(String str) {
        ToastInstance.ShowText(str);
    }

    @Override
    protected ChooseCompanyPresenter createPresenter() {
        return new ChooseCompanyPresenter();
    }

    @Override
    protected int provideContentViewId() {
        return R.layout.activity_choose_company;
    }

    private class CompanyAdapter extends BaseQuickAdapter<IdAndName, BaseViewHolder> {

        public CompanyAdapter() {
            super(R.layout.item_company_choose, null);
        }

        @Override
        protected void convert(BaseViewHolder holder, IdAndName item) {
            AppCompatCheckBox checkBox = holder.getView(R.id.checkbox);
            checkBox.setChecked(mPresenter.isChooseCompany(item));
            checkBox.setText(item.name);
        }
    }

}

不过这个例子不怎么好,功能比较少,没发挥出presenter的功效,假如有多表单,多接口的地方,实际使用就会舒服很多,那些代码就不放了,过于冗长。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值