開發一個App,和起房子應該有異曲同工之處,起房子需要畫好設計圖紙,而我們開發App則需要先設計好App整個架構模式。目前Android一般有MVC、MVP和MVVM,本文則先來說說MVP架構。在瞭解MVP架構之前,有人可能會說,MVP架構是不是有點落後了,但是我想說,如果你公司有老項目,他就是用MVP架構寫的,這時候我們MVP知識是不是就派上用場了;任何架構都有它存在的理由,學習架構的思想纔是關鍵。MVP分別代表Model、View、Presenter三個英文字母,和傳統的MVC 相比,C替換成了P。Presenter英文單詞有主持人意思,也就是說Presenter是View 和 Model 的主持人,按照慣例我們先來看兩張圖。
MVC MVP 架構對比圖
mvc
mvp
- 通過以上兩張圖對比,MVC在Android中就是我們剛開始學習Android時輸出Android代碼的真實寫照,Activity不僅負責顯示View,它還是Controller,我們可以在Activity開始網絡請求,請求完成更新UI,也可以在Activity中通過UI組件獲取用戶輸入數據,然後執行網絡請求再更新UI,這樣一來,一個功能複雜的頁面一個Activity三四千行代碼是很常見的事情,這也會導致後面維護代碼人來讀你的Activity代碼可能會直接崩潰,同時代碼的耦合度也很高。
- 而我們再看MVP架構,這就會很清晰,它把MVC中的VC進行解耦,也就是說把Activity中的UI邏輯抽象成View 接口 ,把業務邏輯抽象成 presenter 接口, model 還是原來的model,這樣其實就呼應了文章我們所說presenter主持人的意思,model 更新UI需要通過presenter,view 更新model數據也需要通過presenter,相當於presenter主持大局。
- 說了這麼多,其實MVC和MVP的區別可以用一句話代替,那就是View能否直接操作Model,接下來我們就看看MVP架構如何在Android中實踐。
Android 實現 mvp 架構
UI邏輯抽象成View接口
/**
* @author maoqitian
* @Description View 的基類
*/
public interface BaseView {
/**
* 正常顯示
*/
void showNormal();
/**
* 顯示錯誤
*/
void showError();
/**
* 正在加載
*/
void showLoading();
/**
* 顯示錯誤信息
* @param errorMsg 錯誤信息
*/
void showErrorMsg(String errorMsg);
}
業務邏輯抽象成 Presenter 接口
- 抽象之前我們可以想一想,每個presenter都對應一個View 界面,所以我們需要一個方法來綁定對應的View,綁定的目的是爲了方便我們在presenter中更新view,當界面銷燬的時候也需要一個方法類解綁View。此外,界面肯定不止一個,並且肯定實現前面我們寫的BaseView接口,我們用泛型代替,就有了如下BasePresenter 接口
/**
* @author maoqitian
* @Description Presenter 基類接口
*/
public interface AbstractBasePresenter<T extends BaseView> {
/**
* 綁定View
* @param view
*/
void attachView(T view);
/**
* 解綁View
*/
void detachView();
}
MVP工作流
- 前面我們已經抽象出了View、Presenter接口,接下來從結合文章開頭MVP architectural pattern圖從用戶打開App獲取數據開始展現整體MVP工作流。
View 與 Presenter 結合
View 獲取Presenter對象
- View 獲取數據需要通過Presenter對象,view在Android 中一般代表Avtivity、或者Fragment。先創建Avtivity和Fragment抽象類類做基礎封裝。
/**
* @author maoqitian
* @Description activity基類
*/
public abstract class AbstractActivity extends AppCompatActivity{
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayout());
onViewCreated();
initToolbar();
initEventAndData();
}
/**
* view 的創建 留給子類實現
*/
protected abstract void onViewCreated();
/**
* 初始化 toolbar
*/
protected abstract void initToolbar();
/**
* 初始化數據留給子類實現
*/
protected abstract void initEventAndData();
/**
* 獲取佈局對象 留給子類實現
*/
protected abstract int getLayout();
}
- 接着我們實現MVP Activity基類
/**
* @author maoqitian
* @Description MVP BaseActivity 基類
*/
public abstract class BaseActivity <T extends AbstractBasePresenter> extends AbstractActivity implements BaseView{
protected T mPresenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
mPresenter = createPresenter();
super.onCreate(savedInstanceState);
}
@Override
protected void onViewCreated() {
if (mPresenter != null) {
mPresenter.attachView(this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if(mPresenter != null){
mPresenter.detachView();
mPresenter = null;
}
}
/**
* 創建Presenter
*/
protected abstract T createPresenter();
@Override
public void showNormal() {
}
@Override
public void showError() {
}
@Override
public void showLoading() {
}
@Override
public void showErrorMsg(String errorMsg) {
}
}
- 到此,只要我們界面繼承BaseActivity,並且實現createPresenter方法,我們就可以很直接在View中通過Presenter來獲獲取數據,如何獲取呢?接着往下看。
Presenter 獲取View 對象
- 現在我們創建一個Presenter基類將其與View結合,爲後續步驟做準備,注意我們RxBasePresenter基類構造方法中需要傳入DataClient,該類其實就可以概括代表Module。
/**
* @author maoqitian
* @Description 基於Presenter封裝
*/
public class RxBasePresenter<T extends BaseView> implements AbstractBasePresenter<T>{
protected T mView;
private DataClient mDataClient;
public RxBasePresenter(DataClient dataClient){
this.mDataClient=dataClient;
}
@Override
public void attachView(T view) {
this.mView=view;
}
@Override
public void detachView() {
this.mView = null;
}
}
Presenter 與 View 之間連接
- 當我們創建View 對應Presenter讓其繼承 RxBasePresenter,則 Presenter便可以執行Updates view,如何操作呢?我們可以通過接口來進行數據獲取與顯示的擴展。以下舉個例子
public interface MainContract {
interface MainView extends BaseView{
void showMainData();
}
interface MainActivityPresenter extends AbstractBasePresenter<MainView>{
void getMainData();
}
}
- 至此,我們基本MVP架構其實就已經搭建完成,我們來看看使用
/**
* @author maoqitian
* @Description MainPresenter (Presenter)
*/
public class MainPresenter extends RxBasePresenter<MainContract.MainView> implements MainContract.MainActivityPresenter {
//(Model)
private DataClient mDataClient;
public MainPresenter(DataClient dataClient) {
super(dataClient);
this.mDataClient = dataClient;
}
@Override
public void attachView(MainContract.MainView view) {
super.attachView(view);
}
//獲取數據
@Override
public void getMainData() {
//mDataClient 網絡請求獲取數據
.......
// 數據獲取成功展示數據
mView.showMainData();
}
}
/**
* @author maoqitian
* @Description MainActivity (View)
*/
public class MainActivity extends BaseActivity<MainPresenter>implements MainContract.MainView{
..........
@Override
protected MainPresenter createPresenter() {
return new MainPresenter(new DataClient());
}
@Override
protected void initEventAndData() {
mPresenter.getMainData();
}
@Override
public void showMainData() {
//顯示數據
}
}
- 通過以上示例代碼,再次對比文章開頭的Android MVP architectural pattern圖,從用戶打開App獲取數據開始展現整體MVP工作流已經走完。
谷歌官方示例MVP demo
- 當然上面只是簡單的講解了在Android中搭建基本MVP架構,其實谷歌官方也給我提供了MVP示例代碼,具體代碼可以自行去了解。
- 谷歌官方MVP示例Demo
使用dagger2優化MVP 架構
- 前面我們大致搭建了一個基礎的MVP架構,每個Presnter需要我們在View 中去創建,創建Presnter的時候還需要傳入Model,這就說明他們之間的解耦還不夠。這裏我們換一種思路,在原有基礎,不管是DataClient(Model)還是對應的Presnter都可以直接提供對應的對象,然後對應的類創建我們就將其注入,這樣不就省去了對象創建,Model、Presnter、View 之間耦合度就進一步降低,如何實現?還是強大的谷歌爸爸給我們提供了方案,使用Dagger2(Dagger是一個完全靜態的編譯時依賴注入框架,適用於Java和Android)。
項目添加對應dagger依賴
- 使用dagger對應版本爲2.22.1
dependencies {
implementation 'com.google.dagger:dagger:2.22.1'
annotationProcessor 'com.google.dagger:dagger-compiler:2.22.1'
//
implementation 'com.google.dagger:dagger-android:2.22.1'
implementation 'com.google.dagger:dagger-android-support:2.22.1'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.22.1'
}
改造Presnter類
- 這裏我們以上面例子中MainPresenter爲例,在其構造方法添加@Inject註解,表明Dagger2 可以從這獲取對應MainPresenter實例,注意構造方法中需要DataClient對象,這裏也使用Dagger來提供對象(稍後再說)
public class MainPresenter extends RxBasePresenter<MainContract.MainView> implements MainContract.MainActivityPresenter {
private DataClient mDataClient;
//@Inject註解表示Dagger2 可以從這獲取Presenter 實例
@Inject
public MainPresenter(DataClient dataClient) {
super(dataClient);
this.mDataClient = dataClient;
}
}
改造View
- View 中結合 Dagger2 本應該繼承 DaggerAppCompatActivity,但是我們基類爲AbstractActivity,查看DaggerAppCompatActivity源碼,直接手動實現DaggerAppCompatActivity中代碼。
/**
* @author maoqitian
* @Description MVP BaseActivity 基類
*/
public abstract class BaseActivity <T extends AbstractBasePresenter> extends AbstractSimpleActivity implements BaseView, HasFragmentInjector,HasSupportFragmentInjector {
//Presenter 對象注入 (注意不能使用 private )
@Inject
protected T mPresenter;
//手動實現DaggerAppCompatActivity功能
@Inject DispatchingAndroidInjector<Fragment> supportFragmentInjector;
@Inject DispatchingAndroidInjector<android.app.Fragment> frameworkFragmentInjector;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
//必須在super.onCreate之前調用AndroidInjection.inject
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
}
@Override
public AndroidInjector<Fragment> supportFragmentInjector() {
return supportFragmentInjector;
}
@Override
public AndroidInjector<android.app.Fragment> fragmentInjector() {
return frameworkFragmentInjector;
}
}
添加Dagger Module和Component
創建MainActivityModule
- 抽象類MainActivityModule加入@Module註解,並添加返回我們對應MainActivityPresenter接口的抽象方法,@Binds註解就可以幫我們把MainActivityPresenter接口綁定到MainPresenter上。
/**
* @author maoqitian
* @Description MainActivity 可以提供的注入對象Module
* @Time 2019/3/27 0027 23:59
*/
@Module
public abstract class MainActivityModule {
@ActivityScope
@Binds
abstract MainContract.MainActivityPresenter bindPresenter(MainPresenter presenter);
}
創建用於生成Activity注入器的Module類
/**
* @author maoqitian
* @Description 用於生成Activity注入器的Module,使用@ContributesAndroidInjector註解並指定modules爲
* @Time 2019/4/14 0014 14:09
*/
@Module
public abstract class ActivityBindingModule {
@ActivityScope
@ContributesAndroidInjector(modules = MainActivityModule.class)
abstract MainActivity contributeMainActivity();
}
創建提供我們需要對象的Module類
- 與前文對應,這裏我們提供了對應了DataClient對象,也就是MVP中的Model,在注入Presenter的時候將其一起注入。
@Module
public class MyAppModule {
@Provides
@Singleton
public DataClient providerDataClient(){
return new DataClient();
}
}
使用@Component註解創建AppCompontent接口類
- Dagger會幫我們自動生成DaggerAppComponent類,繼承自AndroidInjector並指定我們自己的Application類,指定AndroidSupportInjectionModule幫助把Android中四大組件以及Fragment進行綁定,@Singleton註解指定單例
@Singleton
@Component(modules = {
MyAppModule.class,
ActivityBindingModule.class,
AndroidSupportInjectionModule.class
})
public interface AppComponent extends AndroidInjector<MyApplication> {
}
改造Application類繼承自DaggerApplication
- 按照如下改造MyApplication之後我們從新編譯編譯一下代碼,如果編譯通過,dagger就會幫我們生成對應DaggerAppComponent.create()方法,將其返回在applicationInjector()方法中。
public class MyApplication extends DaggerApplication {
@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return DaggerAppComponent.create();
}
}
- 項目編譯通過dagger會在build目錄生成對應對象注入類,具體源碼以後再出文章分析,這裏就先告一段落了。到此,使用dagger2優化MVP 架構基本完成了,但是還有其他細節這裏沒有提及,比如每個Presenter之間該如何通信,可以使用EventBus,也可以Rxbus等等,具體細節可以看接下架構實踐中我寫的開源項目的代碼。
架構實踐
項目介紹
- 通過前面對MVP架構分析介紹,接下來我給大家推薦我的一個開源項目,這是一款有較好用戶體驗的開源玩Android客戶端。提供豐富完整的功能,更好的體驗,旨在更好的瀏覽https://www.wanandroid.com/網站內容,更好的在手機上進行學習。項目使用Retrofit2 + RxJava2 + Dagger2 +MVP+RxBus架構,儘量使用Material Design控件進行開發。
項目架構圖
項目地址
總結
- 以前所說的知識一種MVP架構的寫法,我們也可以根據自己理解寫出不一樣的MVP,其實MVP架構看似不錯,但也還是會有缺點,那就是寫一個頁面會產生很多個類,雖然結構清晰,但是要寫的代碼變多了,凡事都會有利弊。如果你不想自己寫這麼多的類,github上也有大神寫好了輪子(MVPArms)專門幫我們生成MVP架構的框架,但是用框架生成代碼總歸是別人的,只有自己擼一遍,把邏輯流程梳理清楚纔會變成自己的東西,纔會成長。文章中如果有錯誤,請大家給我提出來,大家一起學習進步,如果覺得我的文章給予你幫助,也請給我一個喜歡和關注,同時也歡迎訪問我的個人博客。