Android組件化開發實踐和案例分享

目錄介紹

  • 1.爲何要組件化php

    • 1.1 爲何要組件化
    • 1.2 現階段遇到的問題
  • 2.組件化的概念java

    • 2.1 什麼是組件化
    • 2.2 區分模塊化與組件化
    • 2.3 組件化優點好處
    • 2.4 區分組件化和插件化
    • 2.5 application和library
  • 3.建立組件化框架android

    • 3.1 傳統APP架構圖
    • 3.2 組件化須要考慮問題
    • 3.3 架構設計圖
    • 3.4 組件通訊是經過路由轉發
    • 3.5 解決考慮問題
    • 3.6 業務組件的生命週期
    • 3.7 Fragment通訊難點
  • 4.實際開發案例git

    • 4.1 組件化實踐的開源項目
    • 4.1 如何建立模塊
    • 4.2 如何創建依賴
    • 4.3 如何統一配置文件
    • 4.4 組件化的基礎庫
    • 4.5 組件模式和集成模式如何切換
    • 4.6 組件化解決重複依賴
    • 4.7 組件化注意要點
    • 4.8 組件化時資源名衝突
    • 4.9 組件化開發遇到問題
  • 5.組件間通訊程序員

    • 5.1 選擇那個開源路由庫
    • 5.2 阿里Arouter基礎原理
    • 5.3 使用Arouter注意事項
  • 6.關於其餘github

    • 6.1 參考博客連接
    • 6.2 個人博客介紹
    • 6.3 開源項目地址

1.爲何要組件化

1.1 爲何要組件化

  • APP迭代維護成本增高sql

    • 投資界,新芽,項目工廠等APP自身在飛速發展,版本不斷迭代,新功能不斷增長,業務模塊數量不斷增長,業務上的處理邏輯越變越複雜,同時每一個模塊代碼也變得愈來愈多,這就引起一個問題,所維護的代碼成本愈來愈高,稍微一改動可能就牽一髮而動全身,改個小的功能點就須要迴歸整個APP測試,這就對開發和維護帶來很大的挑戰。
  • 多人組合須要組件化數據庫

    • APP 架構方式是單一工程模式,業務規模擴大,隨之帶來的是團隊規模擴大,那就涉及到多人協做問題,每一個移動端軟件開發人員勢必要熟悉如此之多代碼,若是不按照必定的模塊組件機制去劃分,將很難進行多人協做開發,隨着單一項目變大,並且Andorid項目在編譯代碼方面就會變得很是卡頓,在單一工程代碼耦合嚴重,每修改一處代碼後都須要從新編譯打包測試,致使很是耗時。

1.2 現階段遇到的問題

  • 結合投資界,新芽客戶端分析segmentfault

    • 代碼量膨脹,不利於維護,不利於新功能的開發。項目工程構建速度慢,在一些電腦上寫兩句代碼,從新編譯整個項目,測試的話編譯速度起碼 10-20 分鐘,有的甚至更長。
    • 不一樣模塊之間代碼耦合嚴重,有時候修改一處代碼而牽動許多模塊。每一個模塊之間都有引用第三方庫,但有些第三方庫版本不一致,致使打包APP時候代碼冗餘,容易引發版本衝突。
    • 現有項目基於之前其餘人項目基礎上開發,經手的人次過多,存在着不一樣的代碼風格,項目中代碼規範亂,相似的功能寫法卻不同,致使不統一。

2.組件化的概念

2.1 什麼是組件化

  • 什麼是組件化呢?api

    • 組件(Component)是對數據和方法的簡單封裝,功能單一,高內聚,而且是業務能劃分的最小粒度。
    • 組件化是基於組件可重用的目的上,將一個大的軟件系統按照分離關注點的形式,拆分紅多個獨立的組件,使得整個軟件系統也作到電路板同樣,是單個或多個組件元件組裝起來,哪一個組件壞了,整個系統可繼續運行,而不出現崩潰或不正常現象,作到更少的耦合和更高的內聚。

2.2 區分模塊化與組件化

  • 模塊化

    • 模塊化就是將一個程序按照其功能作拆分,分紅相互獨立的模塊,以便於每一個模塊只包含與其功能相關的內容,模塊咱們相對熟悉,好比登陸功能能夠是一個模塊,搜索功能能夠是一個模塊等等。
  • 組件化

    • 組件化就是更關注可複用性,更注重關注點分離,若是從集合角度來看的話,能夠說每每一個模塊包含了一個或多個組件,或者說模塊是一個容器,由組件組裝而成。簡單來講,組件化相比模塊化粒度更小,二者的本質思想都是一致的,都是把大往小的方向拆分,都是爲了複用和解耦,只不過模塊化更加側重於業務功能的劃分,偏向於複用,組件化更加側重於單一功能的內聚,偏向於解耦。

