通過簡單案例來說明MVP的使用,retrofit2+rxjava+mvp
專案地址:www.github.com/jjdxmashl/j…
##前言
###什麼是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
網路介面請求服務類
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裡面去建立連線
最後建立的類架構圖如下:
編譯執行效果圖如下
同理笑話大全也一樣的建立對應的檔案,最後執行如下