久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

Android 徹底組件化方案實踐

 quasiceo 2017-07-27

Android 徹底組件化方案實踐

2017.07.26mobilehub



作者 | 格竹子

微信公眾號 | mobilehub


1

模塊化,、組件化與插件化


項目發(fā)展到一定程度,隨著人員的增多,,代碼越來越臃腫,,這時候就必須進行模塊化的拆分,。在我看來,模塊化是一種指導(dǎo)理念,,其核心思想就是分而治之,、降低耦合。而在Android工程中如何實施,,目前有兩種途徑,,也是兩大流派,一個是組件化,,一個是插件化,。


提起組件化和插件化的區(qū)別,有一個很形象的圖:


組件化和插件化對比


上面的圖看上去比較清晰,,其實容易導(dǎo)致一些誤解,,有下面幾個小問題,圖中可能說的不太清楚:


  • 組件化是一個整體嗎,?去了頭和胳膊還能存在嗎,?左圖中,似乎組件化是一個有機的整體,,需要所有器官都健在才可以存在,。而實際上組件化的目標之一就是降低整體(app)與器官(組件)的依賴關(guān)系,,缺少任何一個器官app都是可以存在并正常運行的,。

  • 頭和胳膊可以單獨存在嗎,?左圖也沒有說明白,其實答案應(yīng)該是肯定的,。每個器官(組件)可以在補足一些基本功能之后都是可以獨立存活的,。這個是組件化的第二個目標:組件可以單獨運行。

  • 組件化和插件化可以都用右圖來表示嗎,?如果上面兩個問題的答案都是YES的話,,這個問題的答案自然也是YES。每個組件都可以看成一個單獨的整體,,可以按需的和其他組件(包括主項目)整合在一起,,從而完成的形成一個App。

  • 右圖中的小機器人可以動態(tài)的添加和修改嗎,?如果組件化和插件化都用右圖來表示,,那么這個問題的答案就不一樣了。對于組件化來講,,這個問題的答案是部分可以,,也就是在編譯期可以動態(tài)的添加和修改,但是在運行時就沒法這么做了,。而對于插件化,,這個問題的答案很干脆,那就是完全可以,,不論實在編譯期還是運行時,!


本文主要集中講的是組件化的實現(xiàn)思路,對于插件化的技術(shù)細節(jié)不做討論,,我們只是從上面的問答中總結(jié)出一個結(jié)論:組件化和插件化的最大區(qū)別(應(yīng)該也是唯一區(qū)別)就是組件化在運行時不具備動態(tài)添加和修改組件的功能,,但是插件化是可以的。


暫且拋棄對插件化“道德”上的批判,,我認為對于一個Android開發(fā)者來講,,插件化的確是一個福音,這將使我們具備極大的靈活性,。但是苦于目前還沒有一個完全合適,、完美兼容的插件化方案(RePlugin的饑餓營銷做的很好,但還沒看到療效),,特別是對于已經(jīng)有幾十萬代碼量的一個成熟產(chǎn)品來講,,套用任何一個插件化方案都是很危險的工作。所以我們決定先從組件化做起,本著做一個最徹底的組件化方案的思路去進行代碼的重構(gòu),,下面是最近的思考結(jié)果,,歡迎大家提出建議和意見。


2

如何實現(xiàn)組件化


要實現(xiàn)組件化,,不論采用什么樣的技術(shù)路徑,,需要考慮的問題主要包括下面幾個:


  • 代碼解耦,。如何將一個龐大的工程拆分成有機的整體,?

  • 組件單獨運行。上面也講到了,,每個組件都是一個完整的整體,,如何讓其單獨運行和調(diào)試呢?

  • 數(shù)據(jù)傳遞,。因為每個組件都會給其他組件提供的服務(wù),,那么主項目(Host)與組件、組件與組件之間如何傳遞數(shù)據(jù),?

  • UI跳轉(zhuǎn),。UI跳轉(zhuǎn)可以認為是一種特殊的數(shù)據(jù)傳遞,在實現(xiàn)思路上有啥不同,?

  • 組件的生命周期,。我們的目標是可以做到對組件可以按需、動態(tài)的使用,,因此就會涉及到組件加載,、卸載和降維的生命周期。

  • 集成調(diào)試,。在開發(fā)階段如何做到按需的編譯組件,?一次調(diào)試中可能只有一兩個組件參與集成,這樣編譯的時間就會大大降低,,提高開發(fā)效率,。

  • 代碼隔離。組件之間的交互如果還是直接引用的話,,那么組件之間根本沒有做到解耦,,如何從根本上避免組件之間的直接引用呢?也就是如何從根本上杜絕耦合的產(chǎn)生呢,?只有做到這一點才是徹底的組件化,。


