Android 架構(gòu)設(shè)計:MVC哩俭、MVP、MVVM和組件化

Android 架構(gòu)設(shè)計:MVC拳恋、MVP凡资、MVVM和組件化

MVC、MVP和MVVM是常見的三種架構(gòu)設(shè)計模式谬运,當(dāng)前MVP和MVVM的使用相對比較廣泛隙赁,當(dāng)然MVC也并沒有過時之說。而所謂的組件化就是指將應(yīng)用根據(jù)業(yè)務(wù)需求劃分成各個模塊來進行開發(fā)吩谦,每個模塊又可以編譯成獨立的APP進行開發(fā)鸳谜。理論上講,組件化和前面三種架構(gòu)設(shè)計不是一個層次的式廷。它們之間的關(guān)系是咐扭,組件化的各個組件可以使用前面三種架構(gòu)設(shè)計。我們只有了解了這些架構(gòu)設(shè)計的特點之后滑废,才能在進行開發(fā)的時候選擇適合自己項目的架構(gòu)模式蝗肪,這也是本文的目的。

1蠕趁、MVC

MVC (Model-View-Controller, 模型-視圖-控制器)薛闪,標準的MVC是這個樣子的:

  • 模型層 (Model):業(yè)務(wù)邏輯對應(yīng)的數(shù)據(jù)模型,無View無關(guān)俺陋,而與業(yè)務(wù)相關(guān)豁延;
  • 視圖層 (View):一般使用XML或者Java對界面進行描述昙篙;
  • 控制層 (Controllor):在Android中通常指Activity和Fragment,或者由其控制的業(yè)務(wù)類诱咏。

Activity并非標準的Controller苔可,它一方面用來控制了布局,另一方面還要在Activity中寫業(yè)務(wù)代碼袋狞,造成了Activity既像View又像Controller焚辅。

在Android開發(fā)中,就是指直接使用Activity并在其中寫業(yè)務(wù)邏輯的開發(fā)方式苟鸯。顯然同蜻,一方面Activity本身就是一個視圖,另一方面又要負責(zé)處理業(yè)務(wù)邏輯早处,因此邏輯會比較混亂湾蔓。

這種開發(fā)方式不太適合Android開發(fā)。

2陕赃、MVP

2.1 概念梳理

MVP (Model-View-Presenter) 是MVC的演化版本卵蛉,幾個主要部分如下:

  • 模型層 (Model):主要提供數(shù)據(jù)存取功能。
  • 視圖層 (View):處理用戶事件和視圖么库。在Android中,可能是指Activity甘有、Fragment或者View诉儒。
  • 展示層 (Presenter):負責(zé)通過Model存取書數(shù)據(jù),連接View和Model亏掀,從Model中取出數(shù)據(jù)交給View忱反。

所以,對于MVP的架構(gòu)設(shè)計滤愕,我們有以下幾點需要說明:

  1. 這里的Model是用來存取數(shù)據(jù)的温算,也就是用來從指定的數(shù)據(jù)源中獲取數(shù)據(jù),不要將其理解成MVC中的Model间影。在MVC中Model是數(shù)據(jù)模型注竿,在MVP中,我們用Bean來表示數(shù)據(jù)模型魂贬。
  2. Model和View不會直接發(fā)生關(guān)系巩割,它們需要通過Presenter來進行交互。在實際的開發(fā)中付燥,我們可以用接口來定義一些規(guī)范宣谈,然后讓我們的View和Model實現(xiàn)它們,并借助Presenter進行交互即可键科。

為了說明MVP設(shè)計模式闻丑,我們給出一個示例程序漩怎。你可以在Github中獲取到它的源代碼。

2.2 示例程序

在該示例中嗦嗡,我們使用了:

  1. 開眼視頻的API作為數(shù)據(jù)源勋锤;
  2. Retrofit進行數(shù)據(jù)訪問;
  3. 使用ARouter進行路由酸钦;
  4. 使用MVP設(shè)計模式作為程序架構(gòu)怪得。

下面是該模塊的基本的包結(jié)構(gòu):

包結(jié)構(gòu)

這里核心的代碼是MVP部分。

