Android MVP架构模式举例_Android中 MVP框架原理理解
2020-09-02 13:48:08  By: shinyuu

在Android项目中,按照MVC的分层,Activity和Fragment应该属于View层,用于展示UI界面,以及接收用户的输入,此外还要承担一些生命周期的工作。所以开发的时候我们经常把一些业务逻辑直接写在Activity里面,这非常直观方便,代价就是Activity会越来越臃肿,而在Activity中,把View和Controller抽离开来,而这就是MVP模式的工作了。


MVP模式的核心思想

MVP把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,Model类还是原来的Model。这就是MVP模式,现在这样的话,Activity的工作的简单了,只用来响应生命周期,其他工作都丢到Presenter中去完成。从上图可以看出,Presenter是Model和View之间的桥梁,为了让结构变得更加简单,View并不能直接对Model进行操作,这也是MVP与MVC最大的不同之处。


MVP模式的作用

  1. 分离了视图逻辑和业务逻辑,降低了耦合
  2. Activity只处理生命周期的任务,代码变得更加简洁
  3. 视图逻辑和业务逻辑分别抽象到了View和Presenter的接口中去,提高代码的可阅读性
  4. Presenter被抽象成接口,可以有多种具体的实现,所以方便进行单元测试
  5. 把业务逻辑抽到Presenter中去,避免后台线程引用着Activity导致Activity的资源无法被系统回收从而引起内存泄露和OOM


应用举例

在这里使用一个在ListView中更新适配器数据的例子来简单说明MVP模式是怎么使用的。


1.项目结构

在这里,颜色标注的即是我们用到的

android mvp 模式原理


2. model层

使用的是GsonFormat插件自动生成的bean,因此model层不需要关注bean,但由于使用了Retrofit Rxjava。

PS:使用Retrofit是因为可以很简单处理异步请求,而使用Rxjava是因为可以将被观察者跟观察者的调度任务自动切换io跟ui线程。 因此,在model层,只需要关心manager怎么组织即可。

public class DataManager {
    private RetrofitService mRetrofitService;

    public DataManager(){
        this.mRetrofitService = RetrofitHelper.getInstance()
                .getRetrofit("http://api.douban.com/v2/").getServer();
    }

    public Observable<Douban> getDoubanInfo(){
        return mRetrofitService.getInTheaters();
    }
}


3. present层

present就是用来处理所有业务逻辑工作的代理层,假设我们在Activity/Fragment中可以把工作丢给present来处理,那会有哪些方法呢?


更新上映电影的数据

提供页面跳转时的方法 于是就有了IDoubanPresenter这个接口

public interface IDoubanPresenter {
    void updateInTheaters();
    void reachDetail();
}


那么它的实现则为

public class DoubanPresenter implements IDoubanPresenter {

    private DataManager dataManager;
    private CompositeSubscription mCompositeSubscription;
    private IDoubanView mIDoubanView;
    private int index;
    public Douban mDouban;

    public DoubanPresenter (IDoubanView view){
        this.mIDoubanView = view;
        dataManager = new DataManager();
        mCompositeSubscription = new CompositeSubscription();
    }

    public void onStop() {
        if (mCompositeSubscription.hasSubscriptions()){
            mCompositeSubscription.unsubscribe();
        }
    }

    @Override
    public void updateInTheaters(){
        mCompositeSubscription.add(dataManager.getDoubanInfo()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Douban>(){
                    @Override
                    public void onCompleted() {
                        if (mDouban != null){
                            mIDoubanView.updateInTheaters(mDouban);
                        }
                    }

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

                    @Override
                    public void onNext(Douban douban) {
                        mDouban = douban;
                    }
                })
        );
    }

    @Override
    public void reachDetail(){
        mCompositeSubscription.add(dataManager.getDoubanInfo()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Douban>(){
                    @Override
                    public void onCompleted() {
                        if (mDouban != null){
                            mIDoubanView.enterDetail(mDouban, index);
                        }
                    }

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

                    @Override
                    public void onNext(Douban douban) {
                        mDouban = douban;
                    }
                })
        );
    }

}


在Present中可以看到,我们持有dataManager跟view的实例,因为业务逻辑都在这里处理,dataManager用来处理数据很好理解,view在这里不做真正的操作,只是执行一个接口方法,剩下的在接口实现里完成。


4. view层

view层其实就是我们的Activity或者Fragment。在使用List更新豆瓣接口数据,UI操作需要的方法有

  1. 更新上映电影的View
  2. 页面跳转时的方法
  3. 异常时怎么显示 于是就有了IDoubanView这个接口
public interface IDoubanView {
    void updateInTheaters(Douban mDouban);
    void enterDetail(Douban mDouban, int index);
    void onError(String result);
}


然后我们在Fragment中实现这个接口

public class DoubanFragment extends Fragment implements IDoubanView {

    private ListView listView;
    private DoubanItemAdapter mAdapter;
    private DoubanPresenter mDoubanPresenter;

    @Nullable
    @Override
    public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.card_fragment, container, false);
        listView = view.findViewById(R.id.card_listview);
        mDoubanPresenter = new DoubanPresenter(this);
        mDoubanPresenter.updateInTheaters();
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                mDoubanPresenter.reachDetail();
            }
        });
        return view;
    }


    @Override
    public void updateInTheaters(Douban mDouban) {
        mAdapter = new DoubanItemAdapter(getActivity(), mDouban.getSubjects());
        listView.setAdapter(mAdapter);
        mAdapter.notifyDataSetChanged();
    }

    @Override
    public void enterDetail(Douban mDouban, int index) {
        Intent mIntent = new Intent(getActivity(), WebActivity.class);
        mIntent.putExtra("url", mDouban.getSubjects().get(index).getAlt());
        startActivity(mIntent);
    }

    @Override
    public void onError(String result) {

    }
}


其中updateInTheaters跟enterDetail这两个接口方法由prisent调用实现。所以这个链路变成Activity/Fragment(移交任务) -> Present(数据处理) -> Activity/Fragment(渲染view)。


快速评论


技术评论

DD记账
top
+