2.3 組件化優點好處

  • 簡單來講就是提升工做效率,解放生產力,好處以下:

    • 1.提升編譯速度,從而提升並行開發效率。

      • 問題:那麼如何提升編譯速度的呢?組件化框架可使模塊單獨編譯調試,能夠有效地減小編譯的時間。
    • 2.穩定的公共模塊採用依賴庫方式

      • 提供給各個業務線使用,減小重複開發和維護工做量。代碼簡潔,冗餘量少,維護方便,易擴展新功能。
    • 3.每一個組件有本身獨立的版本,能夠獨立編譯、測試、打包和部署。

      • 針對開發程序員多的公司,組件化頗有必要,每一個人負責本身的模塊,能夠較少提交代碼衝突。
      • 爲新業務隨時集成提供了基礎,全部業務可上可下,靈活多變。
      • 各業務線研發能夠互不干擾、提高協做效率,並控制產品質量。
    • 4.避免模塊之間的交叉依賴,作到低耦合、高內聚。
    • 5.引用的第三方庫代碼統一管理,避免版本統一,減小引入冗餘庫。

      • 這個能夠建立一個公共的gradle管理的文件,好比一個項目有十幾個組件,想要改下某個庫或者版本號,總不至於一個個修改吧。這個時候提取公共十分有必要
    • 6.定製項目可按需加載,組件之間能夠靈活組建,快速生成不一樣類型的定製產品。

2.4 區分組件化和插件化

  • 組件化和插件化的區別

    • 組件化不是插件化,插件化是在【運行時】,而組件化是在【編譯時】。換句話說,插件化是基於多APK的,而組件化本質上仍是隻有一個 APK。
    • 組件化和插件化的最大區別(應該也是惟一區別)就是組件化在運行時不具有動態添加和修改組件的功能,可是插件化是能夠的。
  • 組件化的目標

    • 組件化的目標之一就是下降總體工程(app)與組件的依賴關係,缺乏任何一個組件都是能夠存在並正常運行的。app主工程具備和組件進行綁定和解綁的功能。

2.5 application和library

  • 在studio中,對兩種module進行區分,以下所示

    • 一種是基礎庫library,好比常見第三方庫都是lib,這些代碼被其餘組件直接引用。
    • 另外一種是application,也稱之爲Component,這種module是一個完整的功能模塊。好比分享module就是一個Component。
    • 爲了方便,統一把library稱之爲依賴庫,而把Component稱之爲組件,下面所講的組件化也主要是針對Component這種類型。
  • 在項目的build.gradle文件中

    //控制組件模式和集成模式
    if (rootProject.ext.isDouBanApplication) {
        //是Component,能夠獨立運行
        apply plugin: 'com.android.application'
    } else {
        //是lib,被依賴
        apply plugin: 'com.android.library'
    }

3.建立組件化框架

3.1 傳統APP架構圖

  • 傳統APP架構圖

    • 如圖所示,從網上摘來的……
    • image
  • 存在的問題

    • 廣泛使用的 Android APP 技術架構,每每是在一個界面中存在大量的業務邏輯,而業務邏輯中充斥着各類網絡請求、數據操做等行爲,整個項目中也沒有模塊的概念,只有簡單的以業務邏輯劃分的文件夾,而且業務之間也是直接相互調用、高度耦合在一塊兒的。單一工程模型下的業務關係,總的來講就是:你中有我,我中有你,相互依賴,沒法分離。以下圖:
    • image

3.2 組件化須要考慮問題

  • 考慮的問題
  • 分而治之,並行開發,一切皆組件。要實現組件化,不管採用什麼樣的技術方式,須要考慮如下七個方面問題:

    • 代碼解耦。

      • 如何將一個龐大的工程分紅有機的總體?這個須要一步步來了!
      • 對已存在的項目進行模塊拆分,模塊分爲兩種類型,一種是功能組件模塊,封裝一些公共的方法服務等,做爲依賴庫對外提供;另外一種是業務組件模塊,專門處理業務邏輯等功能,這些業務組件模塊最終負責組裝APP。
    • 組件單獨運行。

      • 由於每一個組件都是高度內聚的,是一個完整的總體,如何讓其單獨運行和調試?
      • 經過 Gradle腳本配置方式,進行不一樣環境切換,我本身操做是添加一個boolean值的開關。好比只須要把 Apply plugin: 'com.android.library' 切換成Apply plugin: 'com.android.application' 就能夠獨立運行呢!
      • 須要注意:當切換到application獨立運行時,須要在AndroidManifest清單文件上進行設置,由於一個單獨調試須要有一個入口的Activity。
    • 組件間通訊。

      • 因爲每一個組件具體實現細節都互相不瞭解,但每一個組件都須要給其餘調用方提供服務,那麼主項目與組件、組件與組件之間如何通訊就變成關鍵?
      • 這個我是直接用阿里開源的路由框架,固然你能夠根據須要選擇其餘大廠的開源路由庫。引用阿里的ARouter框架,經過註解方式進行頁面跳轉。
    • 組件生命週期。

      • 這裏的生命週期指的是組件在應用中存在的時間,組件是否能夠作到按需、動態使用、所以就會涉及到組件加載、卸載等管理問題。
    • 集成調試。

      • 在開發階段如何作到按需編譯組件?一次調試中可能有一兩個組件參與集成,這樣編譯時間就會大大下降,提升開發效率。
    • 代碼隔離。

      • 組件之間的交互若是仍是直接引用的話,那麼組件之間根本沒有作到解耦,如何從根本上避免組件之間的直接引用?目前作法是主項目和業務組件都會依賴公共基礎組件庫,業務組件經過路由服務依賴庫按需進行查找,用於不一樣組件之間的通訊。
    • 告別結構臃腫,讓各個業務變得相對獨立,業務組件在組件模式下能夠獨立開發,而在集成模式下又能夠變爲AAR包集成到「APP殼工程」中,組成一個完整功能的 APP。