這里我們首先定義了MVP模式中的最頂層的View和Presenter卑硫,在這里分別是BaseViewBasePresenter徒恋,它們在該項目中是兩個空的接口,在一些項目中欢伏,我們可以根據(jù)自己的需求在這兩個接口中添加自己需要的方法入挣。

然后,我們定義了HomeContract硝拧。它是一個抽象的接口径筏,相當(dāng)于一層協(xié)議,用來規(guī)定指定的功能的View和Presenter分別應(yīng)該具有哪些方法障陶。通常滋恬,對于不同的功能,我們需要分別實現(xiàn)一個MVP抱究,每個MVP都會又一個對應(yīng)的Contract恢氯。筆者認為它的好處在于,將指定的View和Presenter的接口定義在一個接口中鼓寺,更加集中勋拟。它們各自需要實現(xiàn)的方法也一目了然地展現(xiàn)在了我們面前。

這里根據(jù)我們的業(yè)務(wù)場景妈候,該接口的定義如下:

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用來規(guī)定View和Presenter應(yīng)該具有的操作敢靡,在這里它用來指定主頁的View和Presenter的方法。從上面我們也可以看出苦银,這里的IViewIPresenter分別實現(xiàn)了BaseViewBasePresenter啸胧。

上面,我們定義了V和P的規(guī)范墓毒,MVP中還有一項Model吓揪,它用來從網(wǎng)絡(luò)中獲取數(shù)據(jù)。這里我們省去網(wǎng)絡(luò)相關(guān)的具體的代碼所计,你只需要知道APIRetrofit.getEyepetizerService()是用來獲取Retrofit對應(yīng)的Service柠辞,而getMoreHomeData()getFirstHomeData()是用來從指定的接口中獲取數(shù)據(jù)就行。下面是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主胧,上面我們已經(jīng)完成了Model的定義和View及Presenter的規(guī)范的定義叭首。下面习勤,我們就需要具體去實現(xiàn)View和Presenter。

首先是Presenter焙格,下面是我們的HomePresenter的定義图毕。在下面的代碼中,為了更加清晰地展示其中的邏輯眷唉,我刪減了一部分無關(guān)代碼:

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請求數(shù)據(jù)予颤,并在得到請求結(jié)果的時候調(diào)用View的方法進行回調(diào)
    @Override
    public void requestFirstPage() {
        Disposable disposable = homeModel.getFirstHomeData()
                // ....
                .subscribe(itemLists -> { view.setFirstPage(itemLists); },
                        throwable -> { view.onError(throwable.toString()); });
    }

    // 使用Model請求數(shù)據(jù),并在得到請求結(jié)果的時候調(diào)用View的方法進行回調(diào)
    @Override
    public void requestNextPage() {
        Disposable disposable = homeModel.getMoreHomeData(nextPageUrl)
                // ....
                .subscribe(itemLists -> { view.setFirstPage(itemLists); },
                        throwable -> { view.onError(throwable.toString()); });
    }
}

從上面我們可以看出冬阳,在Presenter需要將View和Model建立聯(lián)系蛤虐。我們需要在初始化的時候傳入View,并實例化一個Model肝陪。Presenter通過Model獲取數(shù)據(jù)驳庭,并在拿到數(shù)據(jù)的時候,通過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請求數(shù)據(jù)
        presenter.requestFirstPage();
        loading = true;
    }

    private void configList() {
        // ...
        getBinding().rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                    // 請求下一頁的數(shù)據(jù)
                    presenter.requestNextPage();
                }
            }
        });
    }

    // 當(dāng)請求到結(jié)果的時候在頁面上做處理贝淤,展示到頁面上
    @Override
    public void setFirstPage(List<HomeBean.IssueList.ItemList> itemLists) {
        loading = false;
        homeAdapter.addData(itemLists);
    }

    // 當(dāng)請求到結(jié)果的時候在頁面上做處理,展示到頁面上
    @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的實例政供。
當(dāng)需要請求數(shù)據(jù)的時候會使用該實例的方法來請求數(shù)據(jù)霹娄,所以,在開發(fā)的時候鲫骗,我們需要根據(jù)請求數(shù)據(jù)的情況,在Presenter中定義接口方法踩晶。

