MVP在Android項目中的簡單體現

通過簡單案例來說明MVP的使用,retrofit2+rxjava+mvp
項目地址:http://www.github.com/jjdxmashl/jjdxm_demomvp

前言

什麼是MVP?

MVP模式是一種架構模式,也是一種經典的界面模式。MVP中的M代表Model, V是View, P是Presenter。

Model 一部分是處理業務邏輯,一部分是提供View顯示的數據。
View 代表的是一個接口,一個將UI界面提煉而抽象出來的接口。
Presenter Model和View之間的橋樑

MVP在Android項目中的其中一種體現方式

經過查閱網上一些MVP的文章之後,有部分案例在presenter中實現具體的邏輯或者把Model單純的看作是具體的Bean個人覺得是不太準確的MVX(MVC、MVP和MVVM)中,M的職責都應該包含兩部分業務邏輯和提供View顯示的數據,而X的部分則是爲了實現UI界面和業務邏輯解耦的橋樑,在Android項目中使用MVP架構模式,以下這兩種架構方式是我比較能接受和認可的。

按照模塊分包

|----包名
|   |----base
|   |       BaseActivity        Activity基類
|   |       BaseMVPActivity     MVP Activity基類
|   |       BaseModel           Model基類
|   |       BaseFragment        Fragment基類
|   |       IBaseDelegate       簡化Presenter在Activity的實現
|   |       IBasePresenter      Presenter基類
|   |       IBaseView           View基類
|   |----模塊名1
|   |   |----model              業務邏輯和bean
|   |   |       xxxModel
|   |   |       xxxBean
|   |   |----presenter          連接View和Model的橋樑
|   |   |       xxxPresenter
|   |   |----ui                 UI界面相關的類
|   |   |       xxxActivity
|   |   |       xxxFragment
|   |   |----view               UI界面提煉出來的接口
|   |   |       xxxView
|   |----模塊名2

按照功能分包

|----包名
|   |----activity               具體Activity
|   |       xxxActivity
|   |----adapter                    具體Adapter
|   |       xxxAdapter
|   |----base
|   |       BaseActivity        Activity基類
|   |       BaseMVPActivity     MVP Activity基類
|   |       BaseModel           Model基類
|   |       BaseFragment        Fragment基類
|   |       IBaseDelegate       簡化Presenter在Activity的實現
|   |       IBasePresenter      Presenter基類
|   |       IBaseView           View基類
|   |----fragment               具體Fragment
|   |       xxxFragment
|   |----hodler                 具體Holder
|   |       xxxHodler
|   |----model                  業務邏輯和bean
|   |       xxxModel
|   |       xxxBean
|   |----presenter              連接View和Model的橋樑
|   |       xxxPresenter
|   |----view                   UI界面提煉出來的接口
|   |       xxxView
|   |----widget

前期準備

這裏使用聚合數據提供的免費API來實現兩個具體的功能歷史上的今天和笑話大全,註冊並實名爲聚合數據的用戶後,生成屬於自己的用戶key即可。

快速開始

step1 添加所需的依賴和權限

新建一個項目,在根目錄的build.gradle的dependencies節點中添加,用於註解

    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

如圖

主程序app module中build.gradle的第二行添加,用於註解

apply plugin: 'com.neenbedankt.android-apt'

dependencies節點中添加

compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.android.support:design:24.2.1'
//佈局註解
apt 'com.jakewharton:butterknife-compiler:8.0.1'
compile 'com.jakewharton:butterknife:8.0.1'
//響應式編程
compile 'io.reactivex:rxandroid:1.1.0'
compile 'io.reactivex:rxjava:1.1.0'
//聯網類庫
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
compile 'com.squareup.retrofit2:converter-scalars:2.1.0'
compile 'com.dou361.retrofit2:jjdxm-retrofit-converter-fastjson:1.0.0'
compile 'com.squareup.okhttp3:okhttp:3.3.0'

//自定義view
compile 'com.dou361.customui:jjdxm-customui:1.0.9'
//recyclerview基類
compile('com.dou361.recyclerview:jjdxm-recyclerview:1.0.2') {
    exclude group: 'com.android.support', module: 'design'
}

