前言說明
以下內(nèi)容均為 Android 組件化架構(gòu)知識(shí)點(diǎn)的總結(jié)歸納、修正錯(cuò)誤和完善擴(kuò)展,非系統(tǒng)知識(shí)集,個(gè)人筆記,僅供參考。
組件化基礎(chǔ)
1. 引入庫的三種方式
compile fileTree(include: ['*.jar'], dir: 'libs')
compile project(':base')
compile 'com.dji.dpush:core:1.0.0'
2. AndroidManifest
當(dāng)有多個(gè) Module 時(shí),最終 apk 會(huì)將多個(gè) AndroidManifest 合為一個(gè)。
可在 <app>/build/intermediates/manifests/full/debug 目錄下查看合成的 AndroidManifest。
3. module
每個(gè)子 module 都會(huì)在 <module>/build/out/aar 下生成 aar 文件。
主 module 會(huì)在編譯時(shí)重新編譯子 module,并將這些 module 引用進(jìn)來。
主 module 的合成 manifest 會(huì)補(bǔ)全子 module manifest 中配置的全限定名,如下示例:
主 module 包名:com.app.dixon.module_study
子 module 包名:com.app.dixon.base
子 module activity 在 manifest 中的配置:android:name=".LibraryActivity"
子 module activity 在合成 manifest 中的配置:android:name="com.app.dixon.base.LibraryActivity"
4. Application
application 的引用規(guī)則:
| 主 module | 子 module | 最終結(jié)果 | 要求 |
|---|---|---|---|
| 有 | 無 | 主 application | 主 module 可以同時(shí)配置 tools:replace="android:name",無影響 |
| 無 | 有 | 子 application | 主 module 不能配置 tools:replace="android:name"
|
| 有 | 有 | 解決沖突后,主 application | 主 module 需要配置 tools:replace="android:name",子 module 配置與否沒影響 |
解決沖突:
在主 module 的 application 中配置 tools:replace="android:name"。
多個(gè)可替換項(xiàng)用逗號(hào)分隔,如 tools:replace="android:name,android:theme"
application 的常用方法:
onConfigurationChanged:僅在 activity 不銷毀、旋轉(zhuǎn)屏幕下調(diào)用。
registerActivityLifecycleCallbacks:對(duì) App 內(nèi)所有生命周期事件的監(jiān)聽,還可獲取棧頂端的 activity 對(duì)象。利用該特性可以做全局彈窗、生命周期管理。
組件化編程
1. 組件化通信
原生事件通信推薦 LocalBroadcastReceiver,太過重量級(jí)、不方便、對(duì)解耦不利,所以使用事件總線 EventBus。
Event 3.0 與 2.0 區(qū)別
2.0 采用運(yùn)行時(shí)注解,利用反射,對(duì)整個(gè)注冊(cè)的類的所有方法進(jìn)行掃描完成注冊(cè),效率有一定影響。
3.0 采用編譯時(shí)注解,Java 編譯成 class 文件時(shí),就創(chuàng)建出索引關(guān)系,并編入 apk 中。(使用 EventBusAnnimationProcessor 注解處理器處理)
組件化架構(gòu)圖

依賴特性
implementation:A 依賴 B,B 依賴 C,則 A 不能直接調(diào)用 C 中的類。因?yàn)?implementation 不能傳遞依賴。(優(yōu)勢(shì)在于,底層代碼變更不需要修改上層依賴,跨模塊完全隔離代碼依賴,是 Gradle 4.1、AS 3.0 新增,不是 Android Gradle 插件新增)
api | compile:A 依賴 B,B 依賴 C,則 A 可以直接調(diào)用 C 中的類。因?yàn)?api 可以依賴傳遞。
需要注意,api 需要配置在子 module 里,表示子 module 的某個(gè)依賴可以被向上傳遞。
如 a 依賴 b,b 依賴 c,如果 a 想引用 c,則應(yīng)該在 b 的 build.gradle 里配置 c 的依賴方式為 api,則 c 可以向上傳遞(依賴傳遞)。
如果在 a 的 build.gradle 里配置 b 的依賴方式為 api,則表示 b 中代碼可以被依賴傳遞,c 仍然不能被依賴傳遞。
所以上述架構(gòu)圖依賴關(guān)系代碼為:
| module | 依賴 module |
|---|---|
| 主 module | implementation project(':login') implementation project(':base') |
| login | implementation project(':base') |
| base | api project(':bus') |
| bus | api 'org.greenrobot:eventbus:3.1.1' |
bus 是對(duì)事件通信的解耦,抽離所有 event 事件實(shí)體到該 module 中。
可以看出,各模塊必然包含對(duì) event 事件實(shí)體的耦合,刪除某一 event 必將影響到所有關(guān)聯(lián)模塊。
2. 組件化跳轉(zhuǎn)
顯式啟動(dòng),將會(huì)在主 module 中引入子 module 中的類,未來拆卸子 module,將會(huì)導(dǎo)致主 module 編譯異常。如何解耦呢?
原生實(shí)現(xiàn)推薦隱式啟動(dòng),可以使用下面安全代碼:
Intent intent = new Intent();
//intent.setClassName(getPackageName(), "com.app.dixon.login.LoginActivity"); //or this
intent.setComponent(new ComponentName(getPackageName(), "com.app.dixon.login.LoginActivity"));
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
其中 intent.setClassName 的第一個(gè)參數(shù)是主 module 的包名,因?yàn)?manifest 在合并后子 module 的包名會(huì)被覆蓋(抹除)掉。
更好的實(shí)現(xiàn)方式:ARouter 路由
ARouter 介紹
| 原生 | ARouter |
|---|---|
| 跳轉(zhuǎn)依賴類 | 跳轉(zhuǎn)通過 url 索引 |
| AndroidManifest 注冊(cè) | 注解注冊(cè) |
| 系統(tǒng)控制跳轉(zhuǎn) | AOP 切面編程支持 |
| 失敗無法降級(jí) | 靈活降級(jí) |
| - | 有攔截過濾機(jī)制(如在跳轉(zhuǎn)前進(jìn)行登錄判斷) |
實(shí)現(xiàn)原理:
ARouter 的編譯時(shí)注解框架會(huì)將頁面索引的三個(gè)文件生成到 module/build/generated/source/apt/debug/com/alibaba.android.arouter.routes 目錄下。
Application 加載時(shí),會(huì)初始化調(diào)用 init 方法,將文件中的索引保存到 HashMap 中,這樣就保存了全部模塊的跳轉(zhuǎn)關(guān)系。
跳轉(zhuǎn)時(shí)會(huì)先查詢是否存在跳轉(zhuǎn)對(duì)象、然后經(jīng)過層層攔截器、最終調(diào)用 ActivityCompat.startActivity() 跳轉(zhuǎn)。
其它實(shí)現(xiàn)方式
如果項(xiàng)目中引入 RxJava,則推薦使用 OkDeepLink。
3. 動(dòng)態(tài)創(chuàng)建
動(dòng)態(tài)創(chuàng)建的作用是解耦。
反射
反射可獲取屬性的修飾符
| 方法 | 本 Class | SuperClass |
|---|---|---|
| getField | public | public |
| getDeclaredField | public protected private default | no |
| getMethod | public | public |
| getDeclaredField | public protected private default | no |
| getConstructor | public | no |
| getDeclaredConstructor | public protected private default | no |
獲取父類 Class 的任何屬性:
cl.getSupperclass().getDeclaredField("name");
反射泛型:
cl.getDeclaredMethod("test", Object.class);
反射提供了動(dòng)態(tài)代理的實(shí)現(xiàn):
反射框架
jOOR:鏈?zhǔn)秸{(diào)用、支持動(dòng)態(tài)代理
Fragment 組件化:動(dòng)態(tài)創(chuàng)建
方案1:使用反射獲取 Fragment module 加載。(這樣 Fragment module 移除時(shí)會(huì)拋出異常,而不是 crash)
方案2:使用 ARouter 路由,所有 Fragment module 提供 newInstance 方法返回實(shí)例。
Application 組件化:動(dòng)態(tài)初始化子 module
方案1:子 module 在 Application 中反射引入,再手動(dòng)調(diào)用初始化。
方案2:以接口形式,抽象初始化方法到 base module 中,子 module 繼承并實(shí)現(xiàn)接口,主 module application 則負(fù)責(zé)添加需要初始化的子 module 類。
4. 組件化存儲(chǔ)
greenDao
greenDAO:對(duì)象關(guān)系映射框架,可以通過操作對(duì)象的方式去操作數(shù)據(jù)庫。
因?yàn)閷?duì)象關(guān)系,與 EventBus 有同樣的解耦問題,推薦如下架構(gòu)解決:

如圖,不論 bus 還是 data,都是從 base 基礎(chǔ)層中分離出來的組件模塊,屬于更低的框架層。
5. 組件化權(quán)限
Android 的所有權(quán)限定義在 frameworks/base/core/res/AndroidManifest.xml 中,源碼參考此鏈接。
權(quán)限申請(qǐng)流程圖
啟動(dòng) App 正確的權(quán)限申請(qǐng)流程。

權(quán)限配置
方案1:
normal 權(quán)限放到 base module 中,dangerous 權(quán)限放到各個(gè) module 里。
好處是當(dāng)添加刪除某一模塊時(shí),隱私權(quán)限也將跟著移除。
方案2:
將所有權(quán)限包括 normal 全部移到子 module 中。
好處是最大程度解耦,缺點(diǎn)是增加了編譯時(shí) AndroidManifest 合并檢測(cè)的消耗。(個(gè)人傾向這種)
權(quán)限組件化框架:AndPermission
鏈?zhǔn)讲僮鳌?guó)內(nèi)廠商適配、注解回調(diào)
路由攔截實(shí)現(xiàn)模塊權(quán)限控制
組件化微 Demo Git 地址,臨時(shí)編寫,初級(jí)結(jié)構(gòu),僅供參考(后續(xù)會(huì)上線較完整的私人中小組件化項(xiàng)目,并更新在文章中)。
架構(gòu)說明:
base 層實(shí)現(xiàn) AndPermission 庫的引入,以便于各個(gè)模塊均能使用;
base 層提供返回 TopActivity 的接口,由 app module 實(shí)現(xiàn),因?yàn)榻M件 module 不依賴 app module,所以通過接口曲線救國(guó)。
function(Demo 中不夠嚴(yán)謹(jǐn),臨時(shí)起名 save) 層。即組件 module,實(shí)現(xiàn)權(quán)限定義、ARouter 攔截器等功能,方便后續(xù)移除模塊時(shí)一并移除。
function 與 app 均依賴 ARouter,以實(shí)現(xiàn)路由跳轉(zhuǎn)。
單獨(dú)抽離 bus 層,作為比 base 更底層的基礎(chǔ)層,EventBus 依賴、Event 類均定義于此。
另外還可以利用 ARouter 的攔截功能做登錄前、支付前驗(yàn)證。
6. 靜態(tài)常量與資源沖突
基本規(guī)則
1.
主 Module 編譯的靜態(tài)常量: public static final int
子 Module 編譯的靜態(tài)常量: public static int
因?yàn)樽?Module 的特殊性,導(dǎo)致某些必須為常量的代碼不能使用,如下:
//id 不是 final,不能用于 switch-case
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.tv:
//TODO
break;
}
}
解決方法是 Mac 上將光標(biāo)點(diǎn)至 switch,使用 option + return 可將代碼專為 if-else。
2.
R.java 目錄:build/generated/source/r/debug(release)/包名/R.java
3.
編譯時(shí),aar 文件匯總到主 Module,在解決沖突后,Module 中的 R.java 文件會(huì)合并成一份。
主 Module 與子 Module 有同名資源,則保留主 Module 同名資源。所有資源均是如此,不論 R.string 還是 R.layout。 所以布局上有替代風(fēng)險(xiǎn)。
ButterKnife
1. 配置
annotationProcessor 是編譯時(shí)執(zhí)行依賴的庫,不會(huì)打包進(jìn) apk 中。它和每個(gè) Module 的編譯息息相關(guān),必須配置在每個(gè) Module 的 build.gradle 中。
而不論是 ARouter 還是 ButterKnife 的項(xiàng)目依賴,只需要在 base module 配置一個(gè)傳遞依賴即可。
所以對(duì)于 ButterKnife 的配置:
base module:api 'com.jakewharton:butterknife:8.4.0'
app module & function module:annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
2. ButterKnife module 使用
ButterKnife 生成的文件在 module/build/generated/source/apt/debug(release)/包名/ 下。
ButterKnife 通過注解 findViewById,而注解中只能使用常量,對(duì)此 ButterKnife 提供了生成 R2 final 資源的方式,但是最好的方式還是通過 findViewById(),而不是使用注解。
依賴樹
詳情參考 Gradle 依賴樹
*號(hào)表示依賴被忽略。
默認(rèn)會(huì)選用較新的依賴,如果想指定某一依賴,除上面鏈接中強(qiáng)制指定的方式,還有下面的排除方式:
complie('com.facebook.fresco:fresco:10.10.0'){
exclude group:'com.android.support',module:'support-v4'
}
資源名沖突
上面 基本規(guī)則-3 說到主 Module 會(huì)覆蓋子 Module 中的同名資源,實(shí)際規(guī)則是:
后編譯的模塊會(huì)覆蓋之前編譯模塊的資源字段中的內(nèi)容,而編譯遵循從底層(base)到頂層(app)的順序。
解決辦法是:模塊中資源命名以模塊名為前綴,盡量保證不同模塊間的資源命名不一樣。
7. 組件化混淆
混淆,將類名、方法名、成員變量等重命名為無意義的簡(jiǎn)短名稱,增加逆向工程的難度。
解決混淆沖突
每個(gè) module 都有自己的混淆規(guī)則,會(huì)造成重復(fù)混淆,導(dǎo)致資源找不到。(報(bào)錯(cuò)為 transformClassesAndResourcesWithProguardForRelease)
解決辦法:只在主 module 混淆,其余子 module 均不混淆(實(shí)測(cè)可行,但要注意混淆配置)(即只在主 Module 配置 minifyEnabled true)。
存在問題:主 module 混淆耦合,如果移除主 module 時(shí)沒有刪除相應(yīng)混淆文件,雖然不會(huì)導(dǎo)致編譯不通過,但是會(huì)影響編譯效率。另外多 module 開發(fā)可能涉及協(xié)作問題,主 module 開發(fā)人員可能不了解子 module 的內(nèi)部邏輯(如調(diào)用反射),導(dǎo)致混淆錯(cuò)誤,需要子 module 同步混淆代碼,存在溝通成本問題。
其余方案:1.利用 Gradle 插件重構(gòu)混淆邏輯;2.consumerProguardFiles 方案。但書中 consumerProguardFiles 'proguard-rules.pro' 的方式測(cè)試無效(我實(shí)現(xiàn)有問題?)
混淆基礎(chǔ)知識(shí)
詳情參考 混淆基礎(chǔ)知識(shí)
上面鏈接包括混淆簡(jiǎn)介、基本語法、Android 注意事項(xiàng)等。
資源混淆
AndResGuard 略
8. 多渠道模塊
詳情參考 多渠道打包
多渠道模塊配置
以應(yīng)用的免費(fèi)版和收費(fèi)版為例,收費(fèi)版依賴 vip 模塊、并使用不同的包名。
flavorDimensions "version" //1.定義維度
productFlavors {
//free
free {
dimension "version" //2.選定維度
manifestPlaceholders.put('app_name', '免費(fèi)版') //3.添加維度下特定變量 下一步轉(zhuǎn)Manifest
manifestPlaceholders.put('ver_num', '1')
manifestPlaceholders.put('ver_name', name) //將編譯時(shí)的變種命名生成為Manifest變量
}
//vip
vip {
dimension "version"
applicationId project.android.defaultConfig.applicationId + '.vip' //這樣因?yàn)榘煌梢酝瑫r(shí)安裝 但是要注意Provider-auth不能同名
//applicationIdSuffix 'vip' //或者這樣簡(jiǎn)寫
manifestPlaceholders.put('app_name', 'vip付費(fèi)版')
manifestPlaceholders.put('ver_num', '2')
manifestPlaceholders.put('ver_name', name)
}
}
dependencies {
...
//vip版本引入vip模塊
vipImplementation project(':vip')
}
<!-- 4.將特定變量定義到具體占位符 -->
<meta-data
android:name="ver_num"
android:value="${ver_num}" />
免費(fèi)版因?yàn)闆]有 vip 模塊,所以編寫、調(diào)用 vip 模塊代碼時(shí)需使用反射、ARouter 等無耦合調(diào)用方式,避免直接 import。(上述即是組件化要求解耦的應(yīng)用場(chǎng)景之一,而解耦是組件化的目標(biāo)方向之一)
9. 總結(jié)
效率和適配,是選型的關(guān)鍵。
組件化優(yōu)化
基礎(chǔ)
每個(gè) build.gradle 自身是一個(gè) Project 對(duì)象,project.apply() 會(huì)加載某個(gè)工具庫到 project 對(duì)象中。
apply plugin:"xx" 的方法會(huì)將 project 對(duì)象傳遞入工具庫,然后通過插件中的 Groovy 文件來操作 project 對(duì)象的屬性,以完善配置初始化信息。
android{} 等調(diào)用相當(dāng)于 project.android(){} 方法,方法中會(huì)設(shè)置 project 屬性。
每個(gè) Project 中包含很多 Task 構(gòu)建任務(wù),每個(gè) Task 中包含很多 Action 動(dòng)作,每個(gè) Action 相當(dāng)于代碼塊,包含很多需要被執(zhí)行的代碼。
Gradle 優(yōu)化
Gradle 基礎(chǔ)