實際上执泰,MVP的原理就是View通過Presenter獲取數(shù)據(jù),獲取到數(shù)據(jù)之后再回調(diào)View的方法來展示數(shù)據(jù)渡蜻。

2.3 MVC 和 MVP 的區(qū)別

  1. MVC 中是允許 Model 和 View 進行交互的术吝,而MVP中,Model 與 View 之間的交互由Presenter完成茸苇;
  2. MVP 模式就是將 P 定義成一個接口排苍,然后在每個觸發(fā)的事件中調(diào)用接口的方法來處理,也就是將邏輯放進了 P 中学密,需要執(zhí)行某些操作的時候調(diào)用 P 的方法就行了淘衙。

2.4 MVP的優(yōu)缺點

優(yōu)點:

  1. 降低耦合度,實現(xiàn)了 Model 和 View 真正的完全分離腻暮,可以修改 View 而不影響 Modle彤守;
  2. 模塊職責(zé)劃分明顯毯侦,層次清晰;
  3. 隱藏數(shù)據(jù)具垫;
  4. Presenter 可以復(fù)用侈离,一個 Presenter 可以用于多個 View,而不需要更改 Presenter 的邏輯筝蚕;
  5. 利于測試驅(qū)動開發(fā)卦碾,以前的Android開發(fā)是難以進行單元測試的;
  6. View 可以進行組件化起宽,在MVP當(dāng)中洲胖,View 不依賴 Model。

缺點:

  1. Presenter 中除了應(yīng)用邏輯以外燎含,還有大量的 View->Model宾濒,Model->View 的手動同步邏輯,造成 Presenter 比較笨重屏箍,維護起來會比較困難绘梦;
  2. 由于對視圖的渲染放在了 Presenter 中,所以視圖和 Presenter 的交互會過于頻繁赴魁;
  3. 如果 Presenter 過多地渲染了視圖卸奉,往往會使得它與特定的視圖的聯(lián)系過于緊密,一旦視圖需要變更颖御,那么Presenter也需要變更了榄棵。

3、MVVM (分手大師)

3.1 基礎(chǔ)概念

MVVM 是 Model-View-ViewModel 的簡寫潘拱。它本質(zhì)上就是 MVC 的改進版疹鳄。MVVM 就是將其中的 View 的狀態(tài)和行為抽象化,讓我們將視圖 UI 和業(yè)務(wù)邏輯分開芦岂。

  • 模型層 (Model):負責(zé)從各種數(shù)據(jù)源中獲取數(shù)據(jù)瘪弓;
  • 視圖層 (View):在 Android 中對應(yīng)于 Activity 和 Fragment,用于展示給用戶和處理用戶交互禽最,會驅(qū)動 ViewModel 從 Model 中獲取數(shù)據(jù)腺怯;
  • ViewModel 層:用于將 Model 和 View 進行關(guān)聯(lián),我們可以在 View 中通過 ViewModel 從 Model 中獲取數(shù)據(jù)川无;當(dāng)獲取到了數(shù)據(jù)之后呛占,會通過自動綁定,比如 DataBinding懦趋,來將結(jié)果自動刷新到界面上晾虑。

使用 Google 官方的 Android Architecture Components ,我們可以很容易地將 MVVM 應(yīng)用到我們的應(yīng)用中。下面走贪,我們就使用它來展示一下 MVVM 的實際的應(yīng)用佛猛。你可以在Github中獲取到它的源代碼。

3.2 示例程序

在該項目中坠狡,我們使用了:

  1. 果殼網(wǎng)的 API 作為數(shù)據(jù)源继找;
  2. 使用 Retrofit 進行網(wǎng)絡(luò)數(shù)據(jù)訪問;
  3. 使用 ViewMdeol 作為整體的架構(gòu)設(shè)計逃沿。

該項目的包結(jié)構(gòu)如下圖所示:

mvvm

這里的model.data下面的類是對應(yīng)于網(wǎng)絡(luò)的數(shù)據(jù)實體的婴渡,由JSON自動生成,這里我們不進行詳細描述凯亮。這里的model.repository下面的兩個類是用來從網(wǎng)絡(luò)中獲取數(shù)據(jù)信息的边臼,我們也忽略它的定義。

