其中路由數據組件爲上層業務組件必需要依賴的庫,獨立功能組件和公共 UI 組件能夠根據需求選擇是否依賴。公共 UI 組件爲應用總體 UI 風格上的公共配置和封裝,通常業務組件也都會依賴。基礎SDK 爲最底層的 SDK 庫,全部的業務組件都基於它。頂層的業務 APP 通常按功能模塊進行劃分譬如:郵件 App
、IM App
、視頻 App
java
1、作組件化主要是隨着軟件的版本迭代,暴露出一個巨大的問題。同一個 module 下,各類數據跳轉之間高度的耦合了,雖然開發要求要注意代碼的耦合度,但團隊中每一個人的經驗水平和編碼風格都不同,對這個耦合程度的理解和標準也不同,隨着時間推移模塊間的代碼會越寫相互依賴程度越大。畢竟有時候明明能直接拿過來用,就不會太多的去考慮設計模式。作組件化將相對獨立的模塊獨立出去,達到硬性代碼隔離,強制下降模塊耦合度的目的。 2、項目隨着開發需求的不斷迭代會變得愈來愈龐大,開發過程當中項目整編是個很費時的事,組件化以後能夠靈活配置選擇須要的組件編譯,縮短期 3、多個項目中有的組件是能夠共用的,像我經歷過的兩個項目的網盤模塊和郵件模塊。未採用組件化方案,移代碼移資源太費時費力了。採用組件化方案,直接將 module 導入新的項目,增長對應的路由和 路由Service 方法就能用(前提是項目都採用組件化方案)android
一、在 BaseApplication 中建立 AppProxy 類,(這個類是 IAppLifeCycle 的一個實現類)。在 BaseApplication 的生命週期方法中調用 AppProxy 的生命週期方法 二、AppProxy 構造函數中掃描 Manifest 文件,掃描類中經過反射拿到每一個組件中的實現類。將這些實現類添加到 AppProxy 中的列表中。 三、在生命週期方法中循環第二步中的列表調用列表內各個 module 注入的生命週期代理對象的對應方法 核心處理即在 AppProxy 類中:git
各個 module 的代理實現類必定要註冊到 manifest 中,不然會掃描不到github
<!--配置 Application-->
<meta-data
android:name="com.pandaq.pandamvp.app.lifecycle.LifeCycleInjector"
android:value="AppInjector"/>
複製代碼
這樣配置以後咱們是沒辦法手動控制 module 生命週期方法的調用順序的,所以在 LifeCycleInjector
中增長了優先級選項,默認爲 0,數字越大越延後加載shell
/** * priority for lifeCycle methods inject * * @return priority 0 for first */
int priority();
複製代碼
onCreate()
方法中將 Activity 生命週期回調註冊到 application 中。經過 Application 來管理 Activity 生命週期。@Override
public void onCreate(@NonNull Application application) {
for (IAppLifeCycle appLifeCycle : mAppLifeCycles) {
appLifeCycle.onCreate(application);
}
// 註冊各個 module activity 生命週期回調方法
for (Application.ActivityLifecycleCallbacks callbacks : mActivityLifeCycles) {
application.registerActivityLifecycleCallbacks(callbacks);
}
}
@Override
public void onTerminate(@NonNull Application application) {
if (mAppLifeCycles != null) {
for (IAppLifeCycle appLifeCycle : mAppLifeCycles) {
appLifeCycle.onTerminate(application);
}
}
// app 生命週期結束時註銷 activity 生命週期回調
if (mActivityLifeCycles != null) {
for (Application.ActivityLifecycleCallbacks callbacks : mActivityLifeCycles) {
application.unregisterActivityLifecycleCallbacks(callbacks);
}
}
mAppLifeCycles = null;
mActivityLifeCycles = null;
mFragmentLifecycleCallbacks = null;
AppUtils.release();
}
複製代碼
private void registerFragmentCallbacks(Activity activity) {
//註冊框架外部, 開發者擴展的 BaseFragment 生命週期邏輯
for (FragmentManager.FragmentLifecycleCallbacks fragmentLifecycle : mFragmentLifeCycles) {
if (activity instanceof FragmentActivity) {
((FragmentActivity) activity).getSupportFragmentManager().registerFragmentLifecycleCallbacks(fragmentLifecycle, true);
}
}
}
複製代碼
描述可能不太容易看懂,具體的代碼能夠參考 PandaMvp數據庫
組件中的通訊這裏採用了 ARouter,具體使用這裏不展開,直接去看 ARouter 的文檔,幾個關鍵點: 1、頁面跳轉:.設計模式
@Route(path = "/test/activity")
public class YourActivity extend Activity {
...
}
複製代碼
ARouter.getInstance().build("/test/activity")
.withLong("key1", 666L)
.withString("key3", "888")
.withObject("key4", new Test("Jack", "Rose"))
.navigation();
複製代碼
2、頁面值的回傳:緩存
// 構建標準的路由請求,startActivityForResult
// navigation的第一個參數必須是Activity,第二個參數則是RequestCode
ARouter.getInstance().build("/home/main").navigation(this, 5);
複製代碼
3、Fragment 發現:app
// 獲取Fragment
Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();
複製代碼
4、跨組件方法調用:框架
// 聲明接口,其餘組件經過接口來調用服務, router 組件中定義
public interface EmailService extends IProvider {
EmailAccount getAccount();
}
// 實現接口對應的業務組件中實現
@Route(path = "/email/emailservice")
public class EmailServiceImpl implements EmailService {
@Override
public EmailAccount getAccount() {
return new EmailAccount();
}
@Override
public void init(Context context) {
}
}
複製代碼
// 調用組件中發現服務再調用方法
public class Test {
@Autowired(name = "/email/emailservice")
EmailService emailService;
public Test() {
ARouter.getInstance().inject(this);
EmailAccount account = emailService.getAccount();
}
}
複製代碼
爲避免書寫錯誤等問題,最好定義常量類統一管理路由的 path 併爲每一個組件使用不一樣的父路徑分組
獨立組件間的數據獲取傳遞都經過 Arouter 的服務來完成:
如圖所示,每個獨立的業務 module 在 router module 下都有一個本身的文件夾。以 email 組件爲例將,其餘的業務組件須要獲取郵件組件中用戶郵件的帳號信息和郵件簽名,則 email 將本身的帳號信息和簽名信息類 MailAccount
、MailSign
註冊到 router module 中,這樣其餘組件就能經過對 router module 的依賴識別這兩個數據類。A 組件須要從 Email 組件獲取 MailAccount。首先 Email 組件要在 router 中的服務 EmailService
接口中註冊對外暴露 getAccount() 方法,若是獲取爲異步的,則還須要再 callbacks 中註冊一個回調方法。A 中經過接口回調異步拿到 EmailService
給他的數據。
ORM 數據庫,項目採用的是 GreenDao3.0。由於 GreenDao 的初始化和 Tab生成不能跨 module,因此在存儲數據時有兩種方案: 一、每一個業務 module 本身維護數據庫。業務組件間須要通訊的數據再單首創建類下沉到一個公共庫中去。這種方式能保證業務數據的徹底獨立,但須要多寫數據實體類 二、直接把數據實體類都放在一個公共庫中,GreenDao 的初始化也放在這個庫中。我在項目的實際操做中是將要存入數據庫的實體類放入 Router module 中按文件夾分開存放的。 數據庫的操做工具類定義在對應的組件內,如 Email 組件,其中的緩存表操做工具類叫 EmailTb
,經過 EmailTb 的方法對數據庫進行增刪改查。Email 組件內部增刪改查沒有任何的阻礙隔離,若是 A組件須要對 Email 表進行增刪改查,則須要經過 EmailService 中註冊暴露的方法間接的增刪改查。若是 Email 未註冊暴露對應方法則其餘組件不能對 Email 數據庫操做
resourcePrefix "a_"
複製代碼
經過在 gradle 中配置 resourcePrefix 統一爲資源文件添加前綴限制,在編譯時命名不符合規範編譯器將會提示錯誤。進行組件化改造時這是個體力活,說多了都是淚
module 自由組合運行,則須要 module 既要有成爲 application 的能力又要有做爲 library 的能力。咱們經過 gradle.properties
和 build.gradle
文件配置,經過腳本在編譯時決定打包哪些業務組件 App 組成應用。
# 一、整編模式
# launchApp = app
# buildAll = true
#
# 二、單組件調試
# launchApp = xx (組件module名字)
# buildAll = false
#
# 二、多組件調試
# launchApp = app (組件 module 名字)
# buildAll = false
# loadComponents = xx1,xx2 (須要聯調的組件 module 名字)
#
# 打包容器 App module 名字
shellApp = app
# 被啓動的業務組件的名字,打包發佈時必定爲外殼 APP
launchApp = app_bmodule
#是否整編 App,true 的時候會殼 App 打包會依賴 allComponents。false 打包會依賴 loadComponents
buildAll = false
# 全部業務組件 App 的 module 名字
allComponents = app_amodule,app_bmodule
# 多業務組件放入 shellApp 聯合調試啓用的 module 名字
loadComponents =
複製代碼
公共的 build.gradle 中配置
// 根據配置是否爲 launchApp 決定業務組件 module 是做爲 library 仍是獨立 App
boolean isShellApp = project.getName() == shellApp
boolean isLaunchApp = project.getName() == launchApp
if (isLaunchApp) { // 殼 APP 始終以 application 模式運行,其餘業務組件以依賴庫模式根據配置拔插
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
·
·
·
sourceSets {
main {
jniLibs.srcDirs = ['libs']
if (isShellApp){ // 容器 App 只會以 App模式運行或者不運行
manifest.srcFile 'src/main/AndroidManifest.xml'
}else {
// application 模式和 library 模式的清單文件是不同的,這裏根據 isLaunchApp 肯定使用哪個
if (isLaunchApp) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
}
}
}
}
複製代碼
各業務組件 module 的,build.gradle:
·
·
·
defaultConfig { //根據是否爲 launchApp 決定添加 applicationId 和版本號
if (isLaunchApp) {
applicationId "com.pandaq.app_amodule"
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
·
·
·
複製代碼
容器 App module(主 module),相較於通常業務組件 module 的 build.gradle,還須要配置多組件打包和整編打包時動態依賴業務組件庫:
·
·
·
defaultConfig { //根據是否爲 launchApp 決定添加 applicationId 和版本號
if (isLaunchApp) {
applicationId "com.pandaq.app_amodule"
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
·
·
·
dependencies{
·
·
·
// 按需加載依賴業務 APP
if (buildAll.toBoolean()) { //整編時將業務組件所有添加依賴
for (String name : allComponents.split(",")) {
implementation(project(":$name")) {
exclude group: 'com.android.support'
exclude module: 'appcompat-v7'
exclude module: 'support-v4'
}
}
} else { //非整編時能夠選擇業務組價加入容器 App
for (String name : loadComponents.split(",")) {
if (!name.isEmpty()) {
implementation(project(":$name")) {
exclude group: 'com.android.support'
exclude module: 'appcompat-v7'
exclude module: 'support-v4'
}
}
}
}
}
複製代碼
組件化有風險,推動需謹慎。一個非組件化的大型項目要對其進行組件化改造這個過程是漫長而艱鉅的,項目中各個模塊不可避免的會有各類耦合關係,每每牽一髮而動全身,要對它進行組件化改造。首先要對項目進行封裝解耦,獨立的功能該下沉的下沉,該重寫的重寫。有時候代碼的複用對組件化改造簡直是災難,尤爲是原本不屬於一個功能模塊的界面進行了複用這種。