1. 程式人生 > >懶人模式開啟Android模組自動化Api之旅

懶人模式開啟Android模組自動化Api之旅

在將業務進行模組化時,避免不了模組頁面路由和模組通訊, 大多數我們會用到ARouter,EventBus三方庫。 模組化過程中有一個尷尬的問題擺在面前:Event事件、Router path放在哪裡? 因為很多業務module都需要收發Event事件,進行頁面路由,所以只能將Event, Router path下沉到基礎庫。 這樣導致的結果是基礎庫越來越大,至多 把Event事件、Router path擺放在獨立的module,然後基礎庫依賴這個庫,如下圖所示:

 

我們希望業務模組傳送的事件,註解使用的Router path都在模組自己這裡定義,而不是下層到基礎庫,當其他module需要路由、事件、 介面就暴露出來。關於這點《微信Android模組化架構重構實踐》 也提到了這件事情,並且自創性的使用了一種叫“.api”化的方式來解決這件事情。原理是在編譯期將公用介面下沉到基礎庫同層級, 供其他module使用,而這段程式碼的維護仍然放到非基礎庫中。這種base庫不會膨脹,程式碼維護的責任制更明確,確定挺不錯。如下圖:

 

在ModuleA,B把XXXBusEvents、XXXRouterParams,暴露的公用介面檔案字尾名以.api (並不要求一定.api後者,只要跟後續的自動Api化外掛或者指令碼一致就行)命名, rebuild之後自動生成ModuleA-api,ModuleB-api 模組,ModuleA,B也會自動新增各自對應 api module依賴。

講完了原理,下面就可以實現,這裡使用ARouter,EventBus,只對Java檔案進行Api化,步驟如下:

新建工程,建立base、moduleA、moduleB 模組在moudleA,moduleB中建立api檔案

 

預設情況下,Android stuio 是不能識別.api檔案,如果想編輯.api字尾的java檔案, 為了能讓Android Studio繼續高亮該怎麼辦?可以在File Type中把.api作為java檔案型別,操作如下圖:

 

設定好後,可以在.api檔案中像java檔案一樣愉快擼程式碼了,其他類可以引用.api中的類。

檢視setting.gradle檔案指令碼如下:

1 include ':app', ':base',':modulea',':moduleb'

 

include 4個module,做個測試,在setting.gradle include test,同步後,test目錄下只有iml檔案, 沒有build.gradle、AndroidManifest.xml等檔案,所以除了拷貝.api檔案到對應目錄並重命名為.java, 還需要額外建立這兩個檔案,這裡我事先在base module中準備了通用module的build.gradle檔案, 拷貝到對應目錄即可,AndroidManifest.xml就拷貝base module目錄下的,指令碼實現如下:

 1 def includeWithApi(String moduleName,String baseModuleName) {
 2     //先正常載入這個模組
 3     include(moduleName)
 4 
 5     //找到這個模組的路徑
 6     String originDir = project(moduleName).projectDir
 7     //這個是新的路徑
 8     String targetDir = "${originDir}-api"
 9     //新模組的路徑
10     def sdkName = "${project(moduleName).name}-api"
11     //新模組名字
12     String apiName="${moduleName.substring(1,moduleName.length())}-api"
13 
14 
15     //這個是公共模組的位置,我預先放了一個 ApiBuildGralde.gradle 檔案進去
16     String apiGradle = project(baseModuleName).projectDir
17 
18     // 每次編譯刪除之前的檔案
19     deleteDir(targetDir)
20 
21     //複製.api檔案到新的路徑
22     copy() {
23         from originDir
24         into targetDir
25         exclude '**/build/'
26         exclude '**/res/'
27         include '**/*.api'
28     }
29 
30     //直接複製公共模組的AndroidManifest檔案到新的路徑,作為該模組的檔案
31     copy() {
32         from "${apiGradle}/src/main/AndroidManifest.xml"
33         into "${targetDir}/src/main/"
34     }
35 
36     //file("${targetDir}/src/main/java/com/dhht/${apiName}/").mkdirs()
37 
38     //修改AndroidManifest檔案
39     //fileReader("${targetDir}/src/main/AndroidManifest.xml",apiName);
40 
41     //複製 gradle檔案到新的路徑,作為該模組的gradle
42     copy() {
43         from "${apiGradle}/ApiBuildGralde.gradle"
44         into "${targetDir}/"
45     }
46 
47     //刪除空資料夾
48     deleteEmptyDir(new File(targetDir))
49 
50     //重新命名一下gradle
51     def build = new File(targetDir + "/ApiBuildGralde.gradle")
52     if (build.exists()) {
53         build.renameTo(new File(targetDir + "/build.gradle"))
54     }
55 
56     // 重新命名.api檔案,生成正常的.java檔案
57     renameApiFiles(targetDir, '.api', '.java')
58 
59     //正常載入新的模組
60     include ":$sdkName"
61 
62 }

 