上面就是我們的 Model 的定義假消,并沒有太多的內(nèi)容柠并,基本與 MVP 一致。

下面的是 ViewModel 的代碼富拗,我們選擇了其中的一個方法來進行說明臼予。當(dāng)我們定義 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 來從網(wǎng)絡(luò)中獲取數(shù)據(jù)。然后當(dāng)獲取到數(shù)據(jù)的時候追驴,我們使用 LiveData 的方法把數(shù)據(jù)封裝成一個對象返回給 View 層械哟。在 View 層,我們只需要調(diào)用該方法殿雪,并對返回的 LiveData 進行"監(jiān)聽"即可戒良。這里,我們將錯誤信息和返回的數(shù)據(jù)信息進行了封裝冠摄,并且封裝了一個代表當(dāng)前狀態(tài)的枚舉信息,你可以參考源代碼來詳細了解下這些內(nèi)容几缭。

上面我們定義完了 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包下面的類對應(yīng)于實際的頁面,這里我們 ViewModelProviders 的方法來獲取我們需要使用的 ViewModel,然后纸兔,我們直接使用該 ViewModel 的方法獲取數(shù)據(jù)惰瓜,并對返回的結(jié)果進行“監(jiān)聽”即可。

以上就是 MVVM 的基本使用汉矿,當(dāng)然崎坊,這里我們并沒有使用 DataBinding 直接與返回的列表信息進行綁定,它被更多的用在了整個 Fragment 的布局中洲拇。

3.3 MVVM 的優(yōu)點和缺點

MVVM模式和MVC模式一樣奈揍,主要目的是分離視圖(View)和模型(Model)爆哑,有幾大優(yōu)點:

  1. 低耦合:視圖(View)可以獨立于Model變化和修改今野,一個 ViewModel 可以綁定到不同的 View 上,當(dāng) View 變化的時候 Model 可以不變囊陡,當(dāng) Model 變化的時候 View 也可以不變纽乱。
  2. 可重用性:你可以把一些視圖邏輯放在一個 ViewModel 里面蛾绎,讓很多 view 重用這段視圖邏輯。
  3. 獨立開發(fā):開發(fā)人員可以專注于業(yè)務(wù)邏輯和數(shù)據(jù)的開發(fā)(ViewModel)鸦列,設(shè)計人員可以專注于頁面設(shè)計租冠。
  4. 可測試:界面素來是比較難于測試的,而現(xiàn)在測試可以針對 ViewModel 來寫敛熬。

4肺稀、組件化

4.1 基礎(chǔ)概念

所謂的組件化,通俗理解就是將一個工程分成各個模塊应民,各個模塊之間相互解耦话原,可以獨立開發(fā)并編譯成一個獨立的 APP 進行調(diào)試,然后又可以將各個模塊組合起來整體構(gòu)成一個完整的 APP诲锹。它的好處是當(dāng)工程比較大的時候繁仁,便于各個開發(fā)者之間分工協(xié)作、同步開發(fā)归园;被分割出來的模塊又可以在項目之間共享黄虱,從而達到復(fù)用的目的。組件化有諸多好處庸诱,尤其適用于比較大型的項目捻浦。