代碼解耦


把龐大的代碼進行拆分,Androidstudio能夠提供很好的支持,,使用IDE中的multiple module這個功能,,我們很容易把代碼進行初步的拆分。在這里我們對兩種module進行區(qū)分,


  • 一種是基礎(chǔ)庫library,,這些代碼被其他組件直接引用,。比如網(wǎng)絡(luò)庫module可以認為是一個library。

  • 另一種我們稱之為Component,,這種module是一個完整的功能模塊,。比如讀書或者分享module就是一個Component。


為了方便,,我們統(tǒng)一把library稱之為依賴庫,,而把Component稱之為組件,我們所講的組件化也主要是針對Component這種類型,。而負責拼裝這些組件以形成一個完成app的module,,一般我們稱之為主項目、主module或者Host,,方便起見我們也統(tǒng)一稱為主項目,。


經(jīng)過簡單的思考,我們可能就可以把代碼拆分成下面的結(jié)構(gòu):


組件化簡單拆分


這種拆分都是比較容易做到的,,從圖上看,,讀書、分享等都已經(jīng)拆分組件,,并共同依賴于公共的依賴庫(簡單起見只畫了一個),,然后這些組件都被主項目所引用。讀書,、分享等組件之間沒有直接的聯(lián)系,,我們可以認為已經(jīng)做到了組件之間的解耦。但是這個圖有幾個問題需要指出:


  • 從上面的圖中,,我們似乎可以認為組件只有集成到主項目才可以使用,,而實際上我們的希望是每個組件是個整體,可以獨立運行和調(diào)試,,那么如何做到單獨的調(diào)試呢,?

  • 主項目可以直接引用組件嗎?也就是說我們可以直接使用compile project(:reader)這種方式來引用組件嗎,?如果是這樣的話,,那么主項目和組件之間的耦合就沒有消除啊。我們上面講,,組件是可以動態(tài)管理的,,如果我們刪掉reader(讀書)這個組件,那么主項目就不能編譯了啊,,談何動態(tài)管理呢,?所以主項目對組件的直接引用是不可以的,,但是我們的讀書組件最終是要打到apk里面,不僅代碼要和并到claases.dex里面,,資源也要經(jīng)過meage操作合并到apk的資源里面,,怎么避免這個矛盾呢?

  • 組件與組件之間真的沒有相互引用或者交互嗎,?讀書組件也會調(diào)用分享模塊啊,,而這在圖中根本沒有體現(xiàn)出來啊,那么組件與組件之間怎么交互呢,?


這些問題我們后面一個個來解決,,首先我們先看代碼解耦要做到什么效果,像上面的直接引用并使用其中的類肯定是不行的了,。所以我們認為代碼解耦的首要目標就是組件之間的完全隔離,,我們不僅不能直接使用其他組件中的類,最好能根本不了解其中的實現(xiàn)細節(jié),。只有這種程度的解耦才是我們需要的。


組件的單獨調(diào)試


其實單獨調(diào)試比較簡單,,只需要把apply plugin: 'com.android.library'切換成apply plugin: 'com.android.application'就可以,,但是我們還需要修改一下AndroidManifest文件,因為一個單獨調(diào)試需要有一個入口的actiivity,。


我們可以設(shè)置一個變量isRunAlone,,標記當前是否需要單獨調(diào)試,根據(jù)isRunAlone的取值,,使用不同的gradle插件和AndroidManifest文件,,甚至可以添加Application等Java文件,以便可以做一下初始化的操作,。


為了避免不同組件之間資源名重復(fù),,在每個組件的build.gradle中增加resourcePrefix "xxx_",從而固定每個組件的資源前綴,。下面是讀書組件的build.gradle的示例:


if(isRunAlone.toBoolean()){    

apply plugin: 'com.android.application'

}else{  

apply plugin: 'com.android.library'

}

.....

  resourcePrefix "readerbook_"

  sourceSets {

      main {

          if (isRunAlone.toBoolean()) {

              manifest.srcFile 'src/main/runalone/AndroidManifest.xml'

              java.srcDirs = ['src/main/java','src/main/runalone/java']

              res.srcDirs = ['src/main/res','src/main/runalone/res']

          } else {

              manifest.srcFile 'src/main/AndroidManifest.xml'

          }

      }

  }


通過這些額外的代碼,,我們給組件搭建了一個測試Host,從而讓組件的代碼運行在其中,,所以我們可以再優(yōu)化一下我們上面的框架圖,。


支持單獨調(diào)試的組件化


組件的數(shù)據(jù)傳輸


