Android 組件化架構(gòu) 個人筆記

前言說明

以下內(nèi)容均為 Android 組件化架構(gòu)知識點的總結(jié)歸納券躁、修正錯誤和完善擴展惩坑,非系統(tǒng)知識集,個人筆記也拜,僅供參考以舒。

組件化基礎(chǔ)

1. 引入庫的三種方式

compile fileTree(include: ['*.jar'], dir: 'libs')
compile project(':base')
compile 'com.dji.dpush:core:1.0.0'

2. AndroidManifest

當有多個 Module 時,最終 apk 會將多個 AndroidManifest 合為一個慢哈。

可在 <app>/build/intermediates/manifests/full/debug 目錄下查看合成的 AndroidManifest稀轨。

3. module

每個子 module 都會在 <module>/build/out/aar 下生成 aar 文件。
主 module 會在編譯時重新編譯子 module岸军,并將這些 module 引用進來。

主 module 的合成 manifest 會補全子 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 可以同時配置 tools:replace="android:name"艰赞,無影響
子 application 主 module 不能配置 tools:replace="android:name"
解決沖突后,主 application 主 module 需要配置 tools:replace="android:name"肚吏,子 module 配置與否沒影響

解決沖突:
module 的 application 中配置 tools:replace="android:name"方妖。
多個可替換項用逗號分隔,如 tools:replace="android:name,android:theme"

application 的常用方法:

onConfigurationChanged:僅在 activity 不銷毀罚攀、旋轉(zhuǎn)屏幕下調(diào)用党觅。

registerActivityLifecycleCallbacks:對 App 內(nèi)所有生命周期事件的監(jiān)聽雌澄,還可獲取棧頂端的 activity 對象。利用該特性可以做全局彈窗杯瞻、生命周期管理镐牺。

組件化編程

1. 組件化通信

原生事件通信推薦 LocalBroadcastReceiver,太過重量級魁莉、不方便睬涧、對解耦不利,所以使用事件總線 EventBus旗唁。

Event 3.0 與 2.0 區(qū)別

2.0 采用運行時注解畦浓,利用反射,對整個注冊的類的所有方法進行掃描完成注冊检疫,效率有一定影響讶请。
3.0 采用編譯時注解,Java 編譯成 class 文件時屎媳,就創(chuàng)建出索引關(guān)系夺溢,并編入 apk 中。(使用 EventBusAnnimationProcessor 注解處理器處理)

組件化架構(gòu)圖

初級架構(gòu)

依賴特性

implementation:A 依賴 B剿牺,B 依賴 C企垦,則 A 不能直接調(diào)用 C 中的類。因為 implementation 不能傳遞依賴晒来。(優(yōu)勢在于钞诡,底層代碼變更不需要修改上層依賴,跨模塊完全隔離代碼依賴湃崩,是 Gradle 4.1荧降、AS 3.0 新增,不是 Android Gradle 插件新增)

api | compile:A 依賴 B攒读,B 依賴 C朵诫,則 A 可以直接調(diào)用 C 中的類。因為 api 可以依賴傳遞薄扁。
需要注意剪返,api 需要配置在子 module 里,表示子 module 的某個依賴可以被向上傳遞邓梅。
如 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 是對事件通信的解耦,抽離所有 event 事件實體到該 module 中尚卫。

可以看出归榕,各模塊必然包含對 event 事件實體的耦合,刪除某一 event 必將影響到所有關(guān)聯(lián)模塊焕毫。

2. 組件化跳轉(zhuǎn)

顯式啟動蹲坷,將會在主 module 中引入子 module 中的類,未來拆卸子 module邑飒,將會導(dǎo)致主 module 編譯異常循签。如何解耦呢?

原生實現(xiàn)推薦隱式啟動疙咸,可以使用下面安全代碼:

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 的第一個參數(shù)是主 module 的包名县匠,因為 manifest 在合并后子 module 的包名會被覆蓋(抹除)掉。

更好的實現(xiàn)方式:ARouter 路由

ARouter 介紹

原生 ARouter
跳轉(zhuǎn)依賴類 跳轉(zhuǎn)通過 url 索引
AndroidManifest 注冊 注解注冊
系統(tǒng)控制跳轉(zhuǎn) AOP 切面編程支持
失敗無法降級 靈活降級
- 有攔截過濾機制(如在跳轉(zhuǎn)前進行登錄判斷)

官方 Github

實現(xiàn)原理:

ARouter 的編譯時注解框架會將頁面索引的三個文件生成到 module/build/generated/source/apt/debug/com/alibaba.android.arouter.routes 目錄下撒轮。

Application 加載時乞旦,會初始化調(diào)用 init 方法,將文件中的索引保存到 HashMap 中题山,這樣就保存了全部模塊的跳轉(zhuǎn)關(guān)系兰粉。

跳轉(zhuǎn)時會先查詢是否存在跳轉(zhuǎn)對象、然后經(jīng)過層層攔截器顶瞳、最終調(diào)用 ActivityCompat.startActivity() 跳轉(zhuǎn)玖姑。

其它實現(xiàn)方式

如果項目中引入 RxJava,則推薦使用 OkDeepLink慨菱。

3. 動態(tài)創(chuà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);

反射提供了動態(tài)代理的實現(xiàn):

參考 HookJava基礎(chǔ)

反射框架

jOOR:鏈式調(diào)用、支持動態(tài)代理

Fragment 組件化:動態(tài)創(chuàng)建