簡單了解了組件化之后,讓我們來看一下如何實現(xiàn)組件化開發(fā)桥爽。你可能之前聽說過組件化開發(fā)朱灿,或者被其高大上的稱謂嚇到了,但它實際應(yīng)用起來并不復(fù)雜钠四,至少借助了現(xiàn)成的框架之后并不復(fù)雜盗扒。這里我們先梳理一下,在應(yīng)用組件化的時候需要解決哪些問題:

  1. 如何分成各個模塊?我們可以根據(jù)業(yè)務(wù)來進行拆分侣灶,對于比較大的功能模塊可以作為應(yīng)用的一個模塊來使用甸祭,但是也應(yīng)該注意,劃分出來的模塊不要過多褥影,否則可能會降低編譯的速度并且增加維護的難度池户。
  2. 各個模塊之間如何進行數(shù)據(jù)共享和數(shù)據(jù)通信?我們可以把需要共享的數(shù)據(jù)劃分成一個單獨的模塊來放置公共數(shù)據(jù)伪阶。各個模塊之間的數(shù)據(jù)通信煞檩,我們可以使用阿里的 ARouter 進行頁面的跳轉(zhuǎn),使用封裝之后的 RxJava 作為 EventBus 進行全局的數(shù)據(jù)通信栅贴。
  3. 如何將各個模塊打包成一個獨立的 APP 進行調(diào)試斟湃?首先這個要建立在2的基礎(chǔ)上,然后檐薯,我們可以在各個模塊的 gradle 文件里面配置需要加載的 AndroidManifest.xml 文件凝赛,并可以為每個應(yīng)用配置一個獨立的 Application 和啟動類。
  4. 如何防止資源名沖突問題坛缕?遵守命名規(guī)約就能規(guī)避資源名沖突問題墓猎。
  5. 如何解決 library 重復(fù)依賴以及 sdk 和依賴的第三方版本號控制問題?可以將各個模塊公用的依賴的版本配置到 settings.gradle 里面赚楚,并且可以建立一個公共的模塊來配置所需要的各種依賴毙沾。

Talk is cheap,下面讓我們動手實踐來應(yīng)用組件化進行開發(fā)宠页。你可以在Github中獲取到它的源代碼左胞。

4.2 組件化實踐

包結(jié)構(gòu)

首先,我們先來看整個應(yīng)用的包的結(jié)構(gòu)举户。如下圖所示烤宙,該模塊的劃分是根據(jù)各個模塊的功能來決定的。圖的右側(cè)白色的部分是各個模塊的文件路徑俭嘁,我推薦使用這種方式躺枕,而不是將各個模塊放置在 app 下面,因為這樣看起來更加的清晰供填。為了達到這個目的拐云,你只需要按照下面的方式在 settings.gralde 里面配置一下各個模塊的路徑即可。注意在實際應(yīng)用的時候模塊的路徑的關(guān)系近她,不要搞錯了慨丐。

組件化

然后,我們介紹一下這里的 commons 模塊泄私。它用來存放公共的資源和一些依賴,這里我們將兩者放在了一個模塊中以減少模塊的數(shù)量。下面是它的 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 來進行頁面之間的跳轉(zhuǎn)漆羔,你可以在Github上面了解該框架的配置和使用方式梧奢。這里我們只講解一下在組件化開發(fā)的時候需要注意的地方。注意到 ARouter 是通過注解來進行頁面配置的演痒,并且它的注解是在編譯的時候進行處理的亲轨。所以,我們需要引入arouter-compiler來使用它的編譯時處理功能鸟顺。需要注意的地方是惦蚊,我們只要在公共的模塊中加入arouter-api就可以使用ARouter的API了,但是需要在每個模塊中引入arouter-compiler才能使用編譯時注解讯嫂。也就是說蹦锋,我們需要在每個模塊中都加入arouter-compiler依賴。

模塊獨立

為了能夠?qū)⒏鱾€模塊編譯成一個獨立的 APP欧芽,我們需要在 Gradle 里面做一些配置莉掂。

首先,我們需要在gradle.properties定義一些布爾類型的變量用來判斷各個模塊是作為一個 library 還是 application 進行編譯千扔。這里我的配置如下面的代碼所示憎妙。也就是,我為每個模塊都定義了這么一個布爾類型的變量昏鹃,當(dāng)然尚氛,你也可以只定義一個變量,然后在各個模塊中使用同一個變量來進行判斷洞渤。

isGuokrModuleApp=false
isLiveModuleApp=false
isLayoutModuleApp=false
isLibraryModuleApp=false
isEyepetizerModuleApp=false

然后阅嘶,我們來看一下各個模塊中的 gradle 該如何配置,這里我們以開眼視頻的功能模塊作為例子來進行講解载迄。首先讯柔,一個模塊作為 library 還是 application 是根據(jù)引用的 plugin 來決定的,所以护昧,我們要根據(jù)之前定義的布爾變量來決定使用的 plugin:

if (isEyepetizerModuleApp.toBoolean()) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

