Android元件化入門:一步步搭建元件化架構
最近因為業務需求變更,有考慮採用元件化架構進行開發,這方面我之前沒有接觸過。關於元件化的文章很多,各方大神更是提出了各種的元件化方案,我也看了很多相關文章。但是學習新東西看的再多,不如動手做一次,先不考慮複雜的東西,先動手做個簡單的Demo更有助於理解元件化的思想。元件化相關理論知識這裡就不多講了,想要了解的可以自己去搜或者去看Android元件化方案這篇文章。廢話不多說,直接動手開碼。
2、搭建元件化Demo
先開啟 Android Studio 新建一個專案。

步驟一: 新建config.gradle,統一管理build.gradle中的相關內容
然後在專案目錄下新建一個config.gradle檔案。

接著在這個檔案內新增如下程式碼:
ext { //applicationId版本號sdkVersion統一管理 android = [ compileSdkVersion: 28, buildToolsVersion: 28, applicationId: "com.example.componenttestdemo", minSdkVersion: 19, targetSdkVersion: 28, versionCode: 1, versionName: "1.0", testInstrumentationRunner: "android.support.test.runner.AndroidJUnitRunner" ] //版本號 def APPCOMPAT_V7_VERSION = "28.0.0" def CONSTRAINT_LAYOUT_VERSION = "1.1.3" //三方庫統一管理 dependencies = [ appcompatV7: 'com.android.support:appcompat-v7:' + APPCOMPAT_V7_VERSION, constraintLayout: 'com.android.support.constraint:constraint-layout:' + CONSTRAINT_LAYOUT_VERSION ] } 複製程式碼
因為我們知道專案使用元件化架構後,單一模組 Module
可以作為單個 Application
執行,同時也可以在整個主 Application
中作為一個 Module
執行。所以在 config.gradle
中先定義一個 isModule
來區別這兩種情況,元件化之後可以通過修改這個值來切換這兩種情況的使用。剩下就是對 applicationId
、版本號、 sdkVersion
和三方庫等進行統一管理。
接著修改app下的build.gradle裡設定內容
將原來的 compileSdkVersion
、 applicationId
、 minSdkVersion
、 versionCode
和三方庫等替換成對應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 } 複製程式碼
最後還要在專案目錄下的build.gradle中新增一行:
apply from : "config.gradle" 複製程式碼

Sync Now
同步。最後在進行下一步前,新建一個MyApplication,在
AndroidManifest
設定name屬性。
步驟二:建立Main模組,搬空app殼工程
我們知道元件化中需要一個app殼工程,這個殼工程中不處理任何業務,就只是一個空殼,由它將所需要的各個元件模組組合起來,構成一個完整的應用。而現在專案中的app還是存在預設的入口 Activity
的,所以要新建一個 ModuleMain
將預設的 MainActivity
和其佈局檔案搬過去。

接著進入app的 AndroidManifest
檔案將註冊Activity的相關程式碼也搬到 ModuleMain
模組的 AndroidManifest
中去,只留下 application
標籤。
這裡注意元件化專案中每個Module都會有自己的 AndroidManifest
檔案,最後打包時會將這些檔案合併成一個檔案,所以會出現 application
標籤中的屬性重複問題,要在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
都可能會有重複,所以全部寫上之間用逗號隔開。完整 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
的內容複製覆蓋到Main模組的 build.gradle
中,並且還要做部分修改。因為單個元件可以作為一個元件模組被app殼工程組合使用,也可以單獨作為一個 application
使用。所以要根據 config.gradle
中定義的 isModule
來判斷是作為Module還是Applicaition。同樣還有作為Module是不需要 applicationId
的而作為應用則是需要的。
//通過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 } 複製程式碼
這時modulemain中的 AndroidManifest
會提示資原始檔的缺少,這時先將app中的對應檔案複製到modulemain裡來。

步驟三 :新建 Application
和 AndroidManifest
檔案
在app殼工程和ModuleMain中分別新建一個 Application
,因為Moudule也是需要可以單獨執行的。