3.3 架構設計圖

  • 組件化架構圖

    • 業務組件之間是獨立的,互相沒有關聯,這些業務組件在集成模式下是一個個 Library,被 APP 殼工程所依賴,組成一個具備完整業務功能的 APP 應用,可是在組件開發模式下,業務組件又變成了一個個Application,它們能夠獨立開發和調試,因爲在組件開發模式下,業務組件們的代碼量相比於完整的項目差了很遠,所以在運行時能夠顯著減小編譯時間。
    • image

3.4 組件通訊是經過路由轉發

  • 傳統之前工程下模塊

    • 記得剛開始進入Android開發工做時,只有一個app主工程,後期幾乎全部的需求都寫在這個app主工程裏面。只有簡單的以業務邏輯劃分的文件夾,而且業務之間也是直接相互調用、高度耦合在一塊兒的。
    • 致使後期改項目爲組件化的時候十分痛苦,不一樣模塊之間的業務邏輯實在關聯太多,但仍是沒辦法,因而目錄4步驟一步步實踐。終極目標是,告別結構臃腫,讓各個業務變得相對獨立,業務組件在組件模式下能夠獨立開發。
  • 組件化模式下如何通訊

    • 這是組件化工程模型下的業務關係,業務之間將再也不直接引用和依賴,而是經過「路由」這樣一箇中轉站間接產生聯繫。在這個開源項目中,我使用的阿里開源的路由框架。關於Arouter基礎使用和代碼分析,能夠看我這篇博客:Arouter使用與代碼解析
    • image

3.6 業務組件的生命週期

  • 按照理想狀態的來看待的話

    • 各個業務組件之間沒有任何依賴關係,這時咱們能夠把每一個獨立的業務組件當作一個可運行的app,因此業務組件的生命週期和應與獨立的app保持一致。

3.7 Fragment通訊難點

  • 在網上看到不少博客說,如何拆分組件,按模塊拆分,或者按照功能拆分。但不多有提到fragment在拆分組件時的疑問,這個讓我很奇怪。
  • 先來講一個業務需求,好比一個購物商城app,有4個模塊,作法通常是一個activity+4個fragment,這個你們都很熟悉,這四個模塊分別是:首頁,發現,購物車,個人。而後這幾個頁面是用fragment寫的,共用一個宿主activity,那麼在作組件化的時候,我想把它按照業務拆分紅首頁,發現,購物車和個人四個獨立的業務模塊。
  • 遇到疑問:

    • 若是是拆分紅四個獨立的業務模塊,那麼對應的fragment確定要放到對應的組件中,那麼這樣操做,當主工程與該業務組件解綁的狀況下,如何拿到fragment和傳遞參數進行通訊。
    • Fragment 中 開啓Activity帶requestCode,開啓的Activity關閉後,不會回調Fragment中的onActivityResult。只會調用Fragment 所在Activity的onActivityResult。
    • 多fragment單activity攔截器無論用,難道只能用於攔截activity的跳轉?那若是是要實現登陸攔截的話,那不是隻能在PathReplaceService中進行了?
  • 網絡解決辦法

    • 第一個疑問:因爲我使用阿里路由,因此我看到zhi1ong大佬說:用Router跳轉到這個Activity,而後帶一個參數進去,比方說tab=2,而後本身在onCreate裏面自行切換。但後來嘗試,仍是想問問廣大程序員有沒有更好的辦法。
    • 第二個疑問:仍是zhi1ong大佬說,經過廣播,或者在Activity中轉發這個事件,比方說讓Fragment統一依賴一個接口,而後在Activity中轉發。

4.實際開發案例