- 讀取根目錄
settings.gradle中的include信息,決定哪些工程會(huì)加入構(gòu)建,并創(chuàng)建 project 實(shí)例。 - 按引用樹執(zhí)行所有工程的
build.gradle腳本,配置project對(duì)象,一個(gè)對(duì)象由多個(gè)任務(wù)組成,此階段也會(huì)創(chuàng)建、配置Task及相關(guān)信息。 - 運(yùn)行階段會(huì)根據(jù) Gradle 命令傳遞過來的
Task名稱,執(zhí)行相關(guān)依賴任務(wù)。
Gradle 參數(shù)優(yōu)化
每個(gè) module 的 build.gradle 有一些必要的屬性,且同一個(gè) Android 工程中要求屬性值一致,如 compileSdkVersion、buildToolVersion等。(如果不同,雖然能編譯通過,但是會(huì) bug 告警,且存在安全風(fēng)險(xiǎn)。)
為了使用統(tǒng)一的、基礎(chǔ)的 Gradle 配置,提供以下優(yōu)化方案。
方案一 給 project 添加自定義屬性
1.根目錄創(chuàng)建 config.gradle 文件,如下添加自定義屬性。
project.ext {
compileSdkVersion = 28
minSdkVersion = 15
targetSdkVersion = 28
applicationId = "com.example.plugdemo"
}
project.ext{} 可以直接簡(jiǎn)寫為 ext{} 或 ext = xx。
2.build.gradle 引入config.gradle。
apply from: "${rootProject.rootDir}/config.gradle"
3.應(yīng)用屬性
android {
compileSdkVersion project.ext.compileSdkVersion //全稱
defaultConfig {
applicationId project.ext.applicationId
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion
versionCode versionCode //也可以這樣簡(jiǎn)寫
versionName versionName
...
lib module 也需要上述配置。
方案二 使用閉包設(shè)置屬性
1.方案一中project.ext內(nèi)多添加如下代碼:
setDefaultConfig = {
//定義setDefaultConfig方法
extension -> //extension相當(dāng)于是閉包的參數(shù) 后續(xù)android對(duì)象會(huì)作為參數(shù)傳入
extension.compileSdkVersion project.ext.compileSdkVersion
extension.defaultConfig {
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
書中配置方式為minSdkVersion minSdkVersion,但是實(shí)測(cè)第二個(gè)minSdkVersion的值會(huì)丟失,所以修改為如上配置。
2.調(diào)用setDefaultConfig方法。
除方案一引入外,如下調(diào)用:
android {
project.ext.setDefaultConfig android //關(guān)鍵代碼 調(diào)用配置函數(shù)
defaultConfig {
versionCode versionCode
versionName versionName
//ARouter 編譯生成路由 放在具體功能模塊里
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
...
方案三 project + 閉包配置屬性
上述方案二中創(chuàng)建了一個(gè)方法,并將 android 對(duì)象作為參數(shù)傳入,同理,project 對(duì)象(一個(gè) build.gradle 文件)也可以類似操作,下面是完整的代碼。
新建 config_project.gradle,添加如下代碼:
apply from: "${rootProject.rootDir}/version.gradle"
project.ext {
//主module(app)配置
setAppDefaultConfig = {
extension -> //extension后續(xù)會(huì)傳入project替代
extension.apply plugin: 'com.android.application'
extension.description "app"
//設(shè)置通用Android配置
setAndroidConfig extension.android
//設(shè)置通用依賴配置
setDependencies extension.dependencies
}
//設(shè)置lib配置
setLibDefaultConfig = {
extension ->
extension.apply plugin: 'com.android.library'
extension.description "lib"
//設(shè)置通用Android配置
setAndroidConfig extension.android
//設(shè)置通用依賴配置
setDependencies extension.dependencies
}
//設(shè)置android配置
setAndroidConfig = {
extension -> //extension 即 android 對(duì)象
extension.compileSdkVersion 28
extension.defaultConfig {
minSdkVersion 15
targetSdkVersion 28
versionCode project.ext.versionCode
versionName project.ext.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//ARouter 編譯生成路由
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: extension.project.getName()]
}
}
}
}
//設(shè)置依賴
setDependencies = {
extension ->
extension.implementation fileTree(dir: 'libs', include: ['*.jar'])
extension.implementation 'com.android.support:appcompat-v7:28.0.0'
extension.testImplementation 'junit:junit:4.12'
extension.androidTestImplementation 'com.android.support.test:runner:1.0.2'
extension.androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
//ARouter 路由apt插件,用于生成相應(yīng)代碼,每個(gè)module都需要
extension.annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'
}
}
下面是使用了 config_project.gradle 后的簡(jiǎn)化版 build.gradle 配置:
apply from: "${rootProject.rootDir}/config_project.gradle"
project.ext.setAppDefaultConfig project //將 project 作為參數(shù)傳入方法
android {
defaultConfig {
applicationId "com.example.plugdemo"
signingConfigs {
release {
keyAlias 'xx'
keyPassword 'xx'
storeFile file('/Users/xx/Desktop/xx')
storePassword 'xx'
v2SigningEnabled false
}
}
}
buildTypes {
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
flavorDimensions "version" //1.定義維度
productFlavors {
//free
free {
dimension "version" //2.選定維度
manifestPlaceholders.put('app_name', '免費(fèi)版') //3.添加維度下特定變量 下一步轉(zhuǎn)Manifest
manifestPlaceholders.put('ver_num', '1')
manifestPlaceholders.put('ver_name', name) //將編譯時(shí)的變種命名生成為Manifest變量
}
//vip
vip {
dimension "version"
applicationId project.android.defaultConfig.applicationId + '.vip'
//這樣因?yàn)榘煌梢酝瑫r(shí)安裝 但是要注意Provider-auth不能同名
//applicationIdSuffix 'vip' //或者這樣簡(jiǎn)寫
manifestPlaceholders.put('app_name', 'vip付費(fèi)版')
manifestPlaceholders.put('ver_num', '2')
manifestPlaceholders.put('ver_name', name)
}
}
}
dependencies {
//依賴最底層基礎(chǔ)模塊
implementation project(':base')
//依賴下一層功能模塊
implementation project(':login')
implementation project(':pay')
//vip版本引入vip模塊
vipImplementation project(':vip')
}
當(dāng)然,上述簡(jiǎn)化結(jié)果還可以按需繼續(xù)抽離、精簡(jiǎn)。
config_project.gradle 的意義是為了抽出多個(gè) build.gradle 文件重復(fù)的部分,簡(jiǎn)化代碼的同時(shí),方便管理和維護(hù),對(duì)于各模塊不同的部分無需抽出。
調(diào)試優(yōu)化
優(yōu)化目的
子模塊作為 App 單獨(dú)啟動(dòng),分離調(diào)試。
優(yōu)化方案
以下為具體優(yōu)化步驟:
1. 創(chuàng)建 isXXDebug 變量,控制子模塊是否轉(zhuǎn)變?yōu)榉蛛x模塊。
project.ext {
isLoginDebug = false
isVipDebug = false
}
2. 根據(jù) isXXDebug 變量,轉(zhuǎn)變以下變量:
<1.library → application
if (project.ext.isVipDebug) { //app 模式
project.ext.setAppDefaultConfig project
} else {
project.ext.setLibDefaultConfig project
}
<2.配置 applicationId
if (project.ext.isVipDebug) { //app 模式
applicationIdSuffix 'vipdebug'
}
<3.配置 AndroidManifest 文件
main 文件夾同級(jí)目錄下創(chuàng)建 debug 文件夾,并將 main 中的 AndroidManifest 復(fù)制到這里。
指定 debug 文件夾下 AndroidManifest 文件中的某 Activity 為啟動(dòng) Activity(記得配置 theme)。
<application>
<activity android:name=".VipActivity"
android:theme="@style/Theme.AppCompat.Light.DarkActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
最后使用 sourceSets 指定目標(biāo) AndroidManifest:
sourceSets {
//all 表示所有 type,包括 debug 和 release。
all {
if (project.ext.isVipDebug) {
manifest.srcFile 'src/debug/AndroidManifest.xml'
res.srcDir 'src/main/res'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
resources {
exclude 'src/debug/*'
}
}
}
}
3. App module 移除分離調(diào)試的模塊依賴。
if (!project.ext.isVipDebug) {
vipImplementation project(':vip')
}
綜上配置后,當(dāng) isVipDebug 為 false,只有 app 一個(gè)啟動(dòng)項(xiàng);當(dāng) isVipDebug 為 true,則有 app、vip 倆個(gè)啟動(dòng)項(xiàng),分別安裝后,vip 僅能運(yùn)行其模塊的功能,而 app 則僅能運(yùn)行除 vip 模塊外的其他功能。
意義總結(jié)
從上面可以看出 組件化 的一部分意義:
1.首先,子模塊分離調(diào)試、應(yīng)用,獨(dú)立性很高,調(diào)試快速方便;
2.有同事說,使用 ARouter (或頁面跳轉(zhuǎn)使用反射、不引入子模塊的 Activity 類)的強(qiáng)行解耦合是不必要的,他的理由是存在一定的跳轉(zhuǎn)耦合,可以在移除模塊并編譯時(shí),快速定位相關(guān)聯(lián)的頁面、代碼,進(jìn)而徹底移除冗余代碼。
他的說法有一定的道理,但是結(jié)合上述例子可以一窺,子模塊分離是一件很頻繁的事情,不僅是只有子模塊沒用時(shí)才拋棄移除,它可以應(yīng)用在快速調(diào)試、分離協(xié)作開發(fā)、或用于檢測(cè)子模塊獨(dú)立性等諸多用途,所以假如如同事所說編寫代碼,當(dāng)每次調(diào)試時(shí),都需要上述刪除冗余代碼操作,在反復(fù)修改增加不必要工作量的同時(shí)、將不能有效保證主模塊的正常運(yùn)行(耦合的通病)。所以對(duì)于初學(xué)者,有時(shí)看似沒必要的強(qiáng)行解耦,實(shí)際在開發(fā)、調(diào)試、應(yīng)用中因?yàn)轭l繁移出模塊而很重要。
資源引用配置
資源引用的多種方式
1. sourceSets

2. resValue
可以在 buildType、productFlavor 等變種里動(dòng)態(tài)添加資源。
注意:
<1.是資源不是變量;
<2.只能添加,不能替換,資源名重復(fù) Gradle 會(huì)提示。

3. resConfigs
指定特定尺寸資源,同樣在變種中定義。

4. manifestPlaceholders、buildConfigField
顧名思義,manifestPlaceholders 是 manifest 占位符,buildConfigField 是 BuildConfig 類中的成員變量。同樣變種中定義。

資源引用的優(yōu)先級(jí)
優(yōu)先級(jí)高的會(huì)在優(yōu)先級(jí)低的 之后 合成。

模塊依賴架構(gòu)
分析經(jīng)過上面組件化編程后,可行的多種模塊依賴結(jié)構(gòu)。
我的依賴關(guān)系圖

依賴關(guān)系表(省略部分依賴)
| module | 依賴 module |
|---|---|
| 主 module | implementation project(':login') implementation project(':base') |
| login | implementation project(':base') |
| base | api project(':bus') |
| bus | api 'org.greenrobot:eventbus:3.1.1' |
書籍依賴關(guān)系圖
方案一 上述結(jié)構(gòu)中,子模塊也用 api。
優(yōu)點(diǎn):省去調(diào)用封裝,同時(shí)保證兼容 Gradle 4.1 以下的組件化項(xiàng)目。
缺點(diǎn):犧牲編譯速度,并且存在子模塊全部移除時(shí)主模塊不能運(yùn)行的風(fēng)險(xiǎn)。
架構(gòu)如圖:

依賴關(guān)系表:
| module | 依賴 module |
|---|---|
| 主 module | implementation project(':login') |
| login | api project(':base') |
| base | api project(':bus') |
| bus | api 'org.greenrobot:eventbus:3.1.1' |
方案二 主模塊與 Base 完全解耦,需要定義封裝 Base 的獨(dú)立 module 供主模塊調(diào)用。

依賴關(guān)系表:
| module | 依賴 module |
|---|---|
| 主 module | implementation project(':login') implementation project(':app-core') |
| app-core | implementation project(':base') |
| login | implementation project(':base') |
| base | api project(':bus') |
| bus | api 'org.greenrobot:eventbus:3.1.1' |
選型沒有硬性要求,選擇符合項(xiàng)目需求的合適架構(gòu)即可。
Git 組件化部署
暫略,后續(xù)出組件化部署 Blog。
組件化編譯
Gradle 編譯
Android 基礎(chǔ)編譯流程
基礎(chǔ)編譯流程
從 命令行編譯生成 apk 一文中,可以知道 Gradle 編譯大概有如下幾步:
1.生成 R.java 文件 → 2.生成 class 文件 → 3.生成 dex 文件 → 4.打包資源文件 → 5.生成 apk → 6.簽名對(duì)齊
按照功能劃分,編譯構(gòu)建分為四個(gè)步驟:
代碼編譯 → 代碼合成 → 資源打包 → 簽名對(duì)齊
代碼編譯:Java 編譯器對(duì)工程代碼資源編譯。包括 App 源代碼、apt 編譯生成的 R 文件、AIDL,最終生成為 class 文件。對(duì)應(yīng)上述 1、2 步。
代碼合成:通過 dex 工具,將編譯后所有的 class 文件、依賴庫的 class 文件合成為虛擬機(jī)可執(zhí)行的 .dex 文件。如果使用了 MultiDex,會(huì)產(chǎn)生多個(gè) dex 文件。對(duì)應(yīng)上述 3 步。
資源打包:通過 apkbuilder 工具,將 .dex 文件、apt 編譯后的資源文件、依賴庫中的資源文件打包生成 apk 文件。對(duì)應(yīng)上述 4、5 步。
簽名對(duì)齊:使用 Jarsigner 和 Zipaligin 對(duì) apk 進(jìn)行簽名對(duì)齊。對(duì)應(yīng)上述 6 步。
完整流程圖:

Task 編譯鏈
在下圖位置查看 Gradle 編譯流程與耗時(shí)情況:

- Run init scripts:初始化描述
- Configure build:檢查 build.gradle 中引入的 classpath
- Calculate task graph:計(jì)算出每個(gè)模塊的依賴
- Run tasks:開始構(gòu)建任務(wù)
由于組件化編譯時(shí),開啟了并行編譯,所以上述 tasks 任務(wù)存在并行操作的情況,順序是亂的。網(wǎng)上查的關(guān)閉并行編譯的方式也不生效。為了查看 Task 依賴樹,使用 Gradle 框架 gradle-task-tree:
配置方式:
buildscript {
repositories {
...
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
...
classpath "gradle.plugin.com.dorongold.plugins:task-tree:1.3.1"
}
}
allprojects {
...
apply plugin: TaskTreePlugin
}
調(diào)用:
./gradlew assembleDebug taskTree --no-repeat
下面是結(jié)果的部分截圖:

組件化架構(gòu)一書的配置方式不可行,原因是因?yàn)?class 找不到,比較奇怪(也可能是我的問題...)。
知道了上述順序關(guān)系,就可以在編譯任務(wù)中嵌入額外任務(wù)操作。
Instant Run
部分見熱更新、插件化部分,暫略。
熱部署:不需要重啟應(yīng)用,也不需要重建當(dāng)前 Activity。適合簡(jiǎn)單修改,如方法實(shí)現(xiàn)修改、變量值修改。
溫部署:需要 Activity 重啟。場(chǎng)景多為變更了當(dāng)前頁面的資源文件。
冷部署:App 需要重啟,但不是重裝。場(chǎng)景為一些繼承規(guī)則、方法簽名變更等情況。市場(chǎng)上的 App 熱更新框架多參照冷部署。
三種部署原理詳見 Android組件化架構(gòu) P167。
調(diào)試技巧
開啟了 MultiDex 之后,minSdkVersion 要求最小為 21,為了能在 instant run 調(diào)試時(shí)使用 21 版本,打包成 app 時(shí)使用低版本,需要下面的配置方式(未測(cè)試,instant run 暫略):
android {
productFlavors {
instant {
minSdkVersion 21
}
app {
minSdkVersion 17
}
}
}
其他 Gradle 構(gòu)建優(yōu)化
目的:加快編譯速度
Properties 配置
| 配置 | 用途 |
|---|---|
| org.gradle.parallel=true | 開啟并行編譯 |
| android.enableBuildCache=true | 使用編譯緩存 |
| org.gradle.daemon=true | 守護(hù)進(jìn)程中編譯apk,可以大大減少加載 JVM 和 classes 的時(shí)間 |
| org.gradle.configureondemand=true | 大型多項(xiàng)目快速構(gòu)建 |
| org.gradle.jvmargs=-Xmx3072M -XX\:MaxPermSize\=512m | 加大編譯時(shí)使用的內(nèi)存空間 |
Task 任務(wù)過濾
選擇性去除不需要運(yùn)行的 Gradle Task 任務(wù):
tasks.whenTaskAdded {
task ->
if (task.name.contains("lint") //不掃描潛在bug
|| task.name == "clean"
|| task.name.contains("Aidl") // 項(xiàng)目中不適用 Aidl,則關(guān)閉
|| task.name.contains("mockableAndroidJar") //用不到測(cè)試可以先關(guān)閉
|| task.name.contains("UnitTest") //用不到測(cè)試可以先關(guān)閉
|| task.name.contains("AndroidTest") //用不到測(cè)試可以先關(guān)閉
|| task.name.contains("Ndk") //用不到NDK和JNI可以先關(guān)閉
|| task.name.contains("Jni")) {
task.enabled = false
}
}
不執(zhí)行重復(fù)任務(wù)
[Android 組件化架構(gòu)] 一書,該章節(jié)說默認(rèn)情況下 Library 只發(fā)布并被依賴 Release 版本,但是 Debug 和 Release 的 Task 都要執(zhí)行。
經(jīng)測(cè)試后發(fā)現(xiàn)并沒有只依賴 Release 版本(App module 為 debug 時(shí)依賴的子模塊是 debug library 而不是上面說的 release library),后來確認(rèn)書中描述的現(xiàn)象是 Gradle 的一個(gè)問題,在 AndroidStudio 3.0 上已經(jīng)修復(fù),而我用的版本就是 3.x。新版本 AS 已經(jīng)沒有問題,所以不需要再配置。
增量 build
在 module 中減少使用 Annotation processor 有助于提升編譯速度,因?yàn)?project 不支持 Annotation processor 增量 build。
使用 Gradle 新特性
implementation 的依賴隔離保證了模塊間解耦,之前 compile 機(jī)制因?yàn)榈讓酉蛏媳┞?,為了安全起見,Gradle 會(huì)完全編譯整個(gè) App。而依賴隔離則可以準(zhǔn)確定位編譯模塊。
設(shè)置 API 版本
Android 5.0 以下因?yàn)?.dex 合并時(shí)超過方法數(shù)限制的原因會(huì)多執(zhí)行部分 Task 任務(wù),所以 debug 設(shè)置 minSdkVersion > 5.0 可以跳過不必要的 Task 任務(wù)。
buildTypes {
debug {
defaultConfig {
minSdkVersion 21
}
}
release {
defaultConfig {
minSdkVersion 14
}
}
}
Freeline 極速增量編譯框架
暫略
總結(jié)
編寫業(yè)務(wù)代碼是對(duì)用戶的優(yōu)化,編寫環(huán)境代碼是對(duì)自身工作的優(yōu)化。
所謂組件化編譯,實(shí)際和組件化關(guān)聯(lián)不大,更直接的方向是提升編譯速度,優(yōu)化工作流程。
組件化分發(fā)
Activity 分發(fā)
詳見 Activity 組件化分發(fā)結(jié)構(gòu)
Fragment 分發(fā)
Fragment 生命周期
onAttach → onCreate → onCreateView → onActivityCreated → onStart → onResume
→ onPause → onStop → onDestroyView → onDestroy → onDetach
onAttach:Fragment 與 Activity 建立關(guān)聯(lián)時(shí)調(diào)用,用于獲得 Activity 傳遞的值。
onDetach:Fragment 與 Activity 關(guān)聯(lián)被取消時(shí)調(diào)用。
onCreateView:創(chuàng)建 Fragment 視圖時(shí)調(diào)用。
onActivityCreated:初始化 onCreateView 方法的視圖后返回時(shí)被調(diào)用。
onDestroyView:Fragment 視圖被移除時(shí)調(diào)用。
Fragment 分發(fā)技術(shù)
和 Activity 分發(fā)基本沒有區(qū)別。
View 分發(fā)
View 生命周期
完整生命周期圖

