google官方架構MVP解析與實戰【從零開始搭建android框架系列(3)】

google官方架構MVP解析與實戰【從零開始搭建android框架系列(3)】

字數2387 閱讀14518 評論20 

更多及時技術資訊,歡迎關注我的微博 :Anthony

由於對之前的項目做出了更改,所以下面內容在2016.8.10做出了更新

本篇文章項目github地址:MVPCommon
本文章原地址: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層的交互。


通過上面的分析,在來梳理一下整個步驟:
官方MVP實例,通過協議類XXXContract來對View和Presenter的接口進行內部繼承。是對BaseView和BasePresenter的進一步封裝,所以我們實現的View和Presenter也只需要繼承XXXContract中的對應內部接口就行。

activity的作用主要是創建View(這裏是相應的fragment),以及創建presenter,並把view傳遞給presenter(完成presenter對view實例關聯操作)

在presenter的實現類的構造函數中,通過view的setPresenter,讓view獲得了presenter實例。這樣view中就可以對Presenter中的方法進行操作了。(完成view對presenter實例關聯操作)

在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.3 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.3 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層設計 進行了分析,不再贅述。

具體請參看本篇文章項目github地址:MVPCommon

4 參考資料:

Android官方MVP架構示例項目解析
AndroidArchitectureCollection

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章