Android組件化入門:一步步搭建組件化架構(gòu)

1、前言

最近因?yàn)闃I(yè)務(wù)需求變更,有考慮采用組件化架構(gòu)進(jìn)行開發(fā),這方面我之前沒有接觸過。關(guān)于組件化的文章很多,各方大神更是提出了各種的組件化方案,我也看了很多相關(guān)文章。但是學(xué)習(xí)新東西看的再多,不如動(dòng)手做一次,先不考慮復(fù)雜的東西,先動(dòng)手做個(gè)簡(jiǎn)單的Demo更有助于理解組件化的思想。

2、搭建組件化Demo

先打開Android Studio新建一個(gè)項(xiàng)目。

image.png

步驟一: 新建config.gradle,統(tǒng)一管理build.gradle中的相關(guān)內(nèi)容
然后在項(xiàng)目目錄下新建一個(gè)config.gradle文件。

image.png
ext {
    //applicationId版本號(hào)sdkVersion統(tǒng)一管理
    android = [
            compileSdkVersion        : 28,
            buildToolsVersion        : 28,
            applicationId            : "com.example.componenttestdemo",
            minSdkVersion            : 19,
            targetSdkVersion         : 28,
            versionCode              : 1,
            versionName              : "1.0",
            testInstrumentationRunner: "android.support.test.runner.AndroidJUnitRunner"
    ]

    //版本號(hào)
    def APPCOMPAT_V7_VERSION = "28.0.0"
    def CONSTRAINT_LAYOUT_VERSION = "1.1.3"

    //三方庫(kù)統(tǒng)一管理
    dependencies = [
            appcompatV7     : 'com.android.support:appcompat-v7:' + APPCOMPAT_V7_VERSION,
            constraintLayout: 'com.android.support.constraint:constraint-layout:' + CONSTRAINT_LAYOUT_VERSION
    ]
}

因?yàn)槲覀冎理?xiàng)目使用組件化架構(gòu)后,單一模塊Module可以作為單個(gè)Application運(yùn)行,同時(shí)也可以在整個(gè)主Application中作為一個(gè)Module運(yùn)行。所以在config.gradle中先定義一個(gè)isModule來區(qū)別這兩種情況,組件化之后可以通過修改這個(gè)值來切換這兩種情況的使用。剩下就是對(duì)applicationId、版本號(hào)、sdkVersion和三方庫(kù)等進(jìn)行統(tǒng)一管理。

接著修改app下的build.gradle里設(shè)置內(nèi)容

將原來的compileSdkVersion、applicationId、minSdkVersion、versionCode和三方庫(kù)等替換成對(duì)應(yīng)config.gradle中定義的值。

apply plugin: 'com.android.application'