4.1 組件化實踐的開源項目

  • 關於組件化開發一點感想

    • 關於網上有許多關於組件化的博客,講解了什麼是組件化,爲什麼要組件化,以及組件化的好處。大多數文章提供了組件化的思路,給我着手組件化開發提供了大量的便利。感謝前輩大神的分享!雖然有一些收穫,可是不多有文章可以給出一個總體且有效的方案,或者一個具體的Demo。
    • 可是畢竟看博客也是爲了實踐作準備,當着手將以前的開源案例改版成組件化案例時,出現了大量的問題,也解決了一些問題。主要是學些了組件化開發流程。
    • 大多數公司慢慢着手組件化開發,在小公司,有的人因爲以前沒有作過組件化開發,嘗試組件化也是挺好的;在大公司,有的人一去只是負責某個模塊,可能剛開始組件化已經有人弄好了,那學習實踐組件化那更快一些。業餘實踐,改版以前開源項目,寫了這篇博客,耗費我很多時間,要是對你有些幫助,那我就很開心呢。因爲我也是個小人物,因此寫的很差,勿噴,歡迎提出建議!
  • 關於組件化開源項目

    • 項目總體架構模式採用:組件化+MVP+Rx+Retrofit+design+Dagger2+VLayout+X5
    • 包含的模塊:wanAndroid【kotlin】+乾貨集中營+知乎日報+番茄Todo+精選新聞+豆瓣音樂電影小說+小說讀書+簡易記事本+搞笑視頻+經典遊戲+其餘更多等等
    • 此項目屬於業餘時間練手的項目,接口數據來源均來自網絡,若是存在侵權狀況,請第一時間告知。本項目僅作學習交流使用,API數據內容全部權歸原做公司全部,請勿用於其餘用途。
    • 關於開源組件化的項目地址:https://github.com/yangchong2...

4.1 如何建立模塊

  • 根據3.3 架構設計圖能夠知道
  • 主工程:

    • 除了一些全局配置和主 Activity 以外,不包含任何業務代碼。有的也叫作空殼app,主要是依賴業務組件進行運行。
  • 業務組件:

    • 最上層的業務,每一個組件表示一條完整的業務線,彼此之間互相獨立。原則上來講:各個業務組件之間不能有直接依賴!全部的業務組件均須要能夠作到獨立運行的效果。對於測試的時候,須要依賴多個業務組件的功能進行集成測試的時候。可使用app殼進行多組件依賴管理運行。
    • 該案例中分爲:幹活集中營,玩Android,知乎日報,微信新聞,頭條新聞,搞笑視頻,百度音樂,個人記事本,豆瓣音樂讀書電影,遊戲組件等等。
  • 功能組件:

    • 該案例中分爲,分享組件,評論反饋組件,支付組件,畫廊組件等等。同時注意,可能會涉及多個業務組件對某個功能組件進行依賴!
  • 基礎組件:

    • 支撐上層業務組件運行的基礎業務服務。此部分組件爲上層業務組件提供基本的功能支持。
    • 該案例中:在基礎組件庫中主要有,網絡請求,圖片加載,通訊機制,工具類,分享功能,支付功能等等。固然,我把一些公共第三方庫放到了這個基礎組件中!

4.2 如何創建依賴

  • 關於工程中組件依賴結構圖以下所示

    • image
  • 業務模塊下完整配置代碼

    //控制組件模式和集成模式
    if (rootProject.ext.isGankApplication) {
        apply plugin: 'com.android.application'
    } else {
        apply plugin: 'com.android.library'
    }
    
    android {
        compileSdkVersion rootProject.ext.android["compileSdkVersion"]
        buildToolsVersion rootProject.ext.android["buildToolsVersion"]
    
    
        defaultConfig {
            minSdkVersion rootProject.ext.android["minSdkVersion"]
            targetSdkVersion rootProject.ext.android["targetSdkVersion"]
            versionCode rootProject.ext.android["versionCode"]
            versionName rootProject.ext.android["versionName"]
    
            if (rootProject.ext.isGankApplication){
                //組件模式下設置applicationId
                applicationId "com.ycbjie.gank"
            }
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = [AROUTER_MODULE_NAME: project.getName()]
                }
            }
        }
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    
        //jdk1.8
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
    
        sourceSets {
            main {
                if (rootProject.ext.isGankApplication) {
                    manifest.srcFile 'src/main/module/AndroidManifest.xml'
                } else {
                    manifest.srcFile 'src/main/AndroidManifest.xml'
                }
                jniLibs.srcDirs = ['libs']
            }
        }
    
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation project(':library')
        annotationProcessor rootProject.ext.dependencies["router-compiler"]
    }

