×

Google官方架构MVP解析与实战【从零开始搭建android框架系列(3)】

96
安东尼_Anthony 595a1b60 08f6 4beb 998f 2bf55e230555
2016.05.07 14:38* 字数 1564

由于对之前的项目做出了更改,所以下面内容在2016.8.10做出了更新

本文章原地址:Anthony的博客

1 前言

当然对于MVP的解说也是使用也是层出不穷,我也网络上也能看到各种版本的解说,之前博客也有文章的更新,里面有MVP的详细说明和项目代码--->Android中的MVP模式,带实例

本篇文章将参考 google官方android MVP架构项目的实现,来实现自己的项目。或许看了这篇文章之后,你再去梳理一下google官方架构项目,会让你收获更多。官方的实例肯定具有更好的权威性

推荐关注安卓各种架构相关文章合集github地址:AndroidArchitectureCollection

2 google官方MVP架构解析

1 项目目录

打开github,展开项目目录,会发现项目结构的组织方式是按照功能进行分模块的,当然根据个人情况,也可以按照ui,model,view,presenter这种情况进行划分组织目录。

google官方MVP架构目录视图

2 具体实现流程

我们将关注度放到具体的一个taskdetail模块当中来解析实现MVP的流程。

taskdetail模块

2.1 TaskDetailContract
可以看到这里是通过一个协议类XXXContract来对View和Presenter的接口进行继承。这样做的好处也就是,我们可以将基础的View层的操作放在BaseView里面,对基础的Presenter层的操作放在BasePresenter里面。减少后续代码的重复。一个协议类也将View和Presenter管理起来,方便操作。

2.2 BaseView
那么来看看BaseView,主要是有一个setPresenter的操作,MVP中Presenter和View层是需要交互的,这里通过setPresenter操作,我们也就可以获得相应的Presenter的实例在View层直接mPresenter.xxx()进行交互了。我们可以在下面的代码中看到官方示例代码是通过在TaskDetailPresenter的构造函数中调用mTaskDetailView.setPresenter(this)完成这一步操作的。

所以在我们自己的代码中,我们也可以将加载的loading,以及加载错误页面,加载失败页面等操作放在BaseView里面,这是每个View都会有的:


2.3 BasePresenter
BasePresenter中只有一个start方法,表示“开始”,我们可以在这里进行数据加载初始化等。


2.3 TaskDetailActivity
可以看到这里这里一个初始化了fragment的activity,主要操作当让是new了一个XXXPresenter。activity在项目中是一个全局的控制者,负责创建view以及presenter实例,并将二者联系起来,

2.4 TaskDetailFragment
Fragment是MVP中View的实现类,它不与Model 层进行交互,只和presenter的实例进行交互。


2.5 TaskDetailPresenter
Presenter的真正实现类,在这里进行model层和view层的交互。

通过上面的分析,在来梳理一下整个步骤:
1 官方MVP实例,通过协议类XXXContract来对View和Presenter的接口进行内部继承。是对BaseView和BasePresenter的进一步封装,所以我们实现的View和Presenter也只需要继承XXXContract中的对应内部接口就行。

2 activity的作用主要是创建View(这里是相应的fragment),以及创建presenter,并把view传递给presenter(完成presenter对view实例关联操作)

3 在presenter的实现类的构造函数中,通过view的setPresenter,让view获得了presenter实例。这样view中就可以对Presenter中的方法进行操作了。(完成view对presenter实例关联操作)

4 在presenter的实现类中,可以对Model数据进行操作。实例中,数据的获取、存储、数据状态变化都是model层的任务,presenter会根据需要调用该层的数据处理逻辑并在需要时将回调传入。这样model、presenter、view都只处理各自的任务,此种实现确实是单一职责最好的诠释。

3 实战应用:

说了这么多,通过一个一个最为简单的spalsh页面的搭建,来完整的使用MVP吧。

3.1 BaseView
我在这里没有添加setPresenter方法,而是将loading,以及加载错误,网络加载错误等页面都放在了这里面。

/**
 * Created by Anthony on 2016/5/3.
 * Class Note:
 * interface for MVP View in all of the project
 */
public interface BaseView {

    void showMessage(String msg);

    void close();

    void showProgress(String msg);

    void showProgress(String msg, int progress);

    void hideProgress();

    void showErrorMessage(String msg,String content);
}

3.2 BasePresenter
在我的项目中,我采用的是在BasePresenter中获取View的实例,也就是通过下面的attachView方法完成了View和Presenter的关联性。

public interface BasePresenter<T extends BaseView> {
    void attachView(T view );
    void detachView();
}

这个方法会在相应的Presenter的实现类里面得到实现。而我们需要通过在相应的View类里面通过mPresenter.attachView(this)进行初始化关联,这样就可以在Presenter的实例中使用View的实例。那么上面这个mPresenter实例我是怎么在View的实现类中得到的呢? 答案是使用Dagger2的Inject获取。可以参考我的文章Google官方MVP+Dagger2架构详解 进行Dagger2的学习。

@Inject
SplashPresenter mPresenter;

3.3 SplashContract
同样的我们也将采用契约类来完成Presenter和View接口的展现。这里Presenter主要是完成加载数据(在splash页面中加载数据是应用普遍应当考虑的一种方式。)

