MVC、MVP和MVVM是常見的三種架構設計模式,當前MVP和MVVM的使用相對比較普遍,固然MVC也並無過期之說。而所謂的組件化就是指將應用根據業務需求劃分紅各個模塊來進行開發,每一個模塊又能夠編譯成獨立的APP進行開發。理論上講,組件化和前面三種架構設計不是一個層次的。它們之間的關係是,組件化的各個組件可使用前面三種架構設計。咱們只有瞭解了這些架構設計的特色以後,才能在進行開發的時候選擇適合本身項目的架構模式,這也是本文的目的。java
MVC (Model-View-Controller, 模型-視圖-控制器),標準的MVC是這個樣子的:android
Activity並不是標準的Controller,它一方面用來控制了佈局,另外一方面還要在Activity中寫業務代碼,形成了Activity既像View又像Controller。git
在Android開發中,就是指直接使用Activity並在其中寫業務邏輯的開發方式。顯然,一方面Activity自己就是一個視圖,另外一方面又要負責處理業務邏輯,所以邏輯會比較混亂。github
這種開發方式不太適合Android開發。設計模式
MVP (Model-View-Presenter) 是MVC的演化版本,幾個主要部分以下:api
因此,對於MVP的架構設計,咱們有如下幾點須要說明:bash
爲了說明MVP設計模式,咱們給出一個示例程序。你能夠在Github中獲取到它的源代碼。網絡
在該示例中,咱們使用了:架構
下面是該模塊的基本的包結構:app
這裏核心的代碼是MVP部分。
這裏咱們首先定義了MVP模式中的最頂層的View和Presenter,在這裏分別是BaseView
和BasePresenter
,它們在該項目中是兩個空的接口,在一些項目中,咱們能夠根據本身的需求在這兩個接口中添加本身須要的方法。
而後,咱們定義了HomeContract
。它是一個抽象的接口,至關於一層協議,用來規定指定的功能的View和Presenter分別應該具備哪些方法。一般,對於不一樣的功能,咱們須要分別實現一個MVP,每一個MVP都會又一個對應的Contract。筆者認爲它的好處在於,將指定的View和Presenter的接口定義在一個接口中,更加集中。它們各自須要實現的方法也一目瞭然地展示在了咱們面前。
這裏根據咱們的業務場景,該接口的定義以下:
public interface HomeContract {
interface IView extends BaseView {
void setFirstPage(List<HomeBean.IssueList.ItemList> itemLists);
void setNextPage(List<HomeBean.IssueList.ItemList> itemLists);
void onError(String msg);
}
interface IPresenter extends BasePresenter {
void requestFirstPage();
void requestNextPage();
}
}
複製代碼
HomeContract
用來規定View和Presenter應該具備的操做,在這裏它用來指定主頁的View和Presenter的方法。從上面咱們也能夠看出,這裏的IView
和IPresenter
分別實現了BaseView
和BasePresenter
。
上面,咱們定義了V和P的規範,MVP中還有一項Model,它用來從網絡中獲取數據。這裏咱們省去網絡相關的具體的代碼,你只須要知道APIRetrofit.getEyepetizerService()
是用來獲取Retrofit對應的Service,而getMoreHomeData()
和getFirstHomeData()
是用來從指定的接口中獲取數據就行。下面是HomeModel
的定義:
public class HomeModel {
public Observable<HomeBean> getFirstHomeData() {
return APIRetrofit.getEyepetizerService().getFirstHomeData(System.currentTimeMillis());
}
public Observable<HomeBean> getMoreHomeData(String url) {
return APIRetrofit.getEyepetizerService().getMoreHomeData(url);
}
}
複製代碼
OK,上面咱們已經完成了Model的定義和View及Presenter的規範的定義。下面,咱們就須要具體去實現View和Presenter。
首先是Presenter,下面是咱們的HomePresenter
的定義。在下面的代碼中,爲了更加清晰地展現其中的邏輯,我刪減了一部分無關代碼:
public class HomePresenter implements HomeContract.IPresenter {
private HomeContract.IView view;
private HomeModel homeModel;
private String nextPageUrl;
// 傳入View並實例化Model
public HomePresenter(HomeContract.IView view) {
this.view = view;
homeModel = new HomeModel();
}
// 使用Model請求數據,並在獲得請求結果的時候調用View的方法進行回調
@Override
public void requestFirstPage() {
Disposable disposable = homeModel.getFirstHomeData()
// ....
.subscribe(itemLists -> { view.setFirstPage(itemLists); },
throwable -> { view.onError(throwable.toString()); });
}
// 使用Model請求數據,並在獲得請求結果的時候調用View的方法進行回調
@Override
public void requestNextPage() {
Disposable disposable = homeModel.getMoreHomeData(nextPageUrl)
// ....
.subscribe(itemLists -> { view.setFirstPage(itemLists); },
throwable -> { view.onError(throwable.toString()); });
}
}
複製代碼
從上面咱們能夠看出,在Presenter須要將View和Model創建聯繫。咱們須要在初始化的時候傳入View,並實例化一個Model。Presenter經過Model獲取數據,並在拿到數據的時候,經過View的方法通知給View層。
而後,就是咱們的View層的代碼,一樣,我對代碼作了刪減:
@Route(path = BaseConstants.EYEPETIZER_MENU)
public class HomeActivity extends CommonActivity<ActivityEyepetizerMenuBinding> implements HomeContract.IView {
// 實例化Presenter
private HomeContract.IPresenter presenter;
{
presenter = new HomePresenter(this);
}
@Override
protected int getLayoutResId() {
return R.layout.activity_eyepetizer_menu;
}
@Override
protected void doCreateView(Bundle savedInstanceState) {
// ...
// 使用Presenter請求數據
presenter.requestFirstPage();
loading = true;
}
private void configList() {
// ...
getBinding().rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
// 請求下一頁的數據
presenter.requestNextPage();
}
}
});
}
// 當請求到結果的時候在頁面上作處理,展現到頁面上
@Override
public void setFirstPage(List<HomeBean.IssueList.ItemList> itemLists) {
loading = false;
homeAdapter.addData(itemLists);
}
// 當請求到結果的時候在頁面上作處理,展現到頁面上
@Override
public void setNextPage(List<HomeBean.IssueList.ItemList> itemLists) {
loading = false;
homeAdapter.addData(itemLists);
}
@Override
public void onError(String msg) {
ToastUtils.makeToast(msg);
}
// ...
}
複製代碼
從上面的代碼中咱們能夠看出實際在View中也要維護一個Presenter的實例。 當須要請求數據的時候會使用該實例的方法來請求數據,因此,在開發的時候,咱們須要根據請求數據的狀況,在Presenter中定義接口方法。
實際上,MVP的原理就是View經過Presenter獲取數據,獲取到數據以後再回調View的方法來展現數據。
優勢:
缺點:
MVVM 是 Model-View-ViewModel 的簡寫。它本質上就是 MVC 的改進版。MVVM 就是將其中的 View 的狀態和行爲抽象化,讓咱們將視圖 UI 和業務邏輯分開。
使用 Google 官方的 Android Architecture Components ,咱們能夠很容易地將 MVVM 應用到咱們的應用中。下面,咱們就使用它來展現一下 MVVM 的實際的應用。你能夠在Github中獲取到它的源代碼。
在該項目中,咱們使用了:
該項目的包結構以下圖所示:
這裏的model.data
下面的類是對應於網絡的數據實體的,由JSON自動生成,這裏咱們不進行詳細描述。這裏的model.repository
下面的兩個類是用來從網絡中獲取數據信息的,咱們也忽略它的定義。
上面就是咱們的 Model 的定義,並無太多的內容,基本與 MVP 一致。
下面的是 ViewModel 的代碼,咱們選擇了其中的一個方法來進行說明。當咱們定義 ViewModel 的時候,須要繼承 ViewModel 類。
public class GuokrViewModel extends ViewModel {
public LiveData<Resource<GuokrNews>> getGuokrNews(int offset, int limit) {
MutableLiveData<Resource<GuokrNews>> result = new MutableLiveData<>();
GuokrRetrofit.getGuokrService().getNews(offset, limit)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<GuokrNews>() {
@Override
public void onError(Throwable e) {
result.setValue(Resource.error(e.getMessage(), null));
}
@Override
public void onComplete() { }
@Override
public void onSubscribe(Disposable d) { }
@Override
public void onNext(GuokrNews guokrNews) {
result.setValue(Resource.success(guokrNews));
}
});
return result;
}
}
複製代碼
這裏的 ViewModel 來自 android.arch.lifecycle.ViewModel
,因此,爲了使用它,咱們還須要加入下面的依賴:
api "android.arch.lifecycle:runtime:$archVersion"
api "android.arch.lifecycle:extensions:$archVersion"
annotationProcessor "android.arch.lifecycle:compiler:$archVersion"
複製代碼
在 ViewModel 的定義中,咱們直接使用 Retrofit 來從網絡中獲取數據。而後當獲取到數據的時候,咱們使用 LiveData 的方法把數據封裝成一個對象返回給 View 層。在 View 層,咱們只須要調用該方法,並對返回的 LiveData 進行"監聽"便可。這裏,咱們將錯誤信息和返回的數據信息進行了封裝,而且封裝了一個表明當前狀態的枚舉信息,你能夠參考源代碼來詳細瞭解下這些內容。
上面咱們定義完了 Model 和 ViewModel,下面咱們看下 View 層的定義,以及在 View 層中該如何使用 ViewModel。
@Route(path = BaseConstants.GUOKR_NEWS_LIST)
public class NewsListFragment extends CommonFragment<FragmentNewsListBinding> {
private GuokrViewModel guokrViewModel;
private int offset = 0;
private final int limit = 20;
private GuokrNewsAdapter adapter;
@Override
protected int getLayoutResId() {
return R.layout.fragment_news_list;
}
@Override
protected void doCreateView(Bundle savedInstanceState) {
// ...
guokrViewModel = ViewModelProviders.of(this).get(GuokrViewModel.class);
fetchNews();
}
private void fetchNews() {
guokrViewModel.getGuokrNews(offset, limit).observe(this, guokrNewsResource -> {
if (guokrNewsResource == null) {
return;
}
switch (guokrNewsResource.status) {
case FAILED:
ToastUtils.makeToast(guokrNewsResource.message);
break;
case SUCCESS:
adapter.addData(guokrNewsResource.data.getResult());
adapter.notifyDataSetChanged();
break;
}
});
}
}
複製代碼
以上就是咱們的 View 層的定義,這裏咱們先使用了
這裏的view.fragment
包下面的類對應於實際的頁面,這裏咱們 ViewModelProviders
的方法來獲取咱們須要使用的 ViewModel,而後,咱們直接使用該 ViewModel 的方法獲取數據,並對返回的結果進行「監聽」便可。
以上就是 MVVM 的基本使用,固然,這裏咱們並無使用 DataBinding 直接與返回的列表信息進行綁定,它被更多的用在了整個 Fragment 的佈局中。
MVVM模式和MVC模式同樣,主要目的是分離視圖(View)和模型(Model),有幾大優勢:
所謂的組件化,通俗理解就是將一個工程分紅各個模塊,各個模塊之間相互解耦,能夠獨立開發並編譯成一個獨立的 APP 進行調試,而後又能夠將各個模塊組合起來總體構成一個完整的 APP。它的好處是當工程比較大的時候,便於各個開發者之間分工協做、同步開發;被分割出來的模塊又能夠在項目之間共享,從而達到複用的目的。組件化有諸多好處,尤爲適用於比較大型的項目。
簡單瞭解了組件化以後,讓咱們來看一下如何實現組件化開發。你可能以前據說過組件化開發,或者被其高大上的稱謂嚇到了,但它實際應用起來並不複雜,至少藉助了現成的框架以後並不複雜。這裏咱們先梳理一下,在應用組件化的時候須要解決哪些問題:
Talk is cheap,下面讓咱們動手實踐來應用組件化進行開發。你能夠在Github中獲取到它的源代碼。
首先,咱們先來看整個應用的包的結構。以下圖所示,該模塊的劃分是根據各個模塊的功能來決定的。圖的右側白色的部分是各個模塊的文件路徑,我推薦使用這種方式,而不是將各個模塊放置在 app 下面,由於這樣看起來更加的清晰。爲了達到這個目的,你只須要按照下面的方式在 settings.gralde 裏面配置一下各個模塊的路徑便可。注意在實際應用的時候模塊的路徑的關係,不要搞錯了。
而後,咱們介紹一下這裏的 commons 模塊。它用來存放公共的資源和一些依賴,這裏咱們將二者放在了一個模塊中以減小模塊的數量。下面是它的 gradle 的部分配置。這裏咱們使用了 api 來引入各個依賴,以便在其餘的模塊中也能使用這些依賴。
dependencies {
api fileTree(include: ['*.jar'], dir: 'libs')
// ...
// router
api 'com.alibaba:arouter-api:1.3.1'
annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'
// walle
api 'com.meituan.android.walle:library:1.1.6'
// umeng
api 'com.umeng.sdk:common:1.5.3'
api 'com.umeng.sdk:analytics:7.5.3'
api files('libs/pldroid-player-1.5.0.jar')
}
複製代碼
接着,咱們來看一下路由框架的配置。這裏,咱們使用阿里的 ARouter 來進行頁面之間的跳轉,你能夠在Github上面瞭解該框架的配置和使用方式。這裏咱們只講解一下在組件化開發的時候須要注意的地方。注意到 ARouter 是經過註解來進行頁面配置的,而且它的註解是在編譯的時候進行處理的。因此,咱們須要引入arouter-compiler
來使用它的編譯時處理功能。須要注意的地方是,咱們只要在公共的模塊中加入arouter-api
就可使用ARouter的API了,可是須要在每一個模塊中引入arouter-compiler
才能使用編譯時註解。也就是說,咱們須要在每一個模塊中都加入arouter-compiler
依賴。
爲了可以將各個模塊編譯成一個獨立的 APP,咱們須要在 Gradle 裏面作一些配置。
首先,咱們須要在gradle.properties
定義一些布爾類型的變量用來判斷各個模塊是做爲一個 library 仍是 application 進行編譯。這裏個人配置以下面的代碼所示。也就是,我爲每一個模塊都定義了這麼一個布爾類型的變量,固然,你也能夠只定義一個變量,而後在各個模塊中使用同一個變量來進行判斷。
isGuokrModuleApp=false
isLiveModuleApp=false
isLayoutModuleApp=false
isLibraryModuleApp=false
isEyepetizerModuleApp=false
複製代碼
而後,咱們來看一下各個模塊中的 gradle 該如何配置,這裏咱們以開眼視頻的功能模塊做爲例子來進行講解。首先,一個模塊做爲 library 仍是 application 是根據引用的 plugin 來決定的,因此,咱們要根據以前定義的布爾變量來決定使用的 plugin:
if (isEyepetizerModuleApp.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
複製代碼
假如咱們要將某個模塊做爲一個獨立的 APP,那麼啓動類你確定須要配置。這就意味着你須要兩個 AndroidManifest.xml 文件,一個用於 library 狀態,一個用於 application 狀態。因此,咱們能夠在 main 目錄下面再定義一個 AndroidManifest.xml,而後,咱們在該配置文件中不僅指定啓動類,還使用咱們定義的 Application。指定 Application 有時候是必須的,好比你須要在各個模塊裏面初始化 ARouter 等等。這部分代碼就不給出了,能夠參考源碼,這裏咱們給出一下在 Gradle 裏面指定 AndroidManifest.xml 的方式。
以下所示,咱們能夠根據以前定義的布爾值來決定使用哪個配置文件:
sourceSets {
main {
jniLibs.srcDirs = ['libs']
if (isEyepetizerModuleApp.toBoolean()) {
manifest.srcFile "src/main/debug/AndroidManifest.xml"
} else {
manifest.srcFile "src/main/AndroidManifest.xml"
}
}
}
複製代碼
此外,還須要注意的是,若是咱們但願在每一個模塊中都能應用 DataBinding 和 Java 8 的一些特性,那麼你須要在每一個模塊裏面都加入下面的配置:
// use data binding
dataBinding {
enabled = true
}
// use java 8 language
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
複製代碼
對於編譯時註解之類的配置,咱們也須要在每一個模塊裏面都進行聲明。
完成了以上的配置,咱們只要根據須要編譯的類型,修改以前定義的布爾值,來決定是將該模塊編譯成 APP 仍是做爲類庫來使用便可。
以上就是組件化在 Android 開發當中的應用。
MVC、MVP和MVVM各有各自的特色,能夠根據應用開發的須要選擇適合本身的架構模式。組件化的目的就在於保持各個模塊之間的獨立從而便於分工協做。它們之間的關係就是,你能夠在組件化的各個模塊中應用前面三種架構模式的一種或者幾種。