4.3 如何統一配置文件

  • 因爲組件化實踐中模塊比較多,所以配置gradle,添加依賴庫時,須要考慮簡化工做。那麼究竟如何作呢?
  • 第一步,首先在項目根目錄下建立一個yc.gradle文件。實際開發中只須要更改該文件中版本信息便可。

    • 我在網上看到的絕大多數案例,都是經過一個開關控件組件模式和集成模式的切換,可是這裏我配置了多個組件的開關,分別控制對應的組件切換狀態。
    ext {
    
        isApplication = false  //false:做爲Lib組件存在, true:做爲application存在
        isAndroidApplication = false  //玩Android模塊開關,false:做爲Lib組件存在, true:做爲application存在
        isLoveApplication = false  //愛意表達模塊開關,false:做爲Lib組件存在, true:做爲application存在
        isVideoApplication = false  //視頻模塊開關,false:做爲Lib組件存在, true:做爲application存在
        isNoteApplication = false  //記事本模塊開關,false:做爲Lib組件存在, true:做爲application存在
        isBookApplication = false  //book模塊開關,false:做爲Lib組件存在, true:做爲application存在
        isDouBanApplication = false  //豆瓣模塊開關,false:做爲Lib組件存在, true:做爲application存在
        isGankApplication = false  //乾貨模塊開關,false:做爲Lib組件存在, true:做爲application存在
        isMusicApplication = false  //音樂模塊開關,false:做爲Lib組件存在, true:做爲application存在
        isNewsApplication = false  //新聞模塊開關,false:做爲Lib組件存在, true:做爲application存在
        isToDoApplication = false  //todo模塊開關,false:做爲Lib組件存在, true:做爲application存在
        isZhiHuApplication = false  //知乎模塊開關,false:做爲Lib組件存在, true:做爲application存在
        isOtherApplication = false  //其餘模塊開關,false:做爲Lib組件存在, true:做爲application存在
        
        android = [
                   compileSdkVersion       : 28,
                   buildToolsVersion       : "28.0.3",
                   minSdkVersion           : 17,
                   targetSdkVersion        : 28,
                   versionCode             : 22,
                   versionName             : "1.8.2"    //必須是int或者float,不然影響線上升級
        ]
    
        version = [
                   androidSupportSdkVersion: "28.0.0",
                   retrofitSdkVersion      : "2.4.0",
                   glideSdkVersion         : "4.8.0",
                   canarySdkVersion        : "1.5.4",
                   constraintVersion       : "1.0.2"
        ]
    
        dependencies = [
                    //support
                    "appcompat-v7"             : "com.android.support:appcompat-v7:${version["androidSupportSdkVersion"]}",
                    "multidex"                 : "com.android.support:multidex:1.0.1",
                    //network
                    "retrofit"                 : "com.squareup.retrofit2:retrofit:${version["retrofitSdkVersion"]}",
                    "retrofit-converter-gson"  : "com.squareup.retrofit2:converter-gson:${version["retrofitSdkVersion"]}",
                    "retrofit-adapter-rxjava"  : "com.squareup.retrofit2:adapter-rxjava2:${version["retrofitSdkVersion"]}",
                    //這裏省略一部分代碼
            ]
    }
  • 第二步,而後在項目中的lib【注意這裏是放到基礎組件庫的build.gradle】中添加代碼,以下所示

    apply plugin: 'com.android.library'
    
    android {
        compileSdkVersion rootProject.ext.android["compileSdkVersion"]
        buildToolsVersion rootProject.ext.android["buildToolsVersion"]
    
    
        defaultConfig {
            minSdkVersion rootProject.ext.android["minSdkVersion"]
            targetSdkVersion rootProject.ext.android["targetSdkVersion"]
            versionCode rootProject.ext.android["versionCode"]
            versionName rootProject.ext.android["versionName"]
        }
    }
    
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        api rootProject.ext.dependencies["appcompat-v7"]
        api rootProject.ext.dependencies["design"]
        api rootProject.ext.dependencies["palette"]
        api rootProject.ext.dependencies["glide"]
        api (rootProject.ext.dependencies["glide-transformations"]){
            exclude module: 'glide'
        }
        annotationProcessor rootProject.ext.dependencies["glide-compiler"]
        api files('libs/tbs_sdk_thirdapp_v3.2.0.jar')
        api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
        
        //省略部分代碼
    }
  • 第三步,在其餘model中添加依賴

    • implementation project(':library')便可。

4.4 組件化的基礎庫

  • 基礎庫組件封裝

    • 基礎庫組件封裝庫中主要包括開發經常使用的一些框架。能夠直接看個人項目更加直觀!
    • 一、網絡請求(採用Retrofit+RxJava框架),攔截器
    • 二、圖片加載(策略模式,Glide與Picasso之間能夠切換)
    • 三、通訊機制(RxBus),路由ARouter簡單封裝工具類(不一樣model之間通訊)
    • 四、mvp框架,經常使用的base類,好比BaseActivity,BaseFragment等等
    • 五、通用的工具類,好比切割圓角,動畫工具類等等
    • 六、自定義view(包括對話框,ToolBar佈局,圓形圖片等view的自定義)
    • 七、共有的shape,drawable,layout,color等資源文件
    • 八、全局初始化異步線程池封裝庫,各個組件都可以用到
  • 組件初始化

    • 好比,你將該案例中的新聞組件切換成獨立運行的app,那麼因爲新聞跳轉詳情頁須要使用到x5的WebView,所以須要對它進行初始化。最剛開始作法是,爲每個能夠切換成app的組件配置一個獨立的application,而後初始化一些該組件須要初始化的任務。可是這麼作,有一點很差,不是很方便管理。後來看了知乎組件化實踐方案後,該方案提出,開發了一套多線程初始化框架,每一個組件只要新建若干個啓動 Task 類,並在 Task 中聲明依賴關係。可是具體怎麼用到代碼中後期有待實現!
  • 如何簡化不熟悉組件化的人快速適應組件獨立運行

    • 設置多個組件開關,須要切換那個組件就改那個。若是設置一個開關,要麼把全部組件切成集成模式,要麼把全部組件切成組件模式,有點容易出問題。更多能夠往下看!
  • 嚴格限制公共基礎組件的增加

    • 隨着開發不斷進行,要注意不要往基礎公共組件加入太多內容。而是應該減少體積!假若是基礎組件過於龐大,那麼運行組件也是比較緩慢的!

