1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Android 組件化架構(gòu)設(shè)計從原理到實戰(zhàn)!

        共 30326字,需瀏覽 61分鐘

         ·

        2022-07-25 01:43

         安卓進階漲薪訓(xùn)練營,讓一部分人先進大廠


        大家好,我是皇叔,最近開了一個安卓進階漲薪訓(xùn)練營,可以幫助大家突破技術(shù)&職場瓶頸,從而度過難關(guān),進入心儀的公司。


        詳情見文章:沒錯!皇叔開了個訓(xùn)練營

        作者:captain_p鏈接:https://juejin.cn/post/7068542759027605534

        為什么需要組件化

        小項目是不需要組件化的。當(dāng)一個項目有數(shù)十個人開發(fā),編譯項目要花費10分鐘,修改一個bug就可能會影響到其他業(yè)務(wù),小小的改動就需要進行回歸測試,如果是這種項目,那么我們需要進行組件化了

        組件化和模塊化

        在技術(shù)架構(gòu)演進的過程一定是先出現(xiàn)模塊化后出現(xiàn)組件化,因為組件化就是解決了模塊化的問題。

        模塊化架構(gòu)

        創(chuàng)建一個 Project 后可以創(chuàng)建多個 Module,這個 Module 就是所謂的模塊。一個簡單的例子,可能在寫代碼的時候我們會把首頁、消息、我的模塊拆開,每個 tab 所包含的內(nèi)容就是一個模塊,這樣可以減少 module 的代碼量,但是每個模塊之間的肯定是有頁面的跳轉(zhuǎn),數(shù)據(jù)傳遞等,比如 A 模塊需要 B 模塊的數(shù)據(jù),于是我們會在 A 模塊的 gradle 文件內(nèi)通過 implementation project(':B')依賴 B 模塊,但是 B 模塊又需要跳轉(zhuǎn)到 A 模塊的某個頁面,于是 B 模塊又依賴了 A 模塊。這樣的開發(fā)模式依然沒有解耦,改一個bug依然會改動很多模塊,并不能解決大型項目的問題。如下圖所示,一開始我們定義的module之間并沒有過多耦合:

        然后,隨著項目的不斷迭代,相互調(diào)用的情況會增多,也會增加一些庫的擴展和調(diào)用,工程的架構(gòu)可能變?yōu)椋?/p>

        可以看出,各種業(yè)務(wù)之間的耦合非常嚴重,導(dǎo)致代碼非常難以維護,更難以測試,擴展和維護性非常差,這樣的架構(gòu)肯定會被替代。隨著時間的推移,出現(xiàn)了組件化、插件化等組織架構(gòu)。

        組件化架構(gòu)

        這里先提幾個概念,我們?nèi)粘I(yè)務(wù)需求開發(fā)的組件叫做業(yè)務(wù)組件,如果這個業(yè)務(wù)需求是可以被普遍復(fù)用的,那么叫做業(yè)務(wù)基礎(chǔ)組件,譬如圖片加載、網(wǎng)絡(luò)請求等框架組件我們稱為基礎(chǔ)組件。搭建所有組件的app組件稱為殼組件/工程。接下來看一張架構(gòu)圖:

        實線表示直接依賴關(guān)系,虛線表示間接依賴。比如殼工程肯定是要依賴業(yè)務(wù)基礎(chǔ)組件、業(yè)務(wù)組件、module_common公共庫的。業(yè)務(wù)組件依賴業(yè)務(wù)基礎(chǔ)組件,但并不是直接依賴,而是通過”下沉接口“來實現(xiàn)間接調(diào)用。業(yè)務(wù)組件之間的依賴也是間接依賴。最后common組件依賴所有需要的基礎(chǔ)組件,common也屬于基礎(chǔ)組件,它只是統(tǒng)一了基礎(chǔ)組件的版本,同時也提供了給應(yīng)用提供一些抽象基類,比如BaseActivityBaseFragment,基礎(chǔ)組件初始化等。

        組件化帶來的優(yōu)勢

        • 加快編譯速度:每個業(yè)務(wù)組件都可以單獨運行調(diào)試,速度提升好幾倍。舉個例子:video組件單獨編譯運行時間為3s,因為此時AS只會運行video組件以及video組件依賴的組件的task,而如果集成編譯時間為10s,app所引用的所有的組件的task都會執(zhí)行??梢姡侍嵘?倍。

        • 提高協(xié)作效率:每個組件都有專人維護,不用關(guān)心其他組件是怎么實現(xiàn)的,只需要暴露對方需要的數(shù)據(jù)。測試也不需要整個回歸,只需要重點測試修改的組件即可。

        • 功能重用:一次編碼處處復(fù)用,再也不需要復(fù)制代碼了。尤其是基礎(chǔ)組件和業(yè)務(wù)基礎(chǔ)組件,基本上調(diào)用者根據(jù)文檔就可以一鍵集成和使用。

        • 確保了整體技術(shù)方案的統(tǒng)一性,為未來插件化公用一套底層模型做準備。

        前面有提到非大型項目一般不會進行組件化,但是就像上面提到的功能重用,這個優(yōu)勢并不是只能用到大型項目 。我們可以在寫需求或庫時完全可以擁有組件化思想,把它們單獨寫成一個基礎(chǔ)組件或業(yè)務(wù)基礎(chǔ)組件。當(dāng)?shù)诙€項目來的時候正好也需要這個組件,那我們就省去了拆出這個組件的時間(因為寫需求的時候很可能會造成大量耦合,后續(xù)拆分要花費時間),比如登錄組件,分享組件等等都是可以在一開始就寫成組件的。

        組件化需解決的問題

        • 如何解決資源配置沖突問題?

        • 業(yè)務(wù)組件如何實現(xiàn)單獨調(diào)試?

        • 業(yè)務(wù)組件間沒有依賴,如何實現(xiàn)頁面跳轉(zhuǎn)?

        • 業(yè)務(wù)組件間沒有依賴,如何實現(xiàn)數(shù)據(jù)通信?

        • 業(yè)務(wù)組件間沒有依賴,如何實現(xiàn)消息通信

        • 殼工程Application生命周期如何下發(fā)?

        資源沖突解決

        AndroidManifest

        每個module都有一份AndroidManifest文件來記載信息,最終生成一個App的時候,只會有一份AndroidManifest來知道APP應(yīng)該去如何配置,Manifest Merge Tools 會將多個AndroidManifest合成一個,但是又沖突需要解決。

        build/intermediates/manifest_merge_blame_file下會生成一份合并報告

        1<?xml version="1.0" encoding="utf-8"?>
        2<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        3    package="com.liang.mosic"
        4    android:versionCode="1"
        5    android:versionName="1.0" >

        6
        7    <uses-sdk
        8        android:minSdkVersion="21"
        8-->
        C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml
        9        android:targetSdkVersion="30" />
        9-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml
        10
        11    <uses-permission android:name="android.permission.INTERNET" />
        11-->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:12:5-67
        11-->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:12:22-64
        12
        13    <application
        13-->
        C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:6:5-48:19
        14        android:name="com.liang.mosic.ModuleApplication"
        14-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:10:9-42
        15        android:allowBackup="true"
        15-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:7:9-35
        16        android:appComponentFactory="androidx.core.app.CoreComponentFactory"
        16-->[androidx.core:core:1.5.0] C:\Users\Administrator.gradle\caches\transforms-2\files-2.1\4c9b62de2468f1520f5d232befb24ab8\core-1.5.0\AndroidManifest.xml:24:18-86
        17        android:debuggable="true"
        18        android:icon="@mipmap/ic_launcher"
        18-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:8:9-43
        19        android:label="@string/app_name"
        19-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:9:9-41
        20        android:supportsRtl="true"
        20-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:11:9-35
        21        android:testOnly="true"
        22        android:theme="@style/AppTheme" >
        22-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:13:9-40
        23
        24        <!-- <activity android:name=".MainActivity"> -->
        25        <!-- <intent-filter> -->
        26        <!-- <action android:name="android.intent.action.MAIN" /> -->
        27
        28
        29        <!-- <category android:name="android.intent.category.LAUNCHER" /> -->
        30        <!-- </intent-filter> -->
        31        <!-- </activity> -->
        32        <activity
        32-->
        C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:21:9-28:20
        33            android:name="com.liang.mosic.AdaviceActivity"
        33-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:21:19-50
        34            android:theme="@style/AppWelcome" >
        34-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:22:13-46
        35            <intent-filter>
        35-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:23:13-27:29
        36                <action android:name="android.intent.action.MAIN" />
        36-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:24:17-69
        36-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:24:25-66
        37
        38                <category android:name="android.intent.category.LAUNCHER" />
        38-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:26:17-77
        38-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:26:27-74
        39            </intent-filter>
        40        </activity>
        41        <activity android:name="com.liang.mosic.ModuleMainActivity" >
        41-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:29:9-40:20
        41-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:29:19-53
        42
        43            <!-- <intent-filter> -->
        44            <!-- <action android:name="android.intent.action.MAIN" /> -->
        45
        46
        47            <!-- <category android:name="android.intent.category.LAUNCHER" /> -->
        48            <!-- </intent-filter> -->
        49            <intent-filter>
        49-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:35:13-39:29
        50                <action android:name="com.liang.main" />
        50-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:36:17-60
        50-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:36:25-57
        51
        52                <category android:name="android.intent.category.DEFAULT" />
        52-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:17-76
        52-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:27-73
        53            </intent-filter>
        54        </activity>
        55        <activity android:name="com.liang.mosic.ModuleExampleActivity" >
        55-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:41:9-47:20
        55-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:41:19-56
        56            <intent-filter>
        56-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:42:13-46:29
        57                <action android:name="com.liang.moduleFragment" />
        57-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:43:17-70
        57-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:43:25-67
        58
        59                <category android:name="android.intent.category.DEFAULT" />
        59-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:17-76
        59-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:27-73
        60            </intent-filter>
        61        </activity>
        62        <activity android:name="com.liang.a.MainActivity" >
        62-->[:a] C:\Users\Administrator\Desktop\mosic-master\mosic-master\a\build\intermediates\library_manifest\debug\AndroidManifest.xml:17:9-23:20
        62-->[:a] C:\Users\Administrator\Desktop\mosic-master\mosic-master\a\build\intermediates\library_manifest\debug\AndroidManifest.xml:17:19-61
        63            <intent-filter>
        63-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:23:13-27:29
        64                <action android:name="android.intent.action.MAIN" />
        64-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:24:17-69
        64-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:24:25-66
        65
        66                <category android:name="android.intent.category.LAUNCHER" />
        66-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:26:17-77
        66-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:26:27-74
        67            </intent-filter>
        68        </activity>
        69        <activity android:name="com.liang.b.BActivity" >
        69-->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:21:9-27:20
        69-->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:21:19-58
        70            <intent-filter>
        70-->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:22:13-26:29
        71                <action android:name="com.liang.b.act" />
        71-->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:23:17-61
        71-->[:b] C:\Users\Administrator\Desktop\mosic-master\mosic-master\b\build\intermediates\library_manifest\debug\AndroidManifest.xml:23:25-58
        72
        73                <category android:name="android.intent.category.DEFAULT" />
        73-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:17-76
        73-->C:\Users\Administrator\Desktop\mosic-master\mosic-master\app\src\main\AndroidManifest.xml:38:27-73
        74            </intent-filter>
        75        </activity>
        76    </application>
        77
        78</manifest>

        最終編譯出的app會將其他所有module的AndroidManifest文件合并,合并規(guī)則為:

        • 如果功能module有Application,主module沒有聲明,則使用功能module的Application;
        • 如果主module有定義Application,其他module沒有,則使用主module的;
        • 如果功能module有多個自定義Application,解決沖突后使用;
        • 如果主module有Application,功能module也有,則解決沖突后,最后編譯的主module的Application會在AndroidManifest中。

        如果子module中聲明icon、theme等屬性,會導(dǎo)致合并沖突,需要申明屬性:

        xmlns:tools="http://schemas.android.com/tools"
        tools:replace="android:icon,android:theme"

        權(quán)限申明:

        在子module中申明的權(quán)限,會集成到主module中,四大組件也是相同的規(guī)則。shareUid只有在主module中申明,才會打包到最終的AndroidManifest中。

        獨立調(diào)試

        單工程方案

        所謂的單工程方案就是把所有組件都放到一個工程下,先看一下整體的目錄:

        ps:module_ 開頭表示基礎(chǔ)組件,fun_ 前綴表示業(yè)務(wù)基礎(chǔ)組件,biz_前綴表示業(yè)務(wù)組件,export_前綴表示業(yè)務(wù)組件暴露接口。

        單工程利弊分析:

        • 利:一個模塊修改后只需要編譯一下,依賴它的其他模塊就能馬上感知到變化。
        • 弊:沒能做到完全的職責(zé)拆分,每個模塊的開發(fā)者都有修改其他模塊的權(quán)限。

        首先在 gradle.properties 文件內(nèi)聲明一個變量:

        // gradle.properties
        isModule = true

        isModuletrue 時表示組件可以作為 apk 運行起來,false 表示組件只能作為 library。我們根據(jù)需要改變這個值后同步下gradle即可。然后在某個 module 的 build.gradle 文件內(nèi)用這個變量做三個地方的判斷:

        // build.gradle

        // 區(qū)分是應(yīng)用還是庫
        if(isModule.toBoolean()) {
         apply plugin: 'com.android.application'
        }else {
         apply plugin: 'com.android.library'
        }

        android {
         defaultConfig {
          // 如果是應(yīng)用需要指定application
          if(isModule.toBoolean()) {
           applicationId "com.xxx.xxx"
          }
         }
         sourceSets {
          main {
           // 應(yīng)用和庫的AndroidManifest文件區(qū)分
           if(isModule.toBoolean()) {
            manifest.srcFile 'src/main/debug/AndroidManifest.xml'
           }else {
            manifest.srcFile 'src/main/AndroidManifest.xml' 
           }
          }
         }
        }

        由于library是不需要 Application 和啟動Activity頁,所以我們要區(qū)分這個文件,應(yīng)用manifest指定的路徑?jīng)]有特定,隨意找個路徑創(chuàng)建即可。在應(yīng)用AndroidManifest.xml里我們要設(shè)置啟動頁:

        <manifest xmlns:android="http://schemas.android.com/apk/res/android"
            package="com.sun.biz_home">

            <application
                android:allowBackup="true"
                android:label="@string/home_app_name"
                android:supportsRtl="true"
                android:theme="@style/home_AppTheme">

                <activity android:name=".debug.HomeActivity">
                    <intent-filter>
                        <action android:name="android.intent.action.MAIN" />
                        <category android:name="android.intent.category.LAUNCHER" />
                    </intent-filter>
                </activity>
            </application>
        </manifest>

        libraryAndroidManifest.xml 不需要這些:

        <manifest xmlns:android="http://schemas.android.com/apk/res/android"
            package="com.sun.biz_home">

        </manifest>

        gradle 依賴 module 的方式主要有兩種:

        • implementation: A implementation B,B implementation C, 但 A 不能訪問到 C 的東西。
        • api:A api B,B api C,A能訪問到C的東西。

        一般來說我們只需要使用 implementation 即可,api 是會造成項目編譯時間變長,而且會引入該模塊不需要的功能,代碼之間耦合變得嚴重了。不過 module_common 是統(tǒng)一了基礎(chǔ)組件版本的公共庫,所有組件都應(yīng)需要依賴它并擁有基礎(chǔ)組件的能力,所以基本每個業(yè)務(wù)組件和業(yè)務(wù)基礎(chǔ)組件都應(yīng)該依賴公共庫:

        dependencies {
         implementation project(':module_common')
        }

        而 common 組件依賴基礎(chǔ)組件應(yīng)該是用 api,因為把基礎(chǔ)組件的能力傳遞給上層業(yè)務(wù)組件:

        dependencies {
         api project(':module_base')
         api project(':module_util')
        }

        多工程方案

        多工程就是每個組件都是一個工程,例如創(chuàng)建一個工程后 app 作為殼組件,它依賴 biz_home 運行,因此不需要 isModule 來控制獨立調(diào)試,它本身就是一個工程可以獨立調(diào)試。

        多工程的利弊就是和單工程相反的:

        • 利:做到職責(zé)完全拆分,其他項目復(fù)用更加方便,直接一行依賴引入。
        • 弊:修改后需要上傳到maven倉庫,其他工程再次編譯后才能感知到變化,多了上傳和編譯的時間。

        多工程組件依賴需要用到maven倉庫。把每個組件的aar上傳到公司內(nèi)網(wǎng)的maven倉庫,然后像這樣去依賴:

        implementation 'com.xxx.xxx:module_common:1.0.0'

        我們把三方庫統(tǒng)一放到 config.gradle 內(nèi)管理:

        ext {
            dependencies = [
                    "glide""com.github.bumptech.glide:glide:4.12.0",
                    "glide-compiler""com.github.bumptech.glide:compiler:4.12.0",
                    "okhttp3""com.squareup.okhttp3:okhttp:4.9.0",
                    "retrofit""com.squareup.retrofit2:retrofit:2.9.0",
                    "retrofit-converter-gson"  : "com.squareup.retrofit2:converter-gson:2.9.0",
                    "retrofit-adapter-rxjava2" : "com.squareup.retrofit2:adapter-rxjava2:2.9.0",
                    "rxjava2""io.reactivex.rxjava2:rxjava:2.2.21",
                    "arouter""com.alibaba:arouter-api:1.5.1",
                    "arouter-compiler""com.alibaba:arouter-compiler:1.5.1",
                    // our lib
                    "module_util""com.sun.module:module_util:1.0.0",
                    "module_common""com.sun.module:module_common:1.0.0",
                    "module_base""com.sun.module:module_base:1.0.0",
                    "fun_splash""com.sun.fun:fun_splash:1.0.0",
                    "fun_share""com.sun.fun:fun_share:1.0.0",
                    "export_biz_home""com.sun.export:export_biz_home:1.0.0",
                    "export_biz_me""com.sun.export:export_biz_me:1.0.0",
                    "export_biz_msg""com.sun.export:export_biz_msg:1.0.0",
                    "biz_home""com.sun.biz:biz_home:1.0.0",
                    "biz_me""com.sun.biz:biz_me:1.0.0",
                    "biz_msg""com.sun.biz:biz_msg:1.0.0"
            ]
        }

        這樣方便版本統(tǒng)一管理, 然后在根目錄的 build.gradle 內(nèi)導(dǎo)入:

        apply from: 'config.gradle'

        最后在各自的模塊引入依賴,比如在 module_common 中這么引入依賴即可。

        dependencies {
         api rootProject.ext.dependencies["arouter"]
          kapt rootProject.ext.dependencies["arouter-compiler"]
          api rootProject.ext.dependencies["glide"]
          api rootProject.ext.dependencies["okhttp3"]
          api rootProject.ext.dependencies["retrofit"]
          api rootProject.ext.dependencies["retrofit-converter-gson"]
          api rootProject.ext.dependencies["retrofit-adapter-rxjava2"]
          api rootProject.ext.dependencies["rxjava2"]
          api rootProject.ext.dependencies["module_util"]
          api rootProject.ext.dependencies["module_base"]
        }

        個人覺得多工程適合"很大"的工程,每個業(yè)務(wù)組件可能都需要一個組開發(fā),類似淘寶這樣的app。但這只是針對業(yè)務(wù)組件來說的,業(yè)務(wù)基礎(chǔ)組件和基礎(chǔ)組件修改的頻率不會很大,最好都是單工程上傳至maven倉庫來使用。本文的例子是為了方便所以把所有組件寫到一起了,最好的方式就是把 fun_ 和 module_ 開頭的組件都拆分成單工程獨立開發(fā),業(yè)務(wù)組件寫到一個工程內(nèi)。

        頁面跳轉(zhuǎn)

        做完組件之間的隔離后,暴露出來最明顯的問題就是頁面跳轉(zhuǎn)和數(shù)據(jù)通信的問題。一般來說,頁面跳轉(zhuǎn)都是顯示startActivity跳轉(zhuǎn),在組件化項目內(nèi)就不適用了,隱式跳轉(zhuǎn)可以用,但每個Activity都要寫 intent-filter 就顯得有點麻煩,如下所示:

        <activity android:name="com.liang.lib_video.videoplayer.VideoActivity">
            <intent-filter>
                <action android:name="com.intent.openVideoActivity"></action>
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        Intent intent = new Intent();
        intent.setClass("包名","Activity路徑");
        intent.setComponent(new ComponentName("包名"));
        startActivity(intent);

        使用上述方式跳轉(zhuǎn)會奔潰,提示在AndroidManifest文件中注冊,這里需要注意的是,setClass/setComponent是APP的包名而不是所在module的包名。

        可以參考最終生成的AndroidManifest文件。使用隱式跳轉(zhuǎn)也可以先用resolveActivity進行驗證。如果不想要其他應(yīng)用通過隱式打開,需要設(shè)置exported=false。

        隱式跳轉(zhuǎn)是整個系統(tǒng)都能接收到,會相對想好性能,所以最好的方式還是用路由框架。

        實際上市面已經(jīng)有比較成熟的路由框架專門就是為了組件化而生的,比如美團的WMRouter,阿里的ARouter等,本例使用 ARouter 框架,看下ARouter頁面跳轉(zhuǎn)的基本操作。

        首先肯定是引入依賴,以 module_common 引入ARouter舉例,build.gradle 應(yīng)該添加:

        android {
         defaultConfig {
          javaCompileOptions {
               annotationProcessorOptions {
                  arguments = [AROUTER_MODULE_NAME: project.getName()]
               }
            }
         }
         compileOptions {
              sourceCompatibility JavaVersion.VERSION_1_8
              targetCompatibility JavaVersion.VERSION_1_8
          }
        }
        dependencies {
         api rootProject.ext.dependencies["arouter"]
          kapt rootProject.ext.dependencies["arouter-compiler"]
        }

        kapt注解依賴沒有辦法傳遞,所以我們不可避免得需要在每個模塊都聲明這些配置,除了 api rootProject.ext.dependencies["arouter"] 這行。然后需要全局注冊 ARouter,我是在 module_common 統(tǒng)一注冊的。

        class AppCommonBaseApp{
            override fun onCreate(application: Application) {
                MLog.d(TAG, "BaseApp AppCommon init")
                initARouter(application)
            }

            private fun initARouter(application: Application) {
                if(BuildConfig.DEBUG) {
                    ARouter.openLog()
                    ARouter.openDebug()
                }
                ARouter.init(application)
            }
        }

        接著我們在 module_common 模塊內(nèi)聲明一個路由表用作統(tǒng)一管理路徑。

        // RouterPath.kt
        class RouterPath {
            companion object {
                const val APP_MAIN = "/app/MainActivity"
                const val HOME_FRAGMENT = "/home/HomeFragment"
                const val MSG_FRAGMENT = "/msg/MsgFragment"
                const val ME_FRAGMENT = "/me/MeFragment"
                const val MSG_PROVIDER = "/msg/MsgProviderImpl"
            }
        }

        復(fù)制代碼
        然后在MainActivity類文件上進行注解:
        @Route(path = RouterPath.APP_MAIN)
        class MainActivity : AppCompatActivity() {
        }

        任意模塊只需要調(diào)用 ARouter.getInstance().build(RouterPath.APP_MAIN).navigation() 即可實現(xiàn)跳轉(zhuǎn)。如果我們要加上數(shù)據(jù)傳遞也很方便:

        ARouter.getInstance().build(RouterPath.APP_MAIN)
                    .withString("key""value")
                    .withObject("key1", obj)
                    .navigation()

        然后在MainActivity使用依賴注入接受數(shù)據(jù):

        class MainActivity : AppCompatActivity() {
            @Autowired
            String key = ""
        }

        Arouter 實現(xiàn)組件間方法調(diào)用

        export_biz_msg 組件下聲明 IMsgProvider,此接口必須實現(xiàn) IProvider 接口:

        interface IMsgProviderIProvider {
            fun onCountFromHome(count: Int = 1)
        }

        然后在 biz_msg 組件里實現(xiàn)這個接口:

        @Route(path = RouterPath.MSG_PROVIDER)
        class MsgProviderImplIMsgProvider {
            override fun onCountFromHome(count: Int) {
               // 這里只是對數(shù)據(jù)進行分發(fā),有監(jiān)聽計數(shù)的對象會收到
                MsgCount.instance.addCount(count)
            }
            override fun init(context: Context?) {
                // 對象被初始化時調(diào)用
            }
        }

        biz_home 首頁組件中發(fā)送計數(shù):

        val provider = ARouter.getInstance().build(RouterPath.MSG_PROVIDER).navigation() as IMsgProvider
        provider.onCountFromHome(count)

        可以看到其實和頁面跳轉(zhuǎn)的方式基本雷同,包括獲取 Fragment 實例的方式也是這種。ARouter把所有通信的方式都用一種api實現(xiàn),讓使用者上手非常容易。

        組件化的消息通信

        消息通信方式的選擇

        廣播

        作為Android中四大組件之一,Broadcast的職責(zé)是用于Android系統(tǒng)通信,但是普通的廣播是全局廣播,會造成安全泄露以及效率問題,如果只是在應(yīng)用內(nèi)部通知,可以使用更為高效的LocalBroadCast,相對于全局廣播,本地廣播只會在APp內(nèi)部傳播,不會造成隱私泄露,同時無法接受其他應(yīng)用發(fā)送的廣播,相對于全局廣播來說更加高效。

        事件總線

        由于系統(tǒng)級別的廣播傳遞比較耗時,消息通信科使用通過記錄對象、使用監(jiān)聽者模式實現(xiàn)的事件總線框架,比如EventBus、LivaData等。

        通過將消息的公用部分 ,如自定義消息的bean放入到baseMOdule下的單獨模塊來實現(xiàn)組件間消息的傳遞。組件化的數(shù)據(jù)庫存儲和消息通信的實現(xiàn)方式大同小異,都是將公用的東西放入到baseModule,如果內(nèi)容比較多或者對于職責(zé)界限劃分要求高的話可在base下新建一個DataBaseModule

        Application生命周期分發(fā)

        當(dāng) app 殼工程啟動Application初始化時要通知到其他組件初始化一些功能。這里提供一個簡單的方式。首先我們在module_common公共庫內(nèi)聲明一個接口 BaseApp:

        public interface BaseAppInit {
            boolean onInitCreate(Application application);

            boolean onInitTerminal(Application application);
        }

        然后每個組件都要創(chuàng)建一個 App 類實現(xiàn)此接口,比如在某個業(yè)務(wù)組件:

        public class AudioInit implements BaseAppInit {

            @Override
            public boolean onInitCreate(Application application) {
                return false;
            }

            @Override
            public boolean onInitTerminal(Application application) {
                return false;
            }
        }

        剩下最后一步就是從 app 殼工程分發(fā) application 的生命周期了,這里用到反射技術(shù):

        val moduleInitArr = arrayOf(
            "com.liang.lib_audio.app.AudioInit",
            "com.liang.lib_audio.app.VideoInit",
            "com.liang.lib_audio.app.LoginInit",
            
        )
        class AppApplication() {
            override fun onCreate() {
                super.onCreate()
                initModuleApp(this)
            }
            private fun initModuleApp(application: Application) {
                try {
                    for(appName in moduleInitArr) {
                        val clazz = Class.forName(appName)
                        val module = clazz.getConstructor().newInstance() as BaseApp
                        module.onCreate(application)
                    }
                }catch (e: Exception) {
                    e.printStackTrace()
                }
            }
        }

        我們只需要知道的每個實現(xiàn) BaseApp 接口的類的全限定名并寫到moduleInitArr數(shù)組里,然后通過反射獲取 Class 對象從而獲取構(gòu)造函數(shù)創(chuàng)建實體對象,最后調(diào)用 BaseApp 的 onCreate 方法將 application 傳入,每個Application生命周期的方法都可以通過這種方式傳遞。由于反射會消耗一定的性能,這個操作可以放在子線程,然后線程間通信。當(dāng)然,在每個module定義相對應(yīng)的初始化方法,然后主module 調(diào)用也可以實現(xiàn)初始化,此處使用反射是為了最大程度的解耦。






        為了防止失聯(lián),歡迎關(guān)注我防備的小號



         

                                               微信改了推送機制,真愛請星標(biāo)本公號??

        瀏覽 58
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            欧美老熟妇乱大交XXXXX www.91av | 毛片无码电影 | 欧美日韩大屌 | 日韩熟女精品一区二区三区 | 美女被肏网站 | 找个免费操逼的 | 人人操综合网 | 天堂俺去俺来也WWW色光网 | 中国的黄色视频 | 国产精品日日爽夜夜爽AV |