android {
    compileSdkVersion rootProject.ext.android.compileSdkVersion
    defaultConfig {
        applicationId rootProject.ext.android.applicationId
        minSdkVersion rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion
        versionCode rootProject.ext.android.versionCode
        versionName rootProject.ext.android.versionName
        testInstrumentationRunner rootProject.ext.android.testInstrumentationRunner
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation rootProject.ext.dependencies.appcompatV7
    implementation rootProject.ext.dependencies.constraintLayout

}

最后還要在項(xiàng)目目錄下的build.gradle中添加一行:

apply from : "config.gradle"

image.png

然后點(diǎn)擊Sync Now同步。最后在進(jìn)行下一步前,新建一個(gè)MyApplication,在AndroidManifest設(shè)置name屬性。

步驟二:創(chuàng)建Main模塊,搬空app殼工程

我們知道組件化中需要一個(gè)app殼工程,這個(gè)殼工程中不處理任何業(yè)務(wù),就只是一個(gè)空殼,由它將所需要的各個(gè)組件模塊組合起來,構(gòu)成一個(gè)完整的應(yīng)用。而現(xiàn)在項(xiàng)目中的app還是存在默認(rèn)的入口Activity的,所以要新建一個(gè)ModuleMain將默認(rèn)的MainActivity和其布局文件搬過去。

image.png

接著進(jìn)入app的AndroidManifest文件將注冊(cè)Activity的相關(guān)代碼也搬到ModuleMain模塊的AndroidManifest中去,只留下application標(biāo)簽。

這里注意組件化項(xiàng)目中每個(gè)Module都會(huì)有自己的AndroidManifest文件,最后打包時(shí)會(huì)將這些文件合并成一個(gè)文件,所以會(huì)出現(xiàn)application標(biāo)簽中的屬性重復(fù)問題,要在app的AndroidManifest文件中添加如下兩行代碼:

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

這里的name、label、icon、theme、allowBackup都可能會(huì)有重復(fù),所以全部寫上之間用逗號(hào)隔開。完整AndroidManifest如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.sy.modulesimpledemo"
    xmlns:tools="http://schemas.android.com/tools"    >

    <application
        android:name=".application.MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        tools:replace="android:name,android:label,android:icon, android:theme,android:allowBackup"
        android:theme="@style/AppTheme">
    </application>

</manifest>

接著app殼工程中只剩剛修改的build.gradle還沒刪減,在刪減前先將app中build.gradle的內(nèi)容復(fù)制覆蓋到Main模塊的build.gradle中,并且還要做部分修改。因?yàn)閱蝹€(gè)組件可以作為一個(gè)組件模塊被app殼工程組合使用,也可以單獨(dú)作為一個(gè)application使用。所以要根據(jù)config.gradle中定義的isModule來判斷是作為Module還是Applicaition。同樣還有作為Module是不需要applicationId的而作為應(yīng)用則是需要的。

//通過isModule來判斷是application還是module
if (rootProject.ext.isModule) {
     apply plugin: 'com.android.library'
} else {
    apply plugin: 'com.android.application'
}
android {
    compileSdkVersion rootProject.ext.android.compileSdkVersion
    defaultConfig {
        //是application才需要applicationId
        if (!rootProject.ext.isModule) {
            applicationId "com.example.sy.moduledmain"
        }
        minSdkVersion rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion
        versionCode rootProject.ext.android.versionCode
        versionName rootProject.ext.android.versionName
        testInstrumentationRunner rootProject.ext.android.testInstrumentationRunner
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation rootProject.ext.dependencies.appcompatV7
    implementation rootProject.ext.dependencies.constraintLayout
}

這時(shí)modulemain中的AndroidManifest會(huì)提示資源文件的缺少,這時(shí)先將app中的對(duì)應(yīng)文件復(fù)制到modulemain里來。

image.png

步驟三 :新建Application和AndroidManifest文件
在app殼工程和ModuleMain中分別新建一個(gè)Application,因?yàn)镸oudule也是需要可以單獨(dú)運(yùn)行的。
image.png

接著在ModuleMain里新建AndroidManifest文件,因?yàn)樽鳛镸odule和Application是會(huì)有不一樣的所以要做區(qū)分,在main目錄下新建module文件夾和application文件夾分別存放兩個(gè)情況下的AndroidManifest文件,將原來的AndroidManifest文件拖到module下,再拷貝一份到application下??截愅炅擞浀迷趦蓚€(gè)AndroidManifest里application標(biāo)簽下設(shè)置name屬性。
接著再build.gradle中添加如下代碼,用來分別在兩種情況下指定使用哪個(gè)AndroidManifest。

sourceSets {
        main {
            if (rootProject.ext.isModule) {
                manifest.srcFile 'src/main/module/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/application/AndroidManifest.xml'
                java {
                    //排除java/module文件夾下的所有文件
                    exclude '*module'
                }
            }
        }
    }

然后再到app的build.gradle中在dependencies內(nèi)添加以下代碼,用來引入ModuleMain模塊。

if (rootProject.ext.isModule) {
    implementation project(":modulemain")
}

現(xiàn)在可以再次點(diǎn)擊Sync Now等同步結(jié)束后,雖然項(xiàng)目中只有一個(gè)殼工程和一個(gè)主Module,但是已可以看到組件化的雛形。此時(shí)已經(jīng)可以通過修改config.gradle里的isModule的值,進(jìn)行Application和Module兩種模式的切換,將ModuleMain作為app 的模塊運(yùn)行或者是單獨(dú)作為一個(gè)應(yīng)用運(yùn)行了。

