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)目。

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

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"

然后點(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和其布局文件搬過去。

接著進(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里來。

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

接著在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)行了。

步驟四:新建其他組件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ù)。

接下來解決資源文件沖突的問題,進(jìn)入ModuleMain的build.gradle添加下面這行代碼,為資源文件命名規(guī)范一個(gè)統(tǒng)一開頭:
resourcePrefix "modulemain_"
添加后起名是沒按照規(guī)范Android Studio就會(huì)有一個(gè)提示:

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

步驟五:使用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)行效果:


這里看到模塊之間界面通信正常,并且單個(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。

將原來項(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:

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

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

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



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

接著在到Module的build.gradle中添加依賴:
接著在到Module的build.gradle中添加依賴:
implementation rootProject.ext.dependencies.utilmodule
這里就是之前設(shè)置的包名:Module名:版本號(hào)。SyncNow之后刪除原來項(xiàng)目中的工具類,然后在代碼里使用遠(yuǎn)程倉(cāng)庫(kù)的工具類測(cè)試:

運(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)注哦