View 的構(gòu)造函數(shù)有倆種加載情況:
- View 代碼創(chuàng)建時(shí);
- layout 資源文件加載時(shí);(onFinishInflate 前調(diào)用)
生命周期調(diào)用順序

View 與 Activity 生命周期關(guān)聯(lián)關(guān)系

注意:
1.onSizeChanged 因?yàn)樵?onResume 之后執(zhí)行,其順序晚于 setContentView XML 加載時(shí)機(jī),所以當(dāng)期間大小發(fā)生變化就會(huì)回調(diào)。
2.onPause 和 onStop 會(huì)觸發(fā) onWindowFocusChanged,告知外界 Activity 已失去焦點(diǎn)。
3.Activity 銷毀調(diào)用 onDestroy 時(shí),View 才會(huì)從 Activity 解綁并調(diào)用 onDetachedFromWindow。
4.View 自身也存在 onSaveInstanceState 和 onRestoreInstanceState 來保存、恢復(fù)視圖狀態(tài)。
5.View 也有 onConfigurationChange 函數(shù)來觸發(fā)視圖配置變更。
View 分發(fā)技術(shù)
分發(fā)目的:將業(yè)務(wù)模塊割離,抽成有生命周期的獨(dú)立模塊。
分發(fā)做法:Activity 分發(fā)中,是直接創(chuàng)建與 Activity 同生命周期的 Manager 進(jìn)行生命控制分發(fā)。而 View 分發(fā),目的不變,也是抽離業(yè)務(wù)模塊(而不是 View 的模塊開發(fā)),做法是在 Activity 內(nèi)創(chuàng)建一個(gè) View,因?yàn)樵?View 與 Activity 存在關(guān)聯(lián)關(guān)系(部分生命周期存在同步關(guān)系,不同步的函數(shù)則需要額外調(diào)用),所以可以利用 View 來給 ModuleManager 做生命周期分發(fā)。
View 的分發(fā)雖然解耦更高(書中還說消耗資源少,沒看出來,因?yàn)?Activity 分發(fā)使用 Manager,View 分發(fā)使用 View + Manager,看起來反而增大了),但邏輯不夠直白、配置量增大(View 與 Activity 生命周期兼容導(dǎo)致)、且會(huì)引入大量 module 導(dǎo)致增加編譯配置問題,所以不推薦使用。
部分關(guān)鍵代碼及截圖詳見 <Android 組件化架構(gòu)> P205
從同步關(guān)系看出,View 不能分發(fā)以下 Activity 生命周期函數(shù):
onResume:雖然 onResume 之后會(huì)調(diào)用 View 的onAttachedToWindow,但是該函數(shù)每個(gè) View 只調(diào)用一次。
onPause、onStop。
依賴倒置
依賴倒置原則:程序要依賴于抽象接口,不依賴于具體實(shí)現(xiàn)。核心是面向接口編程。
高層不依賴底層,應(yīng)該依賴抽象,抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴于抽象。
依賴倒置分發(fā)
實(shí)際實(shí)現(xiàn)是 module 不再需要指定 ViewGroup,多個(gè) ViewGroup 移交給實(shí)體 Module 父類管理(經(jīng)由父 Activity 調(diào)用 Module.init 將 ModuleContext 傳給實(shí)體 Module,ModuleContext 內(nèi)包含多個(gè)非指定的布局信息),而實(shí)體 Module 可以自由選擇 ViewGroup 加載。這樣的好處的解除了 Module 對(duì)視圖的依賴,實(shí)際也是 Activity 分發(fā)結(jié)構(gòu)的變種。
代碼有一定參考價(jià)值,可使用 RxJava 實(shí)現(xiàn)。
組件化列表配置
暫時(shí)來看將 Activity 子模塊配置為'列表文件順序加載'較'直接代碼順序加載'而言意義不夠明顯,可能是個(gè)人理解不夠,以后繼續(xù)深入組件化知識(shí)再學(xué)習(xí),暫略。
加載優(yōu)化
線程加載
利用 Handler 或 RxJava 將 Module 創(chuàng)建的工作移交給工作線程,且工作線程使用 newSingleThreadExecutor 來保證 Module 創(chuàng)建、初始化的有序性,這樣既保證了模塊 init 順序,又不會(huì)因多模塊初始化而阻塞主線程。
原先需要等每個(gè)模塊依次初始化結(jié)束后才能執(zhí)行下一步。
現(xiàn)在將初始化完整序列交給了單個(gè)線程池,然后直接執(zhí)行下一步。由 MessageQueue 決定什么時(shí)候回到主線程。
init 函數(shù)記得回歸主線程。(eg:handler.post)