上面我們講到,主項目和組件,、組件與組件之間不能直接使用類的相互引用來進行數(shù)據(jù)交互,。那么如何做到這個隔離呢?在這里我們采用接口+實現(xiàn)的結(jié)構(gòu),。每個組件聲明自己提供的服務(wù)Service,,這些Service都是一些抽象類或者接口,組件負責將這些Service實現(xiàn)并注冊到一個統(tǒng)一的路由Router中去。如果要使用某個組件的功能,,只需要向Router請求這個Service的實現(xiàn),,具體的實現(xiàn)細節(jié)我們?nèi)徊魂P(guān)心,只要能返回我們需要的結(jié)果就可以了,。這與Binder的C/S架構(gòu)很相像,。


因為我們組件之間的數(shù)據(jù)傳遞都是基于接口編程的,接口和實現(xiàn)是完全分離的,,所以組件之間就可以做到解耦,,我們可以對組件進行替換、刪除等動態(tài)管理,。這里面有幾個小問題需要明確:


  • 組件怎么暴露自己提供的服務(wù)呢,?在項目中我們簡單起見,專門建立了一個componentservice的依賴庫,,里面定義了每個組件向外提供的service和一些公共model,。將所有組件的service整合在一起,是為了在拆分初期操作更為簡單,,后面需要改為自動化的方式來生成,。這個依賴庫需要嚴格遵循開閉原則,以避免出現(xiàn)版本兼容等問題,。

  • service的具體實現(xiàn)是由所屬組件注冊到Router中的,,那么是在什么時間注冊的呢?這個就涉及到組件的加載等生命周期,,我們在后面專門介紹,。

  • 一個很容易犯的小錯誤就是通過持久化的方式來傳遞數(shù)據(jù),例如file,、sharedpreference等方式,,這個是需要避免的。


下面就是加上數(shù)據(jù)傳輸功能之后的架構(gòu)圖:


組件之間的數(shù)據(jù)傳輸


組件之間的UI跳轉(zhuǎn)


可以說UI的跳轉(zhuǎn)也是組件提供的一種特殊的服務(wù),,可以歸屬到上面的數(shù)據(jù)傳遞中去,。不過一般UI的跳轉(zhuǎn)我們會單獨處理,一般通過短鏈的方式來跳轉(zhuǎn)到具體的Activity,。每個組件可以注冊自己所能處理的短鏈的schme和host,,并定義傳輸數(shù)據(jù)的格式。然后注冊到統(tǒng)一的UIRouter中,,UIRouter通過schme和host的匹配關(guān)系負責分發(fā)路由,。


UI跳轉(zhuǎn)部分的具體實現(xiàn)是通過在每個Activity上添加注解,然后通過apt形成具體的邏輯代碼,。這個也是目前Android中UI路由的主流實現(xiàn)方式,。


組件的生命周期


由于我們要動態(tài)的管理組件,,所以給每個組件添加幾個生命周期狀態(tài):加載、卸載和降維,。為此我們給每個組件增加一個ApplicationLike類,,里面定義了onCreate和onStop兩個生命周期函數(shù)。


  1. 加載:上面講了,,每個組件負責將自己的服務(wù)實現(xiàn)注冊到Router中,,其具體的實現(xiàn)代碼就寫在onCreate方法中。那么主項目調(diào)用這個onCreate方法就稱之為組件的加載,,因為一旦onCreate方法執(zhí)行完,,組件就把自己的服務(wù)注冊到Router里面去了,其他組件就可以直接使用這個服務(wù)了,。

  2. 卸載:卸載與加載基本一致,,所不同的就是調(diào)用ApplicationLike的onStop方法,在這個方法中每個組件將自己的服務(wù)實現(xiàn)從Router中取消注冊,。不過這種使用場景可能比較少,,一般適用于一些只用一次的組件。

  3. 降維:降維使用的場景更為少見,,比如一個組件出現(xiàn)了問題,,我們想把這個組件從本地實現(xiàn)改為一個wap頁。降維一般需要后臺配置才生效,,可以在onCreate對線上配置進行檢查,如果需要降維,,則把所有的UI跳轉(zhuǎn)到配置的wap頁上面去,。


一個小的細節(jié)是,主項目負責加載組件,,由于主項目和組件之間是隔離的,,那么主項目如何調(diào)用組件ApplicationLike的生命周期方法呢,目前我們采用的是基于編譯期字節(jié)碼插入的方式,,掃描所有的ApplicationLike類(其有一個共同的父類),,然后通過javassisit在主項目的onCreate中插入調(diào)用ApplicationLike.onCreate的代碼。


我們再優(yōu)化一下組件化的架構(gòu)圖:


組件的生命周期


集成調(diào)試


