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

前言說明

以下內(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)圖

初級(jí)架構(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)行登錄判斷)

官方 Github

實(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):

參考 HookJava基礎(chǔ)

反射框架

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)解決:

帶數(shù)據(jù)存儲(chǔ)的項(xiàng)目結(jié)構(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)流程。

啟動(dòng)權(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ǔ)

Gradle 基礎(chǔ)流程
  1. 讀取根目錄 settings.gradle 中的 include 信息,決定哪些工程會(huì)加入構(gòu)建,并創(chuàng)建 project 實(shí)例。
  2. 按引用樹執(zhí)行所有工程的 build.gradle 腳本,配置 project 對(duì)象,一個(gè)對(duì)象由多個(gè)任務(wù)組成,此階段也會(huì)創(chuàng)建、配置 Task 及相關(guān)信息。
  3. 運(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

sourceSets

2. resValue

可以在 buildType、productFlavor 等變種里動(dòng)態(tài)添加資源。

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

resValue

3. resConfigs

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

resConfigs

4. manifestPlaceholders、buildConfigField

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

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

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

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

模塊依賴架構(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ǎn):省去調(diào)用封裝,同時(shí)保證兼容 Gradle 4.1 以下的組件化項(xiàng)目。
缺點(diǎn):犧牲編譯速度,并且存在子模塊全部移除時(shí)主模塊不能運(yùn)行的風(fēng)險(xiǎn)。

架構(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 的獨(dú)立 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'

選型沒有硬性要求,選擇符合項(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 步。

完整流程圖:

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

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

Gradle 工具欄
  • 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 生命周期圖

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

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

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

調(diào)用順序

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

生命周期關(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 自身也存在 onSaveInstanceStateonRestoreInstanceState 來保存、恢復(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ǔ)結(jié)構(gòu)

基礎(chǔ)組件化

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

基礎(chǔ)組件化

模塊化

應(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)程化

進(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é)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容