方案1:使用反射獲取 Fragment module 加載符喝。(這樣 Fragment module 移除時會拋出異常闪彼,而不是 crash)
方案2:使用 ARouter 路由,所有 Fragment module 提供 newInstance 方法返回實例协饲。

Application 組件化:動態(tài)初始化子 module

方案1:子 module 在 Application 中反射引入畏腕,再手動調(diào)用初始化。
方案2:以接口形式茉稠,抽象初始化方法到 base module 中描馅,子 module 繼承并實現(xiàn)接口,主 module application 則負責添加需要初始化的子 module 類战惊。

4. 組件化存儲

greenDao

greenDAO:對象關(guān)系映射框架,可以通過操作對象的方式去操作數(shù)據(jù)庫。

因為對象關(guān)系吞获,與 EventBus 有同樣的解耦問題况凉,推薦如下架構(gòu)解決:

帶數(shù)據(jù)存儲的項目結(jié)構(gòu)

如圖,不論 bus 還是 data各拷,都是從 base 基礎(chǔ)層中分離出來的組件模塊刁绒,屬于更低的框架層。

5. 組件化權(quán)限

Android 的所有權(quán)限定義在 frameworks/base/core/res/AndroidManifest.xml 中烤黍,源碼參考此鏈接知市。

權(quán)限申請流程圖

啟動 App 正確的權(quán)限申請流程。

啟動權(quán)限申請

權(quán)限配置

方案1:

normal 權(quán)限放到 base module 中速蕊,dangerous 權(quán)限放到各個 module 里嫂丙。

好處是當添加刪除某一模塊時,隱私權(quán)限也將跟著移除规哲。

方案2:

將所有權(quán)限包括 normal 全部移到子 module 中跟啤。

好處是最大程度解耦,缺點是增加了編譯時 AndroidManifest 合并檢測的消耗唉锌。(個人傾向這種)

權(quán)限組件化框架:AndPermission

鏈式操作隅肥、國內(nèi)廠商適配、注解回調(diào)

路由攔截實現(xiàn)模塊權(quán)限控制

組件化微 Demo Git 地址袄简,臨時編寫腥放,初級結(jié)構(gòu),僅供參考(后續(xù)會上線較完整的私人中小組件化項目绿语,并更新在文章中)秃症。

架構(gòu)說明:

base 層實現(xiàn) AndPermission 庫的引入,以便于各個模塊均能使用汞舱;
base 層提供返回 TopActivity 的接口伍纫,由 app module 實現(xiàn),因為組件 module 不依賴 app module昂芜,所以通過接口曲線救國莹规。
function(Demo 中不夠嚴謹,臨時起名 save) 層泌神。即組件 module良漱,實現(xiàn)權(quán)限定義、ARouter 攔截器等功能欢际,方便后續(xù)移除模塊時一并移除母市。
function 與 app 均依賴 ARouter,以實現(xiàn)路由跳轉(zhuǎn)损趋。
單獨抽離 bus 層患久,作為比 base 更底層的基礎(chǔ)層,EventBus 依賴、Event 類均定義于此蒋失。

另外還可以利用 ARouter 的攔截功能做登錄前返帕、支付前驗證。

6. 靜態(tài)常量與資源沖突

基本規(guī)則

1.
主 Module 編譯的靜態(tài)常量: public static final int
子 Module 編譯的靜態(tài)常量: public static int

因為子 Module 的特殊性篙挽,導(dǎo)致某些必須為常量的代碼不能使用荆萤,如下:

//id 不是 final,不能用于 switch-case
@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.tv:
            //TODO
            break;
    }
}

解決方法是 Mac 上將光標點至 switch铣卡,使用 option + return 可將代碼專為 if-else链韭。

2.
R.java 目錄:build/generated/source/r/debug(release)/包名/R.java

3.
編譯時,aar 文件匯總到主 Module煮落,在解決沖突后敞峭,Module 中的 R.java 文件會合并成一份。
主 Module 與子 Module 有同名資源州邢,則保留主 Module 同名資源儡陨。所有資源均是如此,不論 R.string 還是 R.layout量淌。 所以布局上有替代風險骗村。

ButterKnife

1. 配置

annotationProcessor 是編譯時執(zhí)行依賴的庫,不會打包進 apk 中呀枢。它和每個 Module 的編譯息息相關(guān)胚股,必須配置在每個 Module 的 build.gradle 中。

而不論是 ARouter 還是 ButterKnife 的項目依賴裙秋,只需要在 base module 配置一個傳遞依賴即可琅拌。

所以對于 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摘刑,而注解中只能使用常量进宝,對此 ButterKnife 提供了生成 R2 final 資源的方式,但是最好的方式還是通過 findViewById()枷恕,而不是使用注解党晋。

依賴樹

詳情參考 Gradle 依賴樹

*號表示依賴被忽略。

默認會選用較新的依賴徐块,如果想指定某一依賴未玻,除上面鏈接中強制指定的方式,還有下面的排除方式:

complie('com.facebook.fresco:fresco:10.10.0'){
    exclude group:'com.android.support',module:'support-v4'
}

資源名沖突

上面 基本規(guī)則-3 說到主 Module 會覆蓋子 Module 中的同名資源胡控,實際規(guī)則是:

后編譯的模塊會覆蓋之前編譯模塊的資源字段中的內(nèi)容扳剿,而編譯遵循從底層(base)到頂層(app)的順序。