首先,MessageQueue 也是阻塞隊(duì)列,本質(zhì)是管道。當(dāng)隊(duì)列無數(shù)據(jù)的情況下,消費(fèi)端線程(即主線程)進(jìn)入等待,直到有數(shù)據(jù)放入隊(duì)列,MessageQueue 重新喚醒消費(fèi)端線程使其繼續(xù)執(zhí)行,以此實(shí)現(xiàn)跨線程。
Looper 會(huì)執(zhí)行死循環(huán),從 MessageQueue 中取出消息。當(dāng) MessageQueue 中沒有待處理消息時(shí),主線程就會(huì)被 MessageQueue 阻塞,所以說,主線程大多數(shù)時(shí)候都是處于休眠狀態(tài),并不會(huì)消耗大量CPU資源。
那么主線程處于被阻塞狀態(tài),如何保證生命周期函數(shù)及其它正常流程呢?原來,四大組件的運(yùn)行中,最終也是通過 binder 線程(用于進(jìn)程間通信的線程,在 Looper.loop() 前創(chuàng)建)調(diào)度 Handler 喚醒主線程,并執(zhí)行生命周期函數(shù)(可參考 ActivityThread.H,它有很多響應(yīng)函數(shù))。
handleMessage 的執(zhí)行順序與主線程原本的代碼流沒有關(guān)聯(lián),實(shí)際會(huì)交叉進(jìn)行。
代碼詳見 YNotes Demo(即上面的私人組件化中小型項(xiàng)目,暫未上傳)。
模塊懶加載
思想:利用 ViewStub 占位,需要時(shí)再喚醒視圖。
public class RealModule extends AbsModule {
private Activity activity;
private ViewGroup parentViewGroup;
//布局懶加載 1
private View mRoot;
private ViewStub stub;
@Override
public void init(ModuleContext moduleContext) {
activity = moduleContext.getContext();
parentViewGroup = moduleContext.getViewGroups().get(0);
//布局懶加載 2
stub = new ViewStub(activity);
parentViewGroup.addView(stub);
}
//布局懶加載 3
private void initView() {
stub.setLayoutResource(R.layout.note_content_note_home);
mRoot = stub.inflate();
//TODO mRoot.findViewById
}
...
層級(jí)限制
Activity 內(nèi)子模塊的順序加載(層級(jí)加載)。有如下方式實(shí)現(xiàn):
代碼列表控制、編寫模塊加載列表(方便清晰閱讀模塊層級(jí)順序)、編譯時(shí)注解調(diào)序、懶加載調(diào)序、全部使用 ViewStub 然后通過'模塊加載列表'調(diào)序等多種方式。
多模板設(shè)計(jì)
暫略(需 Javapoet、編譯時(shí)注解、組件化列表配置 等前置知識(shí)點(diǎn))。
組件化流通
遠(yuǎn)程倉庫與本地倉庫
詳見 Android 倉庫解析
SDK 知識(shí)
AAR 資源合并
SDK 指的是軟件開發(fā)工具,包括 JNI 的 so 庫、Gradle 的插件、Android Studio 的運(yùn)行腳本、jar、aar 等。
模塊依賴通過 implementation project(':library'),它和直接引用 implementation 'com.app.xz.library:librarytest:1.0.0' 的不同是,當(dāng)這倆個(gè) module 都依賴其它庫實(shí)現(xiàn)部分功能時(shí),前者可以通過 api 等屬性傳遞依賴,而后者打包成 aar 時(shí)并不能將其依賴庫同時(shí)打進(jìn) aar,當(dāng)主工程沒有 aar 需要的依賴時(shí),項(xiàng)目就會(huì)報(bào) NoClassDefFoundError。
除在主工程引入 aar 需要的依賴外,也可以通過其它方式將依賴庫資源打包到 aar 中以解決問題。
書中 fat-aar 測(cè)試已過時(shí),新版本似乎不能用(也可能是配置有誤?)。
后續(xù)會(huì)單獨(dú)出博客研究如何將依賴打進(jìn) aar 并闡明原理。
架構(gòu)模板
組件化模板
類模板
工程模板路徑:/Applications/Android Studio.app/Contents/plugins/android/lib/templates

文件目錄說明
activities:Activity 模板;
gradle:默認(rèn)的 gradle-wrapper 文件;
other:Fragment、View、Service 等其它模板;
類模板即創(chuàng)建工程時(shí)的 EmptyActivity or 其它 Activity 類型的模板,制作成本需要學(xué)習(xí) FreeMarker 語法,暫略。
實(shí)時(shí)模板
即使用快捷鍵生成代碼,類似代碼補(bǔ)全。
注釋模板
顧名思義。主要用于統(tǒng)一注釋信息。
注解檢測(cè)
注解基礎(chǔ)
讓代碼在使用過程中獲取提示信息,可以借助特殊的注解。
| 類型 | 效果 | 用途 |
|---|---|---|
| RetentionPolicy.Source | 源碼注解,Java 編譯成 class 時(shí)注解被遺棄。 | 編碼時(shí)檢測(cè),如 @Null |
| RetentionPolicy.CLASS | 注解保留到 class 文件,但 JVM 加載 class 時(shí)遺棄,是默認(rèn)生命周期。 | 編譯時(shí)處理,如編譯時(shí)注解 |
| RetentionPolicy.RunTime | 注解在運(yùn)行時(shí)仍然存在。 | 動(dòng)態(tài)注解,如 EventBus 2.0,或枚舉替代 |
Android 注解庫依賴
Android support library 引入了新的注解庫,包含很多有用的元注解,以此修飾代碼提示。
implementation 'com.android.support:support-annotations:23.1.1'
如果使用了 v4、v7、appcompat 的庫,則內(nèi)部已經(jīng)引用過該庫了。
注解庫元注解說明
詳見 Android support-annotations 注解使用詳解
總結(jié)
模板和提示目的在于引導(dǎo)協(xié)作者,只有規(guī)則穩(wěn)定高效,才能引導(dǎo)更多協(xié)作者完成任務(wù)。
架構(gòu)演化
下面分析項(xiàng)目從小到大架構(gòu)演化的過程。
基礎(chǔ)架構(gòu)
以文件夾作為業(yè)務(wù)區(qū)分的低級(jí)架構(gòu),Base 則負(fù)責(zé)引入多種工具庫。

基礎(chǔ)組件化
每個(gè)組件代表一個(gè)業(yè)務(wù),Base 封裝工具庫和框架。適合中小項(xiàng)目。

模塊化
應(yīng)用層:僅負(fù)責(zé)生成 App、加載初始化。
模塊層:獨(dú)立業(yè)務(wù)模塊。
基礎(chǔ)層:基礎(chǔ)組件的整合,提供基礎(chǔ)組件能力給業(yè)務(wù)層用。基礎(chǔ)層目的是為了隔離模塊層與組件層入口,所以可以是空殼。
組件層:三方庫、基礎(chǔ)功能層。
適合中型 App,要求模塊能不依賴于其它模塊實(shí)現(xiàn),所以考慮重點(diǎn)是業(yè)務(wù)之間如何進(jìn)行信息交互和轉(zhuǎn)發(fā)。

多模板化
融合組件化分發(fā)以后的架構(gòu),目的是在單頁面中承載多個(gè)獨(dú)立的業(yè)務(wù),可以實(shí)現(xiàn)業(yè)務(wù)的自由組合。

插件化
每個(gè)模塊是以業(yè)務(wù)是否獨(dú)立作為劃分條件,對(duì)于基礎(chǔ)業(yè)務(wù)如登陸、支付等需要賬號(hào)的模塊最好集成到宿主 App 里(?)。

小組負(fù)責(zé)獨(dú)立業(yè)務(wù)存在的問題是:通信機(jī)制、頁面跳轉(zhuǎn)、資源冗余、資源沖突、混淆等合作問題。
進(jìn)程化
大型 App 架構(gòu),Android 的進(jìn)程開發(fā)以四大組件為基礎(chǔ),進(jìn)程定義需要以四大組件為入口。

進(jìn)程化注意的問題:
1.靜態(tài)成員和單例失效;
2.線程同步機(jī)制失效,因?yàn)?Java 同步機(jī)制基于虛擬機(jī),而多進(jìn)程會(huì)有多個(gè)虛擬機(jī);
3.SharedPerferences 可靠性下降;
4.并發(fā)訪問文件;
5.Application 多次創(chuàng)建,只能通過進(jìn)程名區(qū)分不同進(jìn)程以進(jìn)行不同進(jìn)程的初始化操作。
總結(jié)
架構(gòu)最重要的是對(duì)未來的思考、未來的把控,以此才能明白遵循嚴(yán)格的開發(fā)規(guī)則的重要性。
目錄
[TOC]
簡(jiǎn)書不識(shí)別 [toc] ... 簡(jiǎn)要目錄(僅包含二級(jí)標(biāo)題,部分知識(shí)點(diǎn)以外鏈方式給出):
- 前言說明
- 組件化基礎(chǔ)
- 組件化編程
- 組件化通信
- 組件化跳轉(zhuǎn)
- 動(dòng)態(tài)創(chuàng)建
- 組件化存儲(chǔ)
- 組件化權(quán)限
- 靜態(tài)變量與資源沖突
- 組件化混淆
- 多渠道模塊
- 總結(jié)
- 組件化優(yōu)化
- 基礎(chǔ)
- Gradle 優(yōu)化
- Git 組件化部署
- 組件化編譯
- Gradle 編譯
- Freeline 極速增量編譯框架
- 總結(jié)
- 組件化分發(fā)
- Activity 分發(fā)
- Fragment 分發(fā)
- View 分發(fā)
- 依賴倒置
- 組件化列表配置
- 加載優(yōu)化
- 層級(jí)限制
- 多模板設(shè)計(jì)
- 組件化流通
- 遠(yuǎn)程倉庫與本地倉庫
- SDK 知識(shí)
- 架構(gòu)模板
- 組件化模板
- 注解檢測(cè)
- 總結(jié)
- 架構(gòu)演化
- 基礎(chǔ)架構(gòu)
- 基礎(chǔ)組件化
- 模塊化
- 多模板化
- 插件化
- 進(jìn)程化
- 總結(jié)