PS:你現在所堅持的,也許在當下沒有什麼效果,不代表將來在某個時刻會用得上。
當 App 項目複雜到一定程度,將項目組件化是必不可少的,組件化可以更好地進行功能的劃分,提到組件化有人可能會想到模塊化,其實組件化和模塊化的本質是一樣的,都是為了代碼重用的業務解耦,模塊化主要按照業務劃分,而組件化主要按照功能劃分,從組件化最基礎的幾個方面打開組件化的大門。
- 組件之間的跳轉
- 動態創建
- 資源衝突
- 靜態常量
組件之間的跳轉#
組件化中兩個功能模塊時不直接依賴的,其依賴規則是通過 Base module 間接依賴,當組件之間的 Activity 進行界面跳轉時,由於沒有相互依賴的關係,往往會無法引用另一個 module 中的 Activity。
隱式跳轉#
隱式跳轉是通過 Android 原生 Intent 匹配機制來實現相應跳轉,就是使用 Action 來跳轉到對應的 Activity,這樣使用隱式跳轉的方式就可以跨 module 實現 Activity 之間的跳轉了,注意一點,如果移出 Activity 所在的 module 而不移出相應的跳轉,如果繼續跳轉會出現異常,使用隱式 Intent 跳轉需要驗證是否會接收該 Intent,需要對該 Intent 對象調用 resolveActivity () 方法來判斷至少有一個應用能夠處理該 Intent,通過隱式跳轉的方式還可以設置 exported 為 false 來確保只有自己的 App 才能夠啟動對應的組件。
ARouter 跳轉#
在 Android 開發中可將 module 看成不同的網絡,而對應的 Router 就是連接各個 module 的中轉站,這個中轉站可以對頁面跳轉的參數等進行統一處理,ARouter 是阿里開源出來的一個頁面跳轉路由,使用 ARouter 可以替代隱式跳轉來完成不同 module、不同組件之間的跳轉以及跳轉過程的監聽、參數的傳遞等,ARouter 支持路徑跳轉和 URL 跳轉兩種方式,使用也非常靈活,ARouter 的具體使用這裡不做介紹,其具體使用會在單獨一篇文章中詳解,ARouter 與 Android 傳統跳轉方式的對比如下:
- 顯示跳轉需要依賴於類,而路由跳轉通過指定的路徑跳轉;
- 隱式跳轉通過 AndroidManifest 集中管理,導致協作開發困難;
- 原生使用 AndroidManifest 來註冊,而路由使用註解註冊;
- 原生 startActivity 之後跳轉過程交由 Android 系統控制,而路由跳轉採用的是 AOP 切面編程可對跳轉過程進行攔截和過濾。
動態創建#
組件化開發中最重要的一點就是各個模塊、各個組件之間要盡可能解耦,這樣很容易就會想到使用 Java 中的反射機制,使用反射可在運行狀態下獲取某個類的所有信息,然後就可以動態操作這個類的屬性和方法了。如果 Fragment 單獨作為一個組件來使用時,當這個 Fragment 組件不需要被移出後,如果是常規的 Fragment 則會因為索引不到該 Fragment 而使得 App 崩潰,想一下如果使用反射創建 Fragment 的方式則至少不會引起 App 崩潰,這裡可以捕捉異常完成相關邏輯,這樣是不是降低了耦合呢。可見,雖然反射有一定的性能問題,但使用反射確實能在一定程度上降低耦合,學習組件化 Java 反射機制應該是必須的一部分。
組件化開發中要求每個組件都能獨立運行,一般情況下每個組件都有一定的初始化步驟,最好的一種情況是項目需要的幾個組件的初始化基本相同,那就可將初始化放在 BaseModule 中進行統一初始化,但是這種情況畢竟比較理想,一般情況是每個組件的初始化都不一樣,可能你會想到在各自的 Application 初始化,如果在各自的 Application 中初始化,當在最終編譯由於 Application 的合併難免會出一些問題,這種方式也不可取,到這裡又想到了反射,在各組件中創建初始化文件,然後在最終的 Application 中通過反射完成各個組件的初始化操作,這裡通過 Java 的反射機制完成了組件化開發中 Application 的動態配置。
資源衝突#
組件化開發過程中,如果 ModuleA 的 AmdroidManifest 文件中使用 android 屬性指定了相應的 Application,而主 App Module 的 AndroidManifest 文件中也使用 android 屬性指定了相對應的 Application,此時就必須在 主 App Module 的 AndroidManifest 文件中使用 tools="android" 來解決衝突,使用 replace 屬性表示該屬性也就是在 標籤下的 android 屬性可在編譯過程中被替換,這樣根據 AndroidManifest 文件替換規則最終指定的 Application 應該是 App Module 中的指定的 Application。
舉一個例子,我在項目中的某個功能 Module 中使用 SMSSDK 來完成短信驗證的功能,因為其他地方不用,所以只引入到了要使用的功能 Module 中,如果其他 Module 會使用應該將 SMSSDK 引入到 BaseModule 中,使用 SMSSDK 如果不指定該 Module 的 Application,MobSDK 會將 com.mob.MobApplication 指定為該 Module 的 Application,此時在整體編譯打包時就會出現 AndroidManifest 文件的 android 屬性衝突,當然了解決方法就是使用 replace 屬性了。 AndroidManifest 文件合併後的主要衝突也就是這個問題了,當然 下的其他屬性有衝突,也是使用 replace 屬性。在實際的開發中多驗證會更有收穫喔。
組件化開發中另外需要注意的一點是防止資源名稱一樣導致最終合併的時候,因為衝突造成資源引用錯誤或者某些資源丟失等,如字符串、顏色值等資源等合併的時候會被後面加載的相同名稱的資源所替換,解決的思路是在資源命名上要有一定的規則,可以在 build.gradle 文件中配置 "resourcePrefix" 組件名稱 ""的方式強制約束開發者確保資源名稱唯一,建議 Module 中資源的命名格式為"Module 名稱_功能_其他 "。
靜態常量#
組件化開發中,最終合併時每個組件都是以 Lib Module 的形式存在,而 Lib Module 中 R.java 文件中定義的靜態變量沒有聲明為 final,這就意味著不能在組件 Module 中使用相對應的常量了,如在 switch 語句中就不能使用了,這就要求在組件中要使用 if 語句來替代 switch 語句,當然在組件獨立運行的時候是沒有這個問題的。
開發中經常會使用到 Butterknife,Butterknife 可非常方便地對 View 及 View 的事件等進行註解操作,它採用的是編譯時註解機制,註解中只能使用常量,所以在 Butterknife 在組件化開發中應該使用 R2 代替 R,R2 實際上是 R 的拷貝, R2 對應聲明的變量是 final,所以在組件化開發中如果使用 Butterknife 在相應的註解中要使用 R2 替代 R,下篇將介紹 Application 的組件化。