假如我們要將某個模塊作為一個獨立的 APP魂迄,那么啟動類你肯定需要配置。這就意味著你需要兩個 AndroidManifest.xml 文件惋耙,一個用于 library 狀態(tài)捣炬,一個用于 application 狀態(tài)熊昌。所以,我們可以在 main 目錄下面再定義一個 AndroidManifest.xml湿酸,然后婿屹,我們在該配置文件中不只指定啟動類,還使用我們定義的 Application推溃。指定 Application 有時候是必須的昂利,比如你需要在各個模塊里面初始化 ARouter 等等。這部分代碼就不給出了铁坎,可以參考源碼蜂奸,這里我們給出一下在 Gradle 里面指定 AndroidManifest.xml 的方式。

如下所示硬萍,我們可以根據(jù)之前定義的布爾值來決定使用哪一個配置文件:

sourceSets {
    main {
        jniLibs.srcDirs = ['libs']
        if (isEyepetizerModuleApp.toBoolean()) {
            manifest.srcFile "src/main/debug/AndroidManifest.xml"
        } else {
            manifest.srcFile "src/main/AndroidManifest.xml"
        }
    }
}

此外扩所,還需要注意的是,如果我們希望在每個模塊中都能應(yīng)用 DataBinding 和 Java 8 的一些特性襟铭,那么你需要在每個模塊里面都加入下面的配置:

// use data binding
dataBinding {
    enabled = true
}
// use java 8 language
compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

對于編譯時注解之類的配置碌奉,我們也需要在每個模塊里面都進行聲明。

完成了以上的配置寒砖,我們只要根據(jù)需要編譯的類型赐劣,修改之前定義的布爾值,來決定是將該模塊編譯成 APP 還是作為類庫來使用即可哩都。

以上就是組件化在 Android 開發(fā)當(dāng)中的應(yīng)用魁兼。

總結(jié)

MVC、MVP和MVVM各有各自的特點漠嵌,可以根據(jù)應(yīng)用開發(fā)的需要選擇適合自己的架構(gòu)模式咐汞。組件化的目的就在于保持各個模塊之間的獨立從而便于分工協(xié)作。它們之間的關(guān)系就是儒鹿,你可以在組件化的各個模塊中應(yīng)用前面三種架構(gòu)模式的一種或者幾種化撕。

獲取更多技術(shù)文章可以直接關(guān)注我的公眾號「Hello 開發(fā)者」,另外感興趣的可以加入技術(shù) QQ 交流群:1018235573.

公眾號信息

以上约炎,感謝閱讀~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末植阴,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子圾浅,更是在濱河造成了極大的恐慌掠手,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狸捕,死亡現(xiàn)場離奇詭異喷鸽,居然都是意外死亡,警方通過查閱死者的電腦和手機灸拍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門做祝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來砾省,“玉大人,你說我怎么就攤上這事混槐〈慷辏” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵纵隔,是天一觀的道長。 經(jīng)常有香客問我炮姨,道長捌刮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任舒岸,我火速辦了婚禮绅作,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蛾派。我一直安慰自己俄认,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布洪乍。 她就那樣靜靜地躺著眯杏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪壳澳。 梳的紋絲不亂的頭發(fā)上岂贩,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機與錄音巷波,去河邊找鬼萎津。 笑死,一個胖子當(dāng)著我的面吹牛抹镊,可吹牛的內(nèi)容都是我干的锉屈。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼垮耳,長吁一口氣:“原來是場噩夢啊……” “哼颈渊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起氨菇,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤儡炼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后查蓉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乌询,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年豌研,在試婚紗的時候發(fā)現(xiàn)自己被綠了妹田。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唬党。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖鬼佣,靈堂內(nèi)的尸體忽然破棺而出驶拱,到底是詐尸還是另有隱情,我是刑警寧澤晶衷,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布蓝纲,位于F島的核電站,受9級特大地震影響晌纫,放射性物質(zhì)發(fā)生泄漏税迷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一锹漱、第九天 我趴在偏房一處隱蔽的房頂上張望箭养。 院中可真熱鬧,春花似錦哥牍、人聲如沸毕泌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撼泛。三九已至,卻和暖如春辩诞,著一層夾襖步出監(jiān)牢的瞬間坎弯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工译暂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留抠忘,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓外永,卻偏偏與公主長得像崎脉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子伯顶,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容