image.png

步驟四:新建其他組件Module和解決資源文件沖突
接著按照新建ModuleMain的步驟重復(fù)新建其他業(yè)務(wù)Module,這里我新建了3個(gè)Module,業(yè)務(wù)A:ModuleA與業(yè)務(wù)B:ModuleB和一個(gè)BaseModule。其中BaseModule主要存放一些基礎(chǔ)類和工具類,只做為Module為上層業(yè)務(wù)模塊提供服務(wù)。

image.png

接下來解決資源文件沖突的問題,進(jìn)入ModuleMain的build.gradle添加下面這行代碼,為資源文件命名規(guī)范一個(gè)統(tǒng)一開頭:

resourcePrefix "modulemain_"

添加后起名是沒按照規(guī)范Android Studio就會(huì)有一個(gè)提示:


image.png

按要求修改文件名后提示消失。

image.png

步驟五:使用ARouter進(jìn)行組件間通信

接下來就要處理組件間的通信問題,采用阿里的ARouter。按照文檔集成ARouter。 首先在defaultConfig下添加如下代碼:

javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }

再引入ARouter依賴:

implementation rootProject.ext.dependencies.arouter
implementation rootProject.ext.dependencies.arouterCompiler

最后在Application中初始化ARouter:

if (isDebug()) {           // 這兩行必須寫在init之前,否則這些配置在init過程中將無效
    ARouter.openLog();     // 打印日志
    ARouter.openDebug();   // 開啟調(diào)試模式(如果在InstantRun模式下運(yùn)行,必須開啟調(diào)試模式!線上版本需要關(guān)閉,否則有安全風(fēng)險(xiǎn))
}
ARouter.init(mApplication); // 盡可能早,推薦在Application中初始化

這樣ARouter就集成好了,接著在MoudleA和ModuleB中新建兩個(gè)Activity,然后使用ARouter進(jìn)行頁(yè)面跳轉(zhuǎn)。

ModuleMain中MainActivity.java:

public class MainActivity extends BaseActivity  {
    /**
     * toA
     */
    private Button mModulemainA;
    /**
     * toB
     */
    private Button mModulemainB;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.modulemain_activity_main);
        initView();
        initEvent();
    }

    private void initEvent() {
        mModulemainA.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ARouter.getInstance().build(ARouterPath.PATH_MOUDULE_A).navigation();
            }
        });

        mModulemainB.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ARouter.getInstance().build(ARouterPath.PATH_MOUDULE_B).withString("key","傳遞的數(shù)據(jù)").navigation();
            }
        });
    }

    private void initView() {
        mModulemainA = (Button) findViewById(R.id.modulemain_a);
        mModulemainB = (Button) findViewById(R.id.modulemain_b);
    }

}

ModuleA中ModuleAActivity.java:

@Route(path = ARouterPath.PATH_MOUDULE_A)
public class ModuleAActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.modulea_activity_module_a);
    }
}

ModuleB中ModuleBActivity.java:

@Route(path = ARouterPath.PATH_MOUDULE_B)
public class ModuleBActivity extends AppCompatActivity {
    @Autowired(name = "key")
    String data;
    /**
     * TextView
     */
    private TextView mTextViewB;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.moduleb_activity_module_b);
        ARouter.getInstance().inject(this);
        initView();
    }
    private void initView() {
        mTextViewB = (TextView) findViewById(R.id.textViewB);
        mTextViewB.setText(data);
    }
}

運(yùn)行效果:


image.png

image.png

這里看到模塊之間界面通信正常,并且單個(gè)業(yè)務(wù)模塊也可以單獨(dú)運(yùn)行,這樣一個(gè)最基本的組件化架構(gòu)Demo差不多就完成了。

3、將Module作為遠(yuǎn)程maven倉(cāng)庫(kù)

在開發(fā)中,可能會(huì)把一些公用Module傳到私有服務(wù)器上,然后在項(xiàng)目中直接依賴使用。下面就將Module上傳到Github作為遠(yuǎn)程maven倉(cāng)庫(kù),在項(xiàng)目直接引用。首先新建一個(gè)項(xiàng)目,創(chuàng)建一個(gè)UtilModule。