4.5 組件模式和集成模式如何切換

  • 在玩Android組件下的build.gradle文件,其餘組件相似。

    • 經過一個開關來控制這個狀態的切換,module若是是一個庫,會使用com.android.library插件;若是是一個應用,則使用com.android.application插件
    //控制組件模式和集成模式
    if (rootProject.ext.isAndroidApplication) {
        apply plugin: 'com.android.application'
    } else {
        apply plugin: 'com.android.library'
    }
  • 集成模式以下所示

    • 首先須要在yc.gradle文件中設置 isApplication=false。Sync下後,發現該組件是library
    ext {
        isAndroidApplication = false  //false:做爲Lib組件存在, true:做爲application存在
  • 組件模式以下所示

    • 首先須要在yc.gradle文件中設置 isApplication=true。Sync下後,發現該組件是application,便可針對模塊進行運行
    ext {
        isAndroidApplication = true  //false:做爲Lib組件存在, true:做爲application存在
  • 須要注意的地方,這個很重要

    • 首先看看網上絕大多數的做法,很是感謝這些大神的無私奉獻!可是我以爲多個組件用一個開關控制也能夠,可是sourceSets裏面切換成組件app時,能夠直接不用下面這麼麻煩,能夠複用java和res文件。

      • image
    • 接下來看看個人作法:
    • 下面這個配置十分重要。也就是說當該玩Android組件從library切換到application時,因爲能夠做爲獨立app運行,因此序意設置applicationId,而且配置清單文件,以下所示!
    • 在 library 和 application 之間切換,manifest文件也須要提供兩套
    android {
        defaultConfig {
            if (rootProject.ext.isAndroidApplication){
                //組件模式下設置applicationId
                applicationId "com.ycbjie.android"
            }
        }
        sourceSets {
            main {
                if (rootProject.ext.isAndroidApplication) {
                    manifest.srcFile 'src/main/module/AndroidManifest.xml'
                } else {
                    manifest.srcFile 'src/main/AndroidManifest.xml'
                }
                jniLibs.srcDirs = ['libs']
            }
        }
    }
    • 具體在項目中以下所示
    • image

4.6 組件化解決重複依賴

  • 重複依賴問題說明

    • 重複依賴問題其實在開發中常常會遇到,好比項目 implementation 了一個A,而後在這個庫裏面又 implementation 了一個B,而後你的工程中又 implementation 了一個一樣的B,就依賴了兩次。
    • 默認狀況下,若是是 aar 依賴,gradle 會自動幫咱們找出新版本的庫而拋棄舊版本的重複依賴。可是若是使用的是project依賴,gradle並不會去去重,最後打包就會出現代碼中有重複的類了。
  • 解決辦法,舉個例子

    api(rootProject.ext.dependencies["logger"]) { 
        exclude module: 'support-v4'//根據組件名排除 
        exclude group: 'android.support.v4'//根據包名排除 
    }

4.7 組件化注意要點

  • 業務組件之間聯動致使耦合嚴重

    • 好比,實際開發中,購物車和首頁商品分別是兩個組件。可是遇到產品需求,好比過節作個活動,發個購物券之類的需求,因爲購物車和商品詳情頁都有活動,所以會形成組件常常會發生聯動。假若前期準備不足,隨着時間的推移,各個業務線的代碼邊界會像組件化以前的主工程同樣逐漸劣化,耦合會愈來愈嚴重。
    • 第一種解決方式:使用 sourceSets 的方式將不一樣的業務代碼放到不一樣的文件夾,可是 sourceSets 的問題在於,它並不能限制各個 sourceSet 之間互相引用,因此這種方式並不太友好!
    • 第二種解決方式:抽取需求爲工具類,經過不一樣組件傳值而達到調用關係,這樣只須要改工具類便可改需求。可是這種只是符合需求同樣,可是用在不一樣模塊的場景。
  • 組件化開發之數據庫分離

    • 好比,我如今開發的視頻模塊想要給別人用,因爲緩存之類須要用到數據庫,難道還要把這個lib還得依賴一個體積較大的第三方數據庫?可是使用系統原生sql數據庫又不太方便,怎麼辦?暫時我也沒找到辦法……

4.8 組件化時資源名衝突

  • 資源名衝突有哪些?

    • 好比,color,shape,drawable,圖片資源,佈局資源,或者anim資源等等,都有可能形成資源名稱衝突。這是爲什麼了,有時候你們負責不一樣的模塊,若是不是按照統一規範命名,則會偶發出現該問題。
    • 尤爲是若是string, color,dimens這些資源分佈在了代碼的各個角落,一個個去拆,很是繁瑣。其實大可沒必要這麼作。由於android在build時,會進行資源的merge和shrink。res/values下的各個文件(styles.xml需注意)最後都只會把用到的放到intermediate/res/merged/../valus.xml,無用的都會自動刪除。而且最後咱們可使用lint來自動刪除。因此這個地方不要耗費太多的時間。
  • 解決辦法

    • 這個問題也不是新問題了,第三方SDK基本都會遇到,能夠經過設置 resourcePrefix 來避免。設置了這個值後,你全部的資源名必須以指定的字符串作前綴,不然會報錯。可是 resourcePrefix 這個值只能限定 xml 裏面的資源,並不能限定圖片資源,全部圖片資源仍然須要你手動去修改資源名。
  • 我的建議

    • 將color,shape等放到基礎庫組件中,由於全部的業務組件都會依賴基礎組件庫。在styles.xml需注意,寫屬性名字的時候,必定要加上前綴限定詞。假如說不加的話,有可能會在打包成aar後給其餘模塊使用的時候,會出現屬性名名字重複的衝突,爲何呢?由於BezelImageView這個名字根本不會出如今intermediate/res/merged/../valus.xml裏, 因此不要覺得這是屬性的限定詞!

4.9 組件化開發遇到問題

  • 如何作到各個組件化模塊能獲取到全局上下文

    • 情景再現

      • 好比,剛開始線上項目是在app主工程裏建立的單利,那麼在lib中或者後期劃分的組件化,是沒法拿到主工程的application類中的上下文。這個時候能夠
    • 解決辦法

      • 很容易,在lib裏寫一個Utils工具類,而後在主工程application中初始化Utils.init(this),這樣就能夠在lib和全部業務組件[已經依賴公共基礎組件庫]中拿到全局上下文呢!
  • butterKnife使用問題

    • 儘管網上有很多博客說能夠解決butterKnife在不一樣組件之間的引用。可是我在實際開發時,遇到組件模式和集成模式切換狀態時,致使出現編譯錯誤問題。要是那位在組件化中解決butterKnife引用問題,能夠告訴我,很是感謝!
  • 當組件化是lib時

    • 不能使用switch(R.id.xx),須要使用if..else來代替。
  • 不要亂髮bus消息

    • 若是項目中大量的使用eventbus,那麼會看到一個類中有大量的onEventMainThread()方法,寫起來很爽,閱讀起來很痛苦。
    • 雖說,前期使用EventBus或者RxBus發送消息來實現組件間通訊十分方便和簡單,可是隨着業務增大,和後期不斷更新,有的還通過多個程序員前先後後修改,會使代碼閱讀量下降。項目中發送這個Event的地方很是多,接收這個Event的地方也不少。在後期想要改進爲組件化開發,而進行代碼拆分時,都不敢輕舉妄動,生怕哪些事件沒有被接收。
  • 頁面跳轉存在問題

    • 若是一個頁面須要登錄狀態才能夠查看,那麼會寫if(isLogin()){//跳轉頁面}else{//跳轉到登陸頁面},每次操做都要寫這些個相同的邏輯。
    • 原生startActivity跳轉,沒法監聽到跳轉的狀態,好比跳轉錯誤,成功,異常等問題。
    • 後時候,後臺會控制從點擊按鈕【不一樣場景下】跳轉到不一樣的頁面,假如後臺配置信息錯誤,或者少了參數,那麼跳轉可能不成功或者致使崩潰,這個也沒有一個好的處理機制。
    • 阿里推出的開源框架Arouter,即可以解決頁面跳轉問題,能夠添加攔截,或者即便後臺配置參數錯誤,當監聽到跳轉異常或者跳轉錯誤時的狀態,能夠直接默認跳轉到首頁。我在該開源案例就是這麼作的!
  • 關於跳轉參數問題

    • 先來看一下這種代碼寫法,這種寫法本沒有問題,只是在多人開發時,若是別人想要跳轉到你開發模塊的某個頁面,那麼就容易傳錯值。建議將key這個值,寫成靜態常量,放到一個專門的類中。方便本身,也方便他人。

      //跳轉
      intent.setClass(this,CommentActivity.class);
      intent.putExtra("id",id);
      intent.putExtra("allNum",allNum);
      intent.putExtra("shortNum",shortNum);
      intent.putExtra("longNum",longNum);
      startActivity(intent);
      
      
      //接收
      Intent intent = getIntent();
      int allNum = intent.getExtras().getInt("allNum");
      int shortNum = intent.getExtras().getInt("shortNum");
      int longNum = intent.getExtras().getInt("longNum");
      int id = intent.getExtras().getInt("id");

5.組件間通訊

5.1 選擇那個開源路由庫

  • 比較有表明性的組件化開源框架有獲得獲得DDComponentForAndroid、阿里Arouter、聚美Router 等等。

    • 獲得DDComponentForAndroid:一套完整有效的android組件化方案,支持組件的組件徹底隔離、單獨調試、集成調試、組件交互、UI跳轉、動態加載卸載等功能。
    • 阿里Arouter:對頁面、服務提供路由功能的中間件,簡單且夠用好用,網上的使用介紹博客也不少,在該組件化案例中,我就是使用這個。
    • Router:一款單品、組件化、插件化全支持的路由框架

5.2 阿里Arouter基礎原理

  • 這裏只是說一下基礎的思路

    • 在代碼里加入的@Route註解,會在編譯時期經過apt生成一些存儲path和activityClass映射關係的類文件,而後app進程啓動的時候會拿到這些類文件,把保存這些映射關係的數據讀到內存裏(保存在map裏),而後在進行路由跳轉的時候,經過build()方法傳入要到達頁面的路由地址。

      • 添加@Route註解而後編譯一下,就能夠生成這個類,而後看一下這個類。以下所示:
      /**
       * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
      public class ARouter$$Group$$video implements IRouteGroup {
        @Override
        public void loadInto(Map<String, RouteMeta> atlas) {
          atlas.put("/video/VideoActivity", RouteMeta.build(RouteType.ACTIVITY, VideoActivity.class, "/video/videoactivity", "video", null, -1, -2147483648));
        }
      }
    • ARouter會經過它本身存儲的路由表找到路由地址對應的Activity.class(activity.class = map.get(path)),而後new Intent(),當調用ARouter的withString()方法它的內部會調用intent.putExtra(String name, String value),調用navigation()方法,它的內部會調用startActivity(intent)進行跳轉,這樣即可以實現兩個相互沒有依賴的module順利的啓動對方的Activity了。

      • 看_ARouter類中的 _navigation方法代碼,在345行。
      private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
          final Context currentContext = null == context ? mContext : context;
      
          switch (postcard.getType()) {
              case ACTIVITY:
                  // Build intent
                  final Intent intent = new Intent(currentContext, postcard.getDestination());
                  intent.putExtras(postcard.getExtras());
      
                  // Set flags.
                  int flags = postcard.getFlags();
                  if (-1 != flags) {
                      intent.setFlags(flags);
                  } else if (!(currentContext instanceof Activity)) {    // Non activity, need less one flag.
                      intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                  }
      
                  // Set Actions
                  String action = postcard.getAction();
                  if (!TextUtils.isEmpty(action)) {
                      intent.setAction(action);
                  }
      
                  // Navigation in main looper.
                  runInMainThread(new Runnable() {
                      @Override
                      public void run() {
                          startActivity(requestCode, currentContext, intent, postcard, callback);
                      }
                  });
      
                  break;
              case PROVIDER:
                  //這裏省略代碼
              case BOARDCAST:
              case CONTENT_PROVIDER:
              case FRAGMENT:
                  //這裏省略代碼
              case METHOD:
              case SERVICE:
              default:
                  return null;
          }
          return null;
      }

5.3 使用Arouter注意事項

  • 使用阿里路由抽取工具類,方便後期維護!

    • 首先看一下網絡上有一種寫法。

      //首先經過註解添加下面代碼
      @Route(path = "/test/TestActivity")
      public class TestActivity extends BaseActivity {
      
      }
      
      //跳轉
      ARouter.getInstance().inject("/test/TestActivity");
    • 優化後的寫法

      • 下面這種作法,是方便後期維護。
      //存放全部的路由路徑常量
      public class ARouterConstant {
          //跳轉到視頻頁面
          public static final String ACTIVITY_VIDEO_VIDEO = "/video/VideoActivity";
          //省略部分diamagnetic
      }
      
      //存放全部的路由跳轉,工具類
      public class ARouterUtils {
          /**
           * 簡單的跳轉頁面
           * @param string                string目標界面對應的路徑
           */
          public static void navigation(String string){
              if (string==null){
                  return;
              }
              ARouter.getInstance().build(string).navigation();
          }
      }
      
      //調用
      @Route(path = ARouterConstant.ACTIVITY_VIDEO_VIDEO)
      public class VideoActivity extends BaseActivity {
      
      }
      ARouterUtils.navigation(ARouterConstant.ACTIVITY_VIDEO_VIDEO);

06.關於其餘內容介紹

6.1 關於博客彙總連接

6.1 參考博客連接

6.2 關於個人博客

6.3 開源項目地址

組件化實踐項目開源地址:https://github.com/yangchong2...
相關文章
相關標籤/搜索