如圖

清單文件AndroidManifest.xml中,添加權限

<uses-permission android:name="android.permission.INTERNET"/>

step2

先寫好兩個網絡請求方法,Observable searchHistory(String month, String day)和Observable loadJoke(String page)分別是查詢歷史今天方法和加載笑話列表

網絡接口請求服務類

public interface IApiService {

    /** 查詢歷史的今天 */
    @GET("/japi/toh")
    Observable<RepoHistory> searchHistory(@QueryMap Map<String, String> map);

    /** 加載笑話列表 */
    @GET("/joke/content/list.from")
    Observable<RepoJoke> loadJoke(@QueryMap Map<String, String> map);
}

網絡接口請求基類

public class ApiBase {

    /**歷史上的今天 http://api.juheapi.com/japi/toh?key=7ac7e02ff7f1f8f1ccdc2f9e5dddb6be&v=1
     * .0&month=11&day=1*/
    /** 笑話大全 http://japi.juhe.cn/joke/content/list
     * .from?key=d796a03545bddee0b56d913111f5f199&page=2&pagesize=10&sort=asc&time=1418745237 */
    protected static IApiService getService() {
        return getService(null);
    }

    protected static IApiService getService(String ip) {
        return getService(ip, 0, 0);
    }

    protected static IApiService getService(String ip, long readTime, long connectTime) {
        OkHttpClient client = new OkHttpClient.Builder()
                .readTimeout(readTime <= 0 ? 30 : readTime, TimeUnit.SECONDS)
                .connectTimeout(connectTime <= 0 ? 30 : connectTime, TimeUnit.SECONDS)
                .build();
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(ip == null ? "http://api.juheapi.com" : ip)
                .client(client)
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(ScalarsConverterFactory.create())
                .addConverterFactory(FastJsonConverterFactory.create())
                .build();
        return retrofit.create(IApiService.class);
    }
}

網絡接口請求工具類

public class ApiUtils extends ApiBase {

    public static Observable<RepoHistory> searchHistory(String month, String day) {
        /**key=7ac7e02ff7f1f8f1ccdc2f9e5dddb6be&v=1.0&month=11&day=1*/
        Map<String, String> map = new HashMap<>();
        map.put("key", "7ac7e02ff7f1f8f1ccdc2f9e5dddb6be");
        map.put("v", "1.0");
        map.put("month", month);
        map.put("day", day);
        return getService().searchHistory(map);
    }

    public static Observable<RepoJoke> loadJoke(String page) {
        /**key=d796a03545bddee0b56d913111f5f199&page=2&pagesize=10&sort=asc&time=1418745237*/
        Map<String, String> map = new HashMap<>();
        map.put("key", "d796a03545bddee0b56d913111f5f199");
        map.put("sort", "asc");
        map.put("time", "1418745237");
        map.put("page", page);
        map.put("pagesize", "10");
        return getService().loadJoke(map);
    }
}

如圖

step3

開始架構MVP模式使用到的基類,這裏沒有使用網上所說的契約類xxxContract把View和Presenter寫在一個類中維護,而是分開出來,主要看個人喜好,如圖

UI界面抽象出來的接口

public interface IBaseView {

    /**
     * 顯示加載
     */
    void showLoading();

    /**
     * 完成加載
     */
    void dismiss();
}

業務邏輯實現的基類

public abstract class BaseModel<SubP> {

    protected SubP mPresenter;

    public BaseModel(SubP presenter) {
        this.mPresenter = presenter;
    }

}

連接Model和View的橋樑的基類

public interface IBasePresenter<V extends IBaseView> {

    /**綁定接口*/
    void attachView(V view);

    /**釋放接口*/
    void detachView();

}

persenter和activity綁定

public interface IBaseDelegate<V extends IBaseView, P extends IBasePresenter<V>> {

    /**初始化presenter*/
    @NonNull
    P createPresenter();

    /**獲取presenter*/
    @NonNull
    P getPresenter();

}