AndroidManifest
檔案,因為作為
Module
和
Application
是會有不一樣的所以要做區分,在main目錄下新建module資料夾和application資料夾分別存放兩個情況下的
AndroidManifest
檔案,將原來的
AndroidManifest
檔案拖到module下,再拷貝一份到application下。拷貝完了記得在兩個
AndroidManifest
裡
application
標籤下設定name屬性。
接著再 build.gradle
中新增如下程式碼,用來分別在兩種情況下指定使用哪個 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
內新增以下程式碼,用來引入ModuleMain模組。
if (rootProject.ext.isModule) { implementation project(":modulemain") } 複製程式碼
現在可以再次點選 Sync Now
等同步結束後,雖然專案中只有一個殼工程和一個主Module,但是已可以看到元件化的雛形。此時已經可以通過修改 config.gradle
裡的 isModule
的值,進行 Application
和 Module
兩種模式的切換,將ModuleMain作為app 的模組執行或者是單獨作為一個應用運行了。

步驟四:新建其他元件Module和解決資原始檔衝突
接著按照新建ModuleMain的步驟重複新建其他業務Module,這裡我新建了3個Module,業務A:ModuleA與業務B:ModuleB和一個BaseModule。其中BaseModule主要存放一些基礎類和工具類,只做為Module為上層業務模組提供服務。

build.gradle
新增下面這行程式碼,為資原始檔命名規範一個統一開頭:
resourcePrefix "modulemain_" 複製程式碼
新增後起名是沒按照規範 Android Studio
就會有一個提示:

按要求修改檔名後提示消失。

步驟五:使用ARouter進行元件間通訊
接下來就要處理元件間的通訊問題,採用阿里的 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();// 開啟除錯模式(如果在InstantRun模式下執行,必須開啟除錯模式!線上版本需要關閉,否則有安全風險) } ARouter.init(mApplication); // 儘可能早,推薦在Application中初始化 複製程式碼
這樣 ARouter
就整合好了,接著在MoudleA和ModuleB中新建兩個 Activity
,然後使用 ARouter
進行頁面跳轉。
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","傳遞的資料").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); } } 複製程式碼
執行效果:


這裡看到模組之間介面通訊正常,並且單個業務模組也可以單獨執行,這樣一個最基本的元件化架構Demo差不多就完成了。
3、將Module作為遠端maven倉庫
在開發中,可能會把一些公用Module傳到私有伺服器上,然後在專案中直接依賴使用。下面就將Module上傳到Github作為遠端maven倉庫,在專案直接引用。首先新建一個專案,建立一個UtilModule。

將原來專案中的工具類移到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" // 版本號 } } } 複製程式碼
然後點選 gradle
中的 uploadArchives
:

進入設定的目錄檢視,aar已經打包好了。

接著開啟Github建立一個新倉庫:

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



github.com
部分修改為
raw.githubusercontent.com
再在結尾加上
/master
表示是主分支,新增到專案中的
build.gradle
中。

build.gradle
中新增依賴:
utilmodule: 'com.example.utilmodule:utilmodule:' + UTIL_MODULE_VERSION 複製程式碼
implementation rootProject.ext.dependencies.utilmodule 複製程式碼
這裡就是之前設定的包名:Module名:版本號。 SyncNow
之後刪除原來專案中的工具類,然後在程式碼裡使用遠端倉庫的工具類測試:

執行列印日誌:
D/com.example.modulemain.MainActivity: onCreate:false 複製程式碼
這說明遠端倉庫依賴成功已經能正常使用其中的類和方法。
4、總結
這篇文章主要是記錄下我初識元件化,搭建元件化Demo的過程,Demo主要對於我對元件化思想的理解和體驗還是很有幫助的,Demo中還有很多沒考慮到的地方,比如Application的動態配置合併、Fragment、元件化的混淆等等,也是我正在學習的問題。這篇文章主要供和我一樣對元件化這塊不太瞭解的新手做參考,希望能對新手有所幫助。