/**
 * Created by Anthony on 2016/5/31.
 * Class Note:
 * contract class for splash view & presenter
 */
public interface SplashContract {
    interface Presenter extends BasePresenter<View> {
        void initData();
    }

    interface View extends BaseView {
        void toMainActivity();
    }
}

3.4 SplashContract.View的实现SplashActivity
官方示例代码采用的方式是fragment作为View的实现,这里灵活变通,采用Activity作为MVP中View的实现。这里继承自AbsBaseActivity其中完成了一些初始化操作,将会在另外的文章中进行讲解。这里我们就采用Dagger2进行了SplashPresenter的实例的获取。也实现了toMainActivity方法,用于跳转到主页面。

/**
 * Created by Anthony on 2016/5/31.
 * Class Note:
 * this class is simple but indicates how to use MVP in your project
 *
 * implements of splash view
 */
public class SplashActivity extends AbsBaseActivity implements SplashContract.View {

    @Inject
    SplashPresenter mPresenter;


    @Override
    public void toMainActivity() {
        Intent intent = new Intent(this, MainActivity.class);
        startActivity(intent);
    }


    @Override
    protected void initViewsAndEvents() {
        mPresenter.attachView(this);
        mPresenter.initData();
    }

    @Override
    protected int getContentViewID() {
        return R.layout.activity_splash;
    }

    @Override
    protected void injectDagger(ActivityComponent activityComponent) {
        activityComponent.inject(this);
    }

}

3.5 SplashContract.Presenter 的实现SplashPresenter
这里对Presenter的实现类是SplashPresenter,我们需要实现的方法自然有三个attachView,detachView,以及initData三个方法。

/**
 * Created by Anthony on 2016/5/31.
 * Class Note:
 * presenter for splash view
 */
public class SplashPresenter implements SplashContract.Presenter {

    private static final short SPLASH_SHOW_SECONDS = 1;
    private long mShowMainTime;

    private SplashContract.View mView;
    private Context mContext;
    private Subscription mSubscription;

    @Inject
    ToastUtils mToastUtil;

    @Inject
    DataManager mDataManager;


    private MyApplication mApplication;

    @Inject
    public SplashPresenter(@ActivityContext Context context, MyApplication application) {
        mContext = context;
        this.mApplication = application;
    }


    @Override
    public void initData() {
        mShowMainTime = System.currentTimeMillis() + SPLASH_SHOW_SECONDS * 2000;


        //load channel list data ,then save to database
        mSubscription = mDataManager.loadChannelList(Constants.FIRST_MENU_URL)
                .doOnNext(mDataManager.saveChannelListToDb)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new HttpSubscriber<List<Channel>>() {
                    @Override
                    public void onNext(List<Channel> channels) {
//                        mApplication.channels = channels;//load to global instance
                        showView();
                    }

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

    /**
     * menu url to load channels
     *
     * @return
     */
//    private String getFirstMenuUrl() {
//        return "raw://news_menu";  //local data fot testing
//    }

    private void showView() {
        AsyncTask<String, String, String> showMainTask = new AsyncTask<String, String, String>() {
            @Override
            protected String doInBackground(String[] params) {
                if (System.currentTimeMillis() < mShowMainTime) {
                    try {
                        long sleepTime = mShowMainTime - System.currentTimeMillis();
                        if (sleepTime > 0) {
                            Thread.sleep(mShowMainTime - System.currentTimeMillis());
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                return null;
            }

            @Override
            protected void onPostExecute(String o) {
                mView.toMainActivity();
                mView.close();
            }
        };

        showMainTask.execute();
    }


    @Override
    public void attachView(SplashContract.View view) {
        mView = view;
    }

    @Override
    public void detachView() {
        mView = null;
        if (mSubscription != null && !mSubscription.isUnsubscribed()) {
            mSubscription.unsubscribe();
        }
    }


}

这里需要关注的点:我们在attackView中完成了view的实例获取mView,以及在需要解除绑定的时候使用detachView方法。这样我们就可以在当前Presenter的全局中使用View的实例了。

    @Override
    public void attachView(SplashContract.View view) {
        mView = view;
    }

    @Override
    public void detachView() {
        mView = null;
        if (mSubscription != null && !mSubscription.isUnsubscribed()) {
            mSubscription.unsubscribe();
        }
    }

注意点:
1 细心的你可能会发现,那么上面的所有BaseView的方法在哪里实现呢?
答案是所有Activity都需要继承的AbsBaseActivity中


这样我们就可以让所有activity继承AbsBaseActivity,他们都是实现了BaseView的所有方法的状态。这样就提供了统一的界面化处理而且减去了许多重复的代码。
2 上面无论是官方的代码还是我们自己的代码,由于使用MVP模式,都会降低模块之间的耦合性。所以这时候能够正确的关联View,Presenter和Model层之间的数据显得尤为的重要。
3 对于Model层的数据,我们可以看到我是在initData方法中调用DataManger的loadChannelList进行数据的加载的。关于Model层这方面的内容,已经在文章浅析MVP中model层设计 进行了分析,不再赘述。

4 参考资料:

Android官方MVP架构示例项目解析
AndroidArchitectureCollection

Web note ad 1