最後是Activity的基類

public abstract class BaseActivity extends
        AppCompatActivity {

    protected void startActivity(Class<?> clz) {
        Intent intent = new Intent(this, clz);
        startActivity(intent);
    }
}

MVP的Activity基類

public abstract class BaseMVPActivity<V extends IBaseView, P extends IBasePresenter<V>> extends
        BaseActivity implements IBaseDelegate<V, P> {

    protected P mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPresenter = createPresenter();
    }

    @NonNull
    @Override
    public P getPresenter() {
        return mPresenter;
    }

    @Override
    protected void onDestroy() {
        mPresenter.detachView();
        super.onDestroy();
    }
}

step4

針對模塊用MVP模式去架構大概有一下4個步驟

步驟1:UI實現View方法,引用Presenter
步驟2:Presenter調用Model,走Model具體邏輯
步驟3:Model邏輯實現,回調Presenter方法
步驟4:Presenter回調View,即回到UI,回調View方法

step5

具體模塊功能的實現,歷史的今天模塊,先創建一個HistoryActivity繼承BaseMVPActivity,新建IHistoryView並實現。

1.View的接口的抽取

抽象出來三個功能和父類IBaseView的兩個方法,分別是顯示加載好的數據,顯示空白數據提示,檢測數據提示,顯示加載中提示,隱藏加載中提示。

public interface IHistoryView extends IBaseView {

    /**顯示數據*/
    void showData(List<HistoryBean> list);

    /**無數據*/
    void showEmpty();

    /**檢測數據*/
    void showMessage(String msg);
}

2.Model的實現

具體的邏輯實現,這裏只有一個方法就是查詢歷史今天

public class HistoryModel extends BaseModel<HistoryPresenter> {

    public HistoryModel(HistoryPresenter presenter) {
        super(presenter);
    }

    public void searchHistory(String month, String day) {
        if (TextUtils.isEmpty(month)) {
            mIPresenter.showMessage("月份不能爲空");
            return;
        }
        int iMonth = Integer.valueOf(month).intValue();
        if (iMonth <= 0 || iMonth > 12) {
            mIPresenter.showMessage("只能輸入1-12的月份");
            return;
        }
        if (TextUtils.isEmpty(day)) {
            mIPresenter.showMessage("天不能爲空");
            return;
        }
        int iDay = Integer.valueOf(day).intValue();
        if (iDay <= 0 || iDay > 31) {
            mIPresenter.showMessage("只能輸入1-31的天");
            return;
        }
        ApiUtils.searchHistory(month, day)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.newThread())
                .subscribe(new Action1<RepoHistory>() {
                    @Override
                    public void call(RepoHistory repoHistory) {
                        if (repoHistory == null || repoHistory.getResult() == null
                                || repoHistory.getResult().size() <= 0) {
                            mIPresenter.showEmpty();
                        } else {
                            mIPresenter.showData(repoHistory.getResult());
                        }
                    }
                });
    }
}

3.Presenter橋樑的實現

public class HistoryPresenter implements IBasePresenter<IHistoryView> {

    private IHistoryView mView;
    private HistoryModel mModel;

    public HistoryPresenter(IHistoryView view) {
        attachView(view);
        mModel = new HistoryModel(this);
    }

    @Override
    public void attachView(IHistoryView view) {
        this.mView = view;
    }

    @Override
    public void detachView() {
        this.mView = null;
    }

    public void showData(List<HistoryBean> list) {
        mView.dismiss();
        mView.showData(list);
    }

    public void showEmpty() {
        mView.dismiss();
        mView.showEmpty();
    }

    public void showMessage(String msg) {
        mView.showMessage(msg);
    }

    public void searchHistory(String month, String day) {
        mView.showLoading();
        mModel.searchHistory(month, day);
    }
}

4.最後在HistoryActivity裏面去建立連接

最後創建的類架構圖如下:

編譯運行效果圖如下


同理笑話大全也一樣的創建對應的文件,最後運行如下

項目地址:http://www.github.com/jjdxmashl/jjdxm_demomvp

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