每個組件單獨調(diào)試通過并不意味著集成在一起沒有問題,,因此在開發(fā)后期我們需要把幾個組件機集成到一個app里面去驗證,。由于我們上面的機制保證了組件之間的隔離,所以我們可以任意選擇幾個組件參與集成,。這種按需索取的加載機制可以保證在集成調(diào)試中有很大的靈活性,,并且可以加大的加快編譯速度。


我們的做法是這樣的,,每個組件開發(fā)完成之后,,發(fā)布一個relaese的aar到一個公共倉庫,,一般是本地的maven庫。然后主項目通過參數(shù)配置要集成的組件就可以了,。所以我們再稍微改動一下組件與主項目之間的連接線,,形成的最終組件化架構(gòu)圖如下:


最終結(jié)構(gòu)圖


代碼隔離


此時在回顧我們在剛開始拆分組件化是提出的三個問題,應(yīng)該說都找到了解決方式,,但是還有一個隱患沒有解決,,那就是我們可以使用compile project(xxx:reader.aar)來引入組件嗎?雖然我們在數(shù)據(jù)傳輸章節(jié)使用了接口+實現(xiàn)的架構(gòu),,組件之間必須針對接口編程,,但是一旦我們引入了reader.aar,那我們就完全可以直接使用到其中的實現(xiàn)類啊,,這樣我們針對接口編程的規(guī)范就成了一紙空文,。千里之堤毀于蟻穴,只要有代碼(不論是有意還是無意)是這么做了,,我們前面的工作就白費了,。


我們希望只在assembleDebug或者assembleRelease的時候把aar引入進來,而在開發(fā)階段,,所有組件都是看不到的,,這樣就從根本上杜絕了引用實現(xiàn)類的問題。我們把這個問題交給gradle來解決,,我們創(chuàng)建一個gradle插件,,然后每個組件都apply這個插件,插件的配置代碼也比較簡單:


//根據(jù)配置添加各種組件依賴,,并且自動化生成組件加載代碼

if (project.android instanceof AppExtension) {

       AssembleTask assembleTask = getTaskInfo(project.gradle.startParameter.taskNames)

       if (assembleTask.isAssemble

               && (assembleTask.modules.contains("all") || assembleTask.modules.contains(module))) {

         //添加組件依賴

          project.dependencies.add("compile","xxx:reader-release@aar")

         //字節(jié)碼插入的部分也在這里實現(xiàn)

       }

}


private AssembleTask getTaskInfo(List<String> taskNames) {

   AssembleTask assembleTask = new AssembleTask();

   for (String task : taskNames) {

       if (task.toUpperCase().contains("ASSEMBLE")) {

           assembleTask.isAssemble = true;

           String[] strs = task.split(":")

           assembleTask.modules.add(strs.length > 1 ? strs[strs.length - 2] : "all");

       }

   }

   return assembleTask

}


3

組件化的拆分步驟和動態(tài)需求


拆分原則


組件化的拆分是個龐大的工程,,特別是從幾十萬行代碼的大工程拆分出去,所要考慮的事情千頭萬緒,。為此我覺得可以分成三步:


  • 從產(chǎn)品需求到開發(fā)階段再到運營階段都有清晰邊界的功能開始拆分,,比如讀書模塊、直播模塊等,,這些開始分批先拆分出去

  • 在拆分中,,造成組件依賴主項目的依賴的模塊繼續(xù)拆出去,比如賬戶體系等

  • 最終主項目就是一個Host,,包含很小的功能模塊(比如啟動圖)以及組件之間的拼接邏輯


組件化的動態(tài)需求


最開始我們講到,,理想的代碼組織形式是插件化的方式,屆時就具備了完備的運行時動態(tài)化,。在向插件化遷徙的過程中,,我們可以通過下面的集中方式來實現(xiàn)編譯速度的提升和動態(tài)更新。


  • 在快速編譯上,,采用組件級別的增量編譯,。在抽離組件之前可以使用代碼級別的增量編譯工具如freeline(但databinding支持較差),、fastdex等

  • 動態(tài)更新方面,暫時不支持新增組件等大的功能改進,??梢耘R時采用方法級別的熱修復(fù)或者功能級別的Tinker等工具,Tinker的接入成本較高,。


4

總結(jié)


本文是筆者在設(shè)計“得到App”的組件化中總結(jié)一些想法,,在設(shè)計之初參考了目前已有的組件化和插件化方案,站在巨人的肩膀上又加了一點自己的想法,,主要是組件化生命周期以及完全的代碼隔離方面,。特別是最后的代碼隔離,不僅要有規(guī)范上的約束(針對接口編程),,更要有機制保證開發(fā)者不犯錯,,我覺得只有做到這一點才能認為是一個徹底的組件化方案。


    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多