解決辦法是:模塊中資源命名以模塊名為前綴昼激,盡量保證不同模塊間的資源命名不一樣庇绽。

7. 組件化混淆

混淆锡搜,將類名、方法名瞧掺、成員變量等重命名為無意義的簡短名稱余爆,增加逆向工程的難度。

解決混淆沖突

每個 module 都有自己的混淆規(guī)則夸盟,會造成重復(fù)混淆,導(dǎo)致資源找不到像捶。(報錯為 transformClassesAndResourcesWithProguardForRelease

解決辦法:只在主 module 混淆上陕,其余子 module 均不混淆(實測可行,但要注意混淆配置)(即只在主 Module 配置 minifyEnabled true)拓春。

存在問題:主 module 混淆耦合释簿,如果移除主 module 時沒有刪除相應(yīng)混淆文件,雖然不會導(dǎo)致編譯不通過硼莽,但是會影響編譯效率庶溶。另外多 module 開發(fā)可能涉及協(xié)作問題拌消,主 module 開發(fā)人員可能不了解子 module 的內(nèi)部邏輯(如調(diào)用反射)眶蕉,導(dǎo)致混淆錯誤财著,需要子 module 同步混淆代碼咸作,存在溝通成本問題徘键。

其余方案:1.利用 Gradle 插件重構(gòu)混淆邏輯采转;2.consumerProguardFiles 方案璧亮。但書中 consumerProguardFiles 'proguard-rules.pro' 的方式測試無效(我實現(xiàn)有問題乖订?)

混淆基礎(chǔ)知識

詳情參考 混淆基礎(chǔ)知識

上面鏈接包括混淆簡介终息、基本語法夺巩、Android 注意事項等。

資源混淆

AndResGuard 略

8. 多渠道模塊

詳情參考 多渠道打包

多渠道模塊配置

以應(yīng)用的免費版和收費版為例周崭,收費版依賴 vip 模塊柳譬、并使用不同的包名。

flavorDimensions "version" //1.定義維度

productFlavors {
    //free
    free {
        dimension "version" //2.選定維度
        manifestPlaceholders.put('app_name', '免費版') //3.添加維度下特定變量 下一步轉(zhuǎn)Manifest
        manifestPlaceholders.put('ver_num', '1')
        manifestPlaceholders.put('ver_name', name) //將編譯時的變種命名生成為Manifest變量
    }
    //vip
    vip {
        dimension "version"
        applicationId project.android.defaultConfig.applicationId + '.vip'  //這樣因為包名不同可以同時安裝 但是要注意Provider-auth不能同名
        //applicationIdSuffix 'vip'  //或者這樣簡寫
        manifestPlaceholders.put('app_name', 'vip付費版')
        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}" />

免費版因為沒有 vip 模塊续镇,所以編寫美澳、調(diào)用 vip 模塊代碼時需使用反射、ARouter 等無耦合調(diào)用方式磨取,避免直接 import人柿。(上述即是組件化要求解耦的應(yīng)用場景之一,而解耦是組件化的目標方向之一

9. 總結(jié)

效率和適配忙厌,是選型的關(guān)鍵凫岖。

組件化優(yōu)化

基礎(chǔ)

每個 build.gradle 自身是一個 Project 對象,project.apply() 會加載某個工具庫到 project 對象中逢净。

apply plugin:"xx" 的方法會將 project 對象傳遞入工具庫哥放,然后通過插件中的 Groovy 文件來操作 project 對象的屬性歼指,以完善配置初始化信息。

android{} 等調(diào)用相當于 project.android(){} 方法甥雕,方法中會設(shè)置 project 屬性踩身。

每個 Project 中包含很多 Task 構(gòu)建任務(wù),每個 Task 中包含很多 Action 動作社露,每個 Action 相當于代碼塊挟阻,包含很多需要被執(zhí)行的代碼。

Gradle 優(yōu)化

Gradle 基礎(chǔ)

Gradle 基礎(chǔ)流程
  1. 讀取根目錄 settings.gradle 中的 include 信息峭弟,決定哪些工程會加入構(gòu)建附鸽,并創(chuàng)建 project 實例。
  2. 按引用樹執(zhí)行所有工程的 build.gradle 腳本瞒瘸,配置 project 對象坷备,一個對象由多個任務(wù)組成,此階段也會創(chuàng)建情臭、配置 Task 及相關(guān)信息省撑。
  3. 運行階段會根據(jù) Gradle 命令傳遞過來的 Task 名稱,執(zhí)行相關(guān)依賴任務(wù)俯在。

Gradle 參數(shù)優(yōu)化

每個 module 的 build.gradle 有一些必要的屬性竟秫,且同一個 Android 工程中要求屬性值一致,如 compileSdkVersion跷乐、buildToolVersion等鸿摇。(如果不同,雖然能編譯通過劈猿,但是會 bug 告警拙吉,且存在安全風險。)

為了使用統(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{} 可以直接簡寫為 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 //也可以這樣簡寫
        versionName versionName
        ...

lib module 也需要上述配置请祖。

方案二 使用閉包設(shè)置屬性

1.方案一中project.ext內(nèi)多添加如下代碼:

    setDefaultConfig = {
            //定義setDefaultConfig方法
        extension -> //extension相當于是閉包的參數(shù) 后續(xù)android對象會作為參數(shù)傳入
            extension.compileSdkVersion project.ext.compileSdkVersion
            extension.defaultConfig {
                minSdkVersion project.ext.minSdkVersion
                targetSdkVersion project.ext.targetSdkVersion

                testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
            }
    }

書中配置方式為minSdkVersion minSdkVersion,但是實測第二個minSdkVersion的值會丟失脖祈,所以修改為如上配置肆捕。

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)建了一個方法慎陵,并將 android 對象作為參數(shù)傳入眼虱,同理,project 對象(一個 build.gradle 文件)也可以類似操作席纽,下面是完整的代碼捏悬。

新建 config_project.gradle,添加如下代碼:

apply from: "${rootProject.rootDir}/version.gradle"

project.ext {

    //主module(app)配置
    setAppDefaultConfig = {
        extension -> //extension后續(xù)會傳入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 對象
            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)代碼过牙,每個module都需要
            extension.annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'
    }
}

下面是使用了 config_project.gradle 后的簡化版 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', '免費版') //3.添加維度下特定變量 下一步轉(zhuǎn)Manifest
            manifestPlaceholders.put('ver_num', '1')
            manifestPlaceholders.put('ver_name', name) //將編譯時的變種命名生成為Manifest變量
        }
        //vip
        vip {
            dimension "version"
            applicationId project.android.defaultConfig.applicationId + '.vip'
            //這樣因為包名不同可以同時安裝 但是要注意Provider-auth不能同名
            //applicationIdSuffix 'vip'  //或者這樣簡寫
            manifestPlaceholders.put('app_name', 'vip付費版')
            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')
}

當然,上述簡化結(jié)果還可以按需繼續(xù)抽離纺铭、精簡抒和。

config_project.gradle 的意義是為了抽出多個 build.gradle 文件重復(fù)的部分,簡化代碼的同時彤蔽,方便管理和維護,對于各模塊不同的部分無需抽出庙洼。

調(diào)試優(yōu)化

優(yōu)化目的

子模塊作為 App 單獨啟動顿痪,分離調(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 文件夾同級目錄下創(chuàng)建 debug 文件夾石咬,并將 main 中的 AndroidManifest 復(fù)制到這里揩悄。

指定 debug 文件夾下 AndroidManifest 文件中的某 Activity 為啟動 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 指定目標 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')
}

綜上配置后焕窝,當 isVipDebug 為 false蹬挺,只有 app 一個啟動項;當 isVipDebug 為 true它掂,則有 app巴帮、vip 倆個啟動項,分別安裝后虐秋,vip 僅能運行其模塊的功能榕茧,而 app 則僅能運行除 vip 模塊外的其他功能。

意義總結(jié)

從上面可以看出 組件化 的一部分意義:

1.首先客给,子模塊分離調(diào)試用押、應(yīng)用,獨立性很高靶剑,調(diào)試快速方便只恨;

2.有同事說译仗,使用 ARouter (或頁面跳轉(zhuǎn)使用反射、不引入子模塊的 Activity 類)的強行解耦合是不必要的官觅,他的理由是存在一定的跳轉(zhuǎn)耦合纵菌,可以在移除模塊并編譯時,快速定位相關(guān)聯(lián)的頁面休涤、代碼咱圆,進而徹底移除冗余代碼
他的說法有一定的道理功氨,但是結(jié)合上述例子可以一窺序苏,子模塊分離是一件很頻繁的事情,不僅是只有子模塊沒用時才拋棄移除捷凄,它可以應(yīng)用在快速調(diào)試忱详、分離協(xié)作開發(fā)、或用于檢測子模塊獨立性等諸多用途跺涤,所以假如如同事所說編寫代碼匈睁,當每次調(diào)試時,都需要上述刪除冗余代碼操作桶错,在反復(fù)修改增加不必要工作量的同時航唆、將不能有效保證主模塊的正常運行(耦合的通病)院刁。所以對于初學(xué)者糯钙,有時看似沒必要的強行解耦,實際在開發(fā)退腥、調(diào)試任岸、應(yīng)用中因為頻繁移出模塊而很重要。

資源引用配置

資源引用的多種方式

1. sourceSets

sourceSets

2. resValue

可以在 buildType狡刘、productFlavor 等變種里動態(tài)添加資源演闭。

注意:
<1.是資源不是變量;
<2.只能添加颓帝,不能替換米碰,資源名重復(fù) Gradle 會提示。

resValue

3. resConfigs

指定特定尺寸資源购城,同樣在變種中定義吕座。

resConfigs

4. manifestPlaceholders、buildConfigField

顧名思義瘪板,manifestPlaceholders 是 manifest 占位符吴趴,buildConfigField 是 BuildConfig 類中的成員變量。同樣變種中定義侮攀。

buildConfigField
資源引用的優(yōu)先級

優(yōu)先級高的會在優(yōu)先級低的 之后 合成锣枝。

資源引用優(yōu)先級

模塊依賴架構(gòu)

分析經(jīng)過上面組件化編程后厢拭,可行的多種模塊依賴結(jié)構(gòu)。

我的依賴關(guān)系圖
my-dependencies

依賴關(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ào)用封裝,同時保證兼容 Gradle 4.1 以下的組件化項目陨闹。
缺點:犧牲編譯速度楞捂,并且存在子模塊全部移除時主模塊不能運行的風險。

架構(gòu)如圖:

書籍推薦1

依賴關(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 的獨立 module 供主模塊調(diào)用寨闹。

書籍推薦2

依賴關(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'

選型沒有硬性要求,選擇符合項目需求的合適架構(gòu)即可君账。

Git 組件化部署

暫略繁堡,后續(xù)出組件化部署 Blog。

組件化編譯

Gradle 編譯

Android 基礎(chǔ)編譯流程

基礎(chǔ)編譯流程

命令行編譯生成 apk 一文中乡数,可以知道 Gradle 編譯大概有如下幾步:

1.生成 R.java 文件 → 2.生成 class 文件 → 3.生成 dex 文件 → 4.打包資源文件 → 5.生成 apk → 6.簽名對齊

按照功能劃分椭蹄,編譯構(gòu)建分為四個步驟:

代碼編譯 → 代碼合成 → 資源打包 → 簽名對齊

代碼編譯:Java 編譯器對工程代碼資源編譯。包括 App 源代碼瞳脓、apt 編譯生成的 R 文件、AIDL澈侠,最終生成為 class 文件劫侧。對應(yīng)上述 1、2 步哨啃。

代碼合成:通過 dex 工具烧栋,將編譯后所有的 class 文件、依賴庫的 class 文件合成為虛擬機可執(zhí)行的 .dex 文件拳球。如果使用了 MultiDex审姓,會產(chǎn)生多個 dex 文件。對應(yīng)上述 3 步祝峻。

資源打包:通過 apkbuilder 工具魔吐,將 .dex 文件、apt 編譯后的資源文件莱找、依賴庫中的資源文件打包生成 apk 文件酬姆。對應(yīng)上述 4、5 步奥溺。

簽名對齊:使用 Jarsigner 和 Zipaligin 對 apk 進行簽名對齊辞色。對應(yīng)上述 6 步。

完整流程圖:

Android 基礎(chǔ)編譯流程
Task 編譯鏈

在下圖位置查看 Gradle 編譯流程與耗時情況:

Gradle 工具欄
  • Run init scripts:初始化描述
  • Configure build:檢查 build.gradle 中引入的 classpath
  • Calculate task graph:計算出每個模塊的依賴
  • Run tasks:開始構(gòu)建任務(wù)

由于組件化編譯時浮定,開啟了并行編譯相满,所以上述 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)一書的配置方式不可行,原因是因為 class 找不到悯辙,比較奇怪(也可能是我的問題...)琳省。

知道了上述順序關(guān)系,就可以在編譯任務(wù)中嵌入額外任務(wù)操作躲撰。

Instant Run

部分見熱更新针贬、插件化部分,暫略拢蛋。

熱部署:不需要重啟應(yīng)用桦他,也不需要重建當前 Activity。適合簡單修改谆棱,如方法實現(xiàn)修改快压、變量值修改。
溫部署:需要 Activity 重啟垃瞧。場景多為變更了當前頁面的資源文件蔫劣。
冷部署:App 需要重啟,但不是重裝个从。場景為一些繼承規(guī)則脉幢、方法簽名變更等情況。市場上的 App 熱更新框架多參照冷部署嗦锐。

三種部署原理詳見 Android組件化架構(gòu) P167嫌松。

調(diào)試技巧

開啟了 MultiDex 之后,minSdkVersion 要求最小為 21奕污,為了能在 instant run 調(diào)試時使用 21 版本萎羔,打包成 app 時使用低版本,需要下面的配置方式(未測試碳默,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 守護進程中編譯apk贾陷,可以大大減少加載 JVM 和 classes 的時間
org.gradle.configureondemand=true 大型多項目快速構(gòu)建
org.gradle.jvmargs=-Xmx3072M -XX\:MaxPermSize\=512m 加大編譯時使用的內(nèi)存空間
Task 任務(wù)過濾

選擇性去除不需要運行的 Gradle Task 任務(wù):

tasks.whenTaskAdded {
    task ->
        if (task.name.contains("lint") //不掃描潛在bug
                || task.name == "clean"
                || task.name.contains("Aidl") // 項目中不適用 Aidl,則關(guān)閉
                || task.name.contains("mockableAndroidJar") //用不到測試可以先關(guān)閉
                || task.name.contains("UnitTest") //用不到測試可以先關(guān)閉
                || task.name.contains("AndroidTest") //用不到測試可以先關(guān)閉
                || task.name.contains("Ndk") //用不到NDK和JNI可以先關(guān)閉
                || task.name.contains("Jni")) {
            task.enabled = false
        }
}
不執(zhí)行重復(fù)任務(wù)

[Android 組件化架構(gòu)] 一書嘱根,該章節(jié)說默認情況下 Library 只發(fā)布并被依賴 Release 版本昵宇,但是 Debug 和 Release 的 Task 都要執(zhí)行。

經(jīng)測試后發(fā)現(xiàn)并沒有只依賴 Release 版本(App module 為 debug 時依賴的子模塊是 debug library 而不是上面說的 release library)儿子,后來確認書中描述的現(xiàn)象是 Gradle 的一個問題瓦哎,在 AndroidStudio 3.0 上已經(jīng)修復(fù),而我用的版本就是 3.x。新版本 AS 已經(jīng)沒有問題蒋譬,所以不需要再配置割岛。

增量 build

在 module 中減少使用 Annotation processor 有助于提升編譯速度,因為 project 不支持 Annotation processor 增量 build犯助。

使用 Gradle 新特性

implementation 的依賴隔離保證了模塊間解耦癣漆,之前 compile 機制因為底層向上暴露,為了安全起見剂买,Gradle 會完全編譯整個 App惠爽。而依賴隔離則可以準確定位編譯模塊。

設(shè)置 API 版本

Android 5.0 以下因為 .dex 合并時超過方法數(shù)限制的原因會多執(zhí)行部分 Task 任務(wù)瞬哼,所以 debug 設(shè)置 minSdkVersion > 5.0 可以跳過不必要的 Task 任務(wù)婚肆。

buildTypes {
    debug {
        defaultConfig {
            minSdkVersion 21
        }
    }
    release {
        defaultConfig {
            minSdkVersion 14
        }
    }
}

Freeline 極速增量編譯框架

暫略

總結(jié)

編寫業(yè)務(wù)代碼是對用戶的優(yōu)化,編寫環(huán)境代碼是對自身工作的優(yōu)化坐慰。

所謂組件化編譯较性,實際和組件化關(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)時調(diào)用,用于獲得 Activity 傳遞的值糟港。
onDetach:Fragment 與 Activity 關(guān)聯(lián)被取消時調(diào)用攀操。
onCreateView:創(chuàng)建 Fragment 視圖時調(diào)用。
onActivityCreated:初始化 onCreateView 方法的視圖后返回時被調(diào)用秸抚。
onDestroyView:Fragment 視圖被移除時調(diào)用速和。

Fragment 分發(fā)技術(shù)

和 Activity 分發(fā)基本沒有區(qū)別。

View 分發(fā)

View 生命周期

完整生命周期圖

View 生命周期圖

View 的構(gòu)造函數(shù)有倆種加載情況:

  1. View 代碼創(chuàng)建時耸别;
  2. layout 資源文件加載時健芭;(onFinishInflate 前調(diào)用)

生命周期調(diào)用順序

調(diào)用順序

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

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

注意:
1.onSizeChanged 因為在 onResume 之后執(zhí)行县钥,其順序晚于 setContentView XML 加載時機秀姐,所以當期間大小發(fā)生變化就會回調(diào)。
2.onPause 和 onStop 會觸發(fā) onWindowFocusChanged若贮,告知外界 Activity 已失去焦點省有。
3.Activity 銷毀調(diào)用 onDestroy 時,View 才會從 Activity 解綁并調(diào)用 onDetachedFromWindow谴麦。
4.View 自身也存在 onSaveInstanceStateonRestoreInstanceState 來保存蠢沿、恢復(fù)視圖狀態(tài)。
5.View 也有 onConfigurationChange 函數(shù)來觸發(fā)視圖配置變更匾效。

View 分發(fā)技術(shù)

分發(fā)目的:將業(yè)務(wù)模塊割離舷蟀,抽成有生命周期的獨立模塊

分發(fā)做法:Activity 分發(fā)中,是直接創(chuàng)建與 Activity 同生命周期的 Manager 進行生命控制分發(fā)野宜。而 View 分發(fā)扫步,目的不變,也是抽離業(yè)務(wù)模塊(而不是 View 的模塊開發(fā))匈子,做法是在 Activity 內(nèi)創(chuàng)建一個 View河胎,因為該 View 與 Activity 存在關(guān)聯(lián)關(guān)系(部分生命周期存在同步關(guān)系,不同步的函數(shù)則需要額外調(diào)用)虎敦,所以可以利用 View 來給 ModuleManager 做生命周期分發(fā)游岳。

View 的分發(fā)雖然解耦更高(書中還說消耗資源少,沒看出來其徙,因為 Activity 分發(fā)使用 Manager胚迫,View 分發(fā)使用 View + Manager,看起來反而增大了)擂橘,但邏輯不夠直白晌区、配置量增大(View 與 Activity 生命周期兼容導(dǎo)致)、且會引入大量 module 導(dǎo)致增加編譯配置問題通贞,所以不推薦使用朗若。

部分關(guān)鍵代碼及截圖詳見 <Android 組件化架構(gòu)> P205

從同步關(guān)系看出,View 不能分發(fā)以下 Activity 生命周期函數(shù):
onResume:雖然 onResume 之后會調(diào)用 View 的 onAttachedToWindow昌罩,但是該函數(shù)每個 View 只調(diào)用一次哭懈。
onPauseonStop茎用。

依賴倒置

依賴倒置原則:程序要依賴于抽象接口遣总,不依賴于具體實現(xiàn)。核心是面向接口編程轨功。

高層不依賴底層旭斥,應(yīng)該依賴抽象,抽象不應(yīng)該依賴于細節(jié)古涧,細節(jié)應(yīng)該依賴于抽象垂券。

依賴倒置分發(fā)

實際實現(xiàn)是 module 不再需要指定 ViewGroup,多個 ViewGroup 移交給實體 Module 父類管理(經(jīng)由父 Activity 調(diào)用 Module.init 將 ModuleContext 傳給實體 Module羡滑,ModuleContext 內(nèi)包含多個非指定的布局信息)菇爪,而實體 Module 可以自由選擇 ViewGroup 加載。這樣的好處的解除了 Module 對視圖的依賴柒昏,實際也是 Activity 分發(fā)結(jié)構(gòu)的變種凳宙。

代碼有一定參考價值,可使用 RxJava 實現(xiàn)职祷。

組件化列表配置

暫時來看將 Activity 子模塊配置為'列表文件順序加載'較'直接代碼順序加載'而言意義不夠明顯氏涩,可能是個人理解不夠届囚,以后繼續(xù)深入組件化知識再學(xué)習,暫略是尖。

加載優(yōu)化

線程加載

利用 Handler 或 RxJava 將 Module 創(chuàng)建的工作移交給工作線程奖亚,且工作線程使用 newSingleThreadExecutor 來保證 Module 創(chuàng)建、初始化的有序性析砸,這樣既保證了模塊 init 順序昔字,又不會因多模塊初始化而阻塞主線程。

原先需要等每個模塊依次初始化結(jié)束后才能執(zhí)行下一步首繁。
現(xiàn)在將初始化完整序列交給了單個線程池作郭,然后直接執(zhí)行下一步。由 MessageQueue 決定什么時候回到主線程弦疮。
init 函數(shù)記得回歸主線程夹攒。(eg:handler.post)

代碼

首先,MessageQueue 也是阻塞隊列胁塞,本質(zhì)是管道咏尝。當隊列無數(shù)據(jù)的情況下,消費端線程(即主線程)進入等待啸罢,直到有數(shù)據(jù)放入隊列编检,MessageQueue 重新喚醒消費端線程使其繼續(xù)執(zhí)行,以此實現(xiàn)跨線程扰才。

Looper 會執(zhí)行死循環(huán)允懂,從 MessageQueue 中取出消息。當 MessageQueue 中沒有待處理消息時衩匣,主線程就會被 MessageQueue 阻塞蕾总,所以說,主線程大多數(shù)時候都是處于休眠狀態(tài)琅捏,并不會消耗大量CPU資源生百。

那么主線程處于被阻塞狀態(tài),如何保證生命周期函數(shù)及其它正常流程呢柄延?原來蚀浆,四大組件的運行中,最終也是通過 binder 線程(用于進程間通信的線程拦焚,在 Looper.loop() 前創(chuàng)建)調(diào)度 Handler 喚醒主線程蜡坊,并執(zhí)行生命周期函數(shù)(可參考 ActivityThread.H杠输,它有很多響應(yīng)函數(shù))赎败。

handleMessage 的執(zhí)行順序與主線程原本的代碼流沒有關(guān)聯(lián),實際會交叉進行蠢甲。

代碼詳見 YNotes Demo(即上面的私人組件化中小型項目僵刮,暫未上傳)。

模塊懶加載

思想:利用 ViewStub 占位,需要時再喚醒視圖搞糕。

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
    }
    ...

層級限制

Activity 內(nèi)子模塊的順序加載(層級加載)勇吊。有如下方式實現(xiàn):

代碼列表控制、編寫模塊加載列表(方便清晰閱讀模塊層級順序)窍仰、編譯時注解調(diào)序汉规、懶加載調(diào)序、全部使用 ViewStub 然后通過'模塊加載列表'調(diào)序等多種方式驹吮。

多模板設(shè)計

暫略(需 Javapoet针史、編譯時注解組件化列表配置 等前置知識點)碟狞。

組件化流通

遠程倉庫與本地倉庫

詳見 Android 倉庫解析

SDK 知識

AAR 資源合并

SDK 指的是軟件開發(fā)工具啄枕,包括 JNI 的 so 庫、Gradle 的插件助析、Android Studio 的運行腳本畦粮、jar尤泽、aar 等。

模塊依賴通過 implementation project(':library')常空,它和直接引用 implementation 'com.app.xz.library:librarytest:1.0.0' 的不同是,當這倆個 module 都依賴其它庫實現(xiàn)部分功能時盖溺,前者可以通過 api 等屬性傳遞依賴窟绷,而后者打包成 aar 時并不能將其依賴庫同時打進 aar,當主工程沒有 aar 需要的依賴時咐柜,項目就會報 NoClassDefFoundError兼蜈。

除在主工程引入 aar 需要的依賴外,也可以通過其它方式將依賴庫資源打包到 aar 中以解決問題拙友。

書中 fat-aar 測試已過時为狸,新版本似乎不能用(也可能是配置有誤?)遗契。

后續(xù)會單獨出博客研究如何將依賴打進 aar 并闡明原理辐棒。

架構(gòu)模板

組件化模板

類模板

工程模板路徑:/Applications/Android Studio.app/Contents/plugins/android/lib/templates

文件目錄

文件目錄說明

activities:Activity 模板;
gradle:默認的 gradle-wrapper 文件牍蜂;
other:Fragment漾根、View、Service 等其它模板鲫竞;

類模板即創(chuàng)建工程時的 EmptyActivity or 其它 Activity 類型的模板辐怕,制作成本需要學(xué)習 FreeMarker 語法,暫略从绘。

實時模板

即使用快捷鍵生成代碼寄疏,類似代碼補全是牢。

注釋模板

顧名思義。主要用于統(tǒng)一注釋信息陕截。

注解檢測

注解基礎(chǔ)

讓代碼在使用過程中獲取提示信息驳棱,可以借助特殊的注解。

類型 效果 用途
RetentionPolicy.Source 源碼注解农曲,Java 編譯成 class 時注解被遺棄社搅。 編碼時檢測,如 @Null
RetentionPolicy.CLASS 注解保留到 class 文件乳规,但 JVM 加載 class 時遺棄罚渐,是默認生命周期。 編譯時處理驯妄,如編譯時注解
RetentionPolicy.RunTime 注解在運行時仍然存在荷并。 動態(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)演化

下面分析項目從小到大架構(gòu)演化的過程。

基礎(chǔ)架構(gòu)

以文件夾作為業(yè)務(wù)區(qū)分的低級架構(gòu)犁享,Base 則負責引入多種工具庫余素。

基礎(chǔ)結(jié)構(gòu)

基礎(chǔ)組件化

每個組件代表一個業(yè)務(wù),Base 封裝工具庫和框架炊昆。適合中小項目桨吊。

基礎(chǔ)組件化

模塊化

應(yīng)用層:僅負責生成 App、加載初始化凤巨。
模塊層:獨立業(yè)務(wù)模塊视乐。
基礎(chǔ)層:基礎(chǔ)組件的整合,提供基礎(chǔ)組件能力給業(yè)務(wù)層用敢茁∮拥恚基礎(chǔ)層目的是為了隔離模塊層與組件層入口,所以可以是空殼卷要。
組件層:三方庫渣聚、基礎(chǔ)功能層。

適合中型 App僧叉,要求模塊能不依賴于其它模塊實現(xiàn)奕枝,所以考慮重點是業(yè)務(wù)之間如何進行信息交互和轉(zhuǎn)發(fā)。

模塊化

多模板化

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

多模板化

插件化

每個模塊是以業(yè)務(wù)是否獨立作為劃分條件郎笆,對于基礎(chǔ)業(yè)務(wù)如登陸谭梗、支付等需要賬號的模塊最好集成到宿主 App 里(?)宛蚓。

插件化

小組負責獨立業(yè)務(wù)存在的問題是:通信機制激捏、頁面跳轉(zhuǎn)、資源冗余凄吏、資源沖突远舅、混淆等合作問題。

進程化

大型 App 架構(gòu)痕钢,Android 的進程開發(fā)以四大組件為基礎(chǔ)图柏,進程定義需要以四大組件為入口

進程化

進程化注意的問題:

1.靜態(tài)成員和單例失效任连;
2.線程同步機制失效蚤吹,因為 Java 同步機制基于虛擬機,而多進程會有多個虛擬機随抠;
3.SharedPerferences 可靠性下降裁着;
4.并發(fā)訪問文件;
5.Application 多次創(chuàng)建拱她,只能通過進程名區(qū)分不同進程以進行不同進程的初始化操作跨算。

總結(jié)

架構(gòu)最重要的是對未來的思考、未來的把控椭懊,以此才能明白遵循嚴格的開發(fā)規(guī)則的重要性诸蚕。

目錄

[TOC]

簡書不識別 [toc] ... 簡要目錄(僅包含二級標題,部分知識點以外鏈方式給出):

  • 前言說明
  • 組件化基礎(chǔ)
  • 組件化編程
    • 組件化通信
    • 組件化跳轉(zhuǎn)
    • 動態(tài)創(chuàng)建
    • 組件化存儲
    • 組件化權(quán)限
    • 靜態(tài)變量與資源沖突
    • 組件化混淆
    • 多渠道模塊
    • 總結(jié)
  • 組件化優(yōu)化
    • 基礎(chǔ)
    • Gradle 優(yōu)化
    • Git 組件化部署
  • 組件化編譯
    • Gradle 編譯
    • Freeline 極速增量編譯框架
    • 總結(jié)
  • 組件化分發(fā)
    • Activity 分發(fā)
    • Fragment 分發(fā)
    • View 分發(fā)
    • 依賴倒置
    • 組件化列表配置
    • 加載優(yōu)化
    • 層級限制
    • 多模板設(shè)計
  • 組件化流通
    • 遠程倉庫與本地倉庫
    • SDK 知識
  • 架構(gòu)模板
    • 組件化模板
    • 注解檢測
    • 總結(jié)
  • 架構(gòu)演化
    • 基礎(chǔ)架構(gòu)
    • 基礎(chǔ)組件化
    • 模塊化
    • 多模板化
    • 插件化
    • 進程化
    • 總結(jié)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末氧猬,一起剝皮案震驚了整個濱河市背犯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌盅抚,老刑警劉巖漠魏,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異妄均,居然都是意外死亡柱锹,警方通過查閱死者的電腦和手機哪自,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來禁熏,“玉大人壤巷,你說我怎么就攤上這事∏票校” “怎么了胧华?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長宙彪。 經(jīng)常有香客問我矩动,道長,這世上最難降的妖魔是什么释漆? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任悲没,我火速辦了婚禮,結(jié)果婚禮上男图,老公的妹妹穿的比我還像新娘檀训。我一直安慰自己,他們只是感情好享言,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布峻凫。 她就那樣靜靜地躺著,像睡著了一般览露。 火紅的嫁衣襯著肌膚如雪荧琼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天差牛,我揣著相機與錄音命锄,去河邊找鬼。 笑死偏化,一個胖子當著我的面吹牛脐恩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播侦讨,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼驶冒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了韵卤?” 一聲冷哼從身側(cè)響起骗污,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沈条,沒想到半個月后需忿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年屋厘,在試婚紗的時候發(fā)現(xiàn)自己被綠了涕烧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡汗洒,死狀恐怖议纯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情仲翎,我是刑警寧澤痹扇,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布铛漓,位于F島的核電站溯香,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏浓恶。R本人自食惡果不足惜玫坛,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望包晰。 院中可真熱鬧湿镀,春花似錦、人聲如沸伐憾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽树肃。三九已至蒸矛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胸嘴,已是汗流浹背雏掠。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留劣像,地道東北人乡话。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像耳奕,于是被迫代替她去往敵國和親绑青。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345