修改setting.gradle檔案如下:

1 include ':app', ':base'
2 includeWithApi(":modulea",":base")
3 includeWithApi(":moduleb",":base")

 

rebuild後,就可以看到moduleA-api,moduleB-api,並有對應的java檔案如下圖:

 

新增moduleA路由到moduleB,moduleB給moduleA傳送事件邏輯,進行打包,會報如下錯誤: 

 

很顯然,ARouter註解處理器無法識別.api檔案,path置為null處理,在moduleA,B新增對應的***-api模組依賴,就可以打包成功了。

奔著偷懶的原則,不想每次手動新增***-api模組依賴,自動動態新增依賴,實現gradle指令碼如下:

 1 ext{
 2      //自動新增***-api依賴
 3     autoImportApiDependency = {extension -> //extension project物件
 4         def children = project.rootProject.childProjects
 5         //遍歷所有child project
 6         children.each {child ->
 7             //判斷 是否同時存在 *** module 和 ***-api module
 8             if(child.key.contains("-api") && children.containsKey(child.key.substring(0,child.key.length() - 4))){
 9                 print "\n"
10 
11                 def targetKey = child.key.substring(0,child.key.length() - 4)
12                 def targetProject = children[targetKey]
13 
14                 targetProject.afterEvaluate {
15 
16                     print '*********************\n'
17                     print targetProject.dependencies
18                     //通過列印 所有dependencies,推斷需要新增如下兩個依賴
19                     targetProject.dependencies.add("implementation",targetProject.dependencies.create(project(":" + child.key)))
20                     targetProject.dependencies.add("implementationDependenciesMetadata",targetProject.dependencies.create(project(":" + child.key)))
21 
22                     //列印 module 新增的依賴
23                     targetProject.configurations.each {configuration ->
24                         print '\n---------------------------------------\n'
25                         configuration.allDependencies.each { dependency ->
26 
27                             print configuration.name + "--->" +dependency.group + ":" + dependency.name + ":" + dependency.version +'\n'
28                         }
29 
30                     }
31 
32 
33 
34                     print '*********************\n'
35                 }
36 
37             }
38 
39 
40         }
41     }
42 }

 

autoImportApiDependency 方法封裝在Config.gradle,在根build.gradle中呼叫:

1 apply from: 'Config.gradle'
2 ext.autoImportApiDependency(this)

 

可以正常打包,併成功運行了。

 

遇坑集錦:

1.kotlin整合ARouter,儘管設定了AROUTER_MODULE_NAME,依然報如下錯誤: ARouter::Compiler An exception is encountered, [null] 可以考慮是否是gradle和 kotlin 版本的問題。

2.業務模組moduleA處於整合模式時,即整合到App殼工程中去,也會將單一模組做 成App啟動的原始碼和資源打包apk中,儘管設定了sourceSets,也沒效果。

 

問題就出在debug資料夾的名字,把debug資料夾改成其他名字,就沒有這個問題了,是不是很奇怪!沒去究其原因。

傳送門:懶人模式開啟Android模組自動化Api之旅github

 

參考資料:

微信 Android 模組化架構重構實踐(上)

Android實現模組 api 化

美團貓眼電影Android模組化實戰總結

 

如果您對博主的更新內容持續感興趣,請關注公眾號!