image.png

將原來項(xiàng)目中的工具類移到UtilModule中,接著在UtilModule的build.gradle中添加以下代碼:

apply plugin: 'maven'
uploadArchives {
    repositories.mavenDeployer {
        def mavenDirPath = file('\\Users\\sy\\AndroidProjects\\UtilModule') // 本地存放地址
        repository(url:"file://${mavenDirPath.absolutePath}")
        pom.project {
            groupId "com.example.utilmodule" // 包名
            artifactId "utilmodule" // module的名字
            version "1.0.0" // 版本號(hào)
        }
    }
}

然后點(diǎn)擊gradle中的uploadArchives:


image.png

進(jìn)入設(shè)置的目錄查看,aar已經(jīng)打包好了。


image.png

接著打開Github創(chuàng)建一個(gè)新倉(cāng)庫(kù):


image.png

按照Github上的命令,將本地打包好的UtilModule上傳到Github上:
image.png

image.png

image.png

上傳完成后將倉(cāng)庫(kù)地址復(fù)制下來,將其中的github.com部分修改為raw.githubusercontent.com再在結(jié)尾加上/master表示是主分支,添加到項(xiàng)目中的build.gradle中。


image.png

接著在到Module的build.gradle中添加依賴:
接著在到Module的build.gradle中添加依賴:
implementation rootProject.ext.dependencies.utilmodule

這里就是之前設(shè)置的包名:Module名:版本號(hào)。SyncNow之后刪除原來項(xiàng)目中的工具類,然后在代碼里使用遠(yuǎn)程倉(cāng)庫(kù)的工具類測(cè)試:

image.png

運(yùn)行打印日志:

D/com.example.modulemain.MainActivity: onCreate:false

這說明遠(yuǎn)程倉(cāng)庫(kù)依賴成功已經(jīng)能正常使用其中的類和方法。

4、總結(jié)

這篇文章主要是記錄下我初識(shí)組件化,搭建組件化Demo的過程,Demo主要對(duì)于我對(duì)組件化思想的理解和體驗(yàn)還是很有幫助的,Demo中還有很多沒考慮到的地方,比如Application的動(dòng)態(tài)配置合并、Fragment、組件化的混淆等等,也是我正在學(xué)習(xí)的問題。這篇文章主要供和我一樣對(duì)組件化這塊不太了解的新手做參考,希望能對(duì)新手有所幫助。

喜歡請(qǐng)點(diǎn)擊+關(guān)注哦

最后編輯于
?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

  • MVVMHabitComponent 關(guān)于Android的組件化,相信大家并不陌生,網(wǎng)上談?wù)摻M件化的文章,多如過江...
    goldze閱讀 5,788評(píng)論 2 22
  • 問題 在已經(jīng)開發(fā)過幾個(gè)項(xiàng)目的童鞋,如果這時(shí)需要重新開發(fā)一個(gè)新項(xiàng)目,是否需要自己重新搭建框架呢,還是從老項(xiàng)目中拷貝粘...
    8ba406212441閱讀 43,565評(píng)論 84 380
  • 不怕跌倒,所以飛翔 組件化開發(fā) 參考資源 Android組件化方案 為什么要組件化開發(fā) 解決問題 實(shí)際業(yè)務(wù)變化非常...
    筆墨Android閱讀 3,096評(píng)論 0 0
  • Android 架構(gòu)系列:Android 架構(gòu)一:Android 架構(gòu)淺析Android 架構(gòu)二:縱向橫向結(jié)合構(gòu)建...
    IT前沿技術(shù)分享閱讀 1,890評(píng)論 2 16
  • 蟈蟈與蛐蛐 現(xiàn)代 · 濟(jì)慈 大地的詩(shī)歌從來不會(huì)死亡:當(dāng)所有的鳥兒因驕陽(yáng)而昏暈, 隱藏在陰涼的林中,就有一種聲音 在...
    冰大頭閱讀 201評(píng)論 0 0

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