經過簡單案例來講明MVP的使用,retrofit2+rxjava+mvp
項目地址:www.github.com/jjdxmashl/j…javascript
##前言java
###什麼是MVP?
MVP模式是一種架構模式,也是一種經典的界面模式。MVP中的M表明Model, V是View, P是Presenter。react
Model 一部分是處理業務邏輯,一部分是提供View顯示的數據。
View 表明的是一個接口,一個將UI界面提煉而抽象出來的接口。
Presenter Model和View之間的橋樑android
###MVP在Android項目中的其中一種體現方式
通過查閱網上一些MVP的文章以後,有部分案例在presenter中實現具體的邏輯或者把Model單純的看做是具體的Bean,我的以爲是不太準確的,MVX(MVC、MVP和MVVM)中,M的職責都應該包含兩部分業務邏輯和提供View顯示的數據,而X的部分則是爲了實現UI界面和業務邏輯解耦的橋樑,在Android項目中使用MVP架構模式,如下這兩種架構方式是我比較能接受和承認的。git
按照模塊分包github
|----包名
| |----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便可。json
##快速開始api
###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
網絡接口請求服務類
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裏面去創建鏈接
最後建立的類架構圖以下:
編譯運行效果圖以下
同理笑話大全也同樣的建立對應的文件,最後運行以下