阿裡4輪面試,兩輪面試都被問到元件化問題,面試的點各不相同,有元件化架構設計、插件化架構設計、路由架構設計、熱修複設計等問題,但是最終都是殊途同歸,所有的問題都彙集在這,如何對手機淘寶組架構設計?

元件化如何實作,元件化與插件化的差别在 哪裡,該怎麼選型
**面試官:**元件化如何實作,元件化與插件化的差别在哪裡,該怎麼
選型
**心理分析:**面試官從架構層次 了解求職者是否用過 子產品化 元件 化 和插件化,在過去經驗有沒有運用過這些技術到項目中,這道 題屬于一個連環炮。求職者該格外小心
**求職者:**應該從 App 開發的需求來定義技術選型,分别說說模 塊化,元件化 插件化的優勢和差別
一、元件化
元件化,就是把 APP 拆分成不同功能子產品,形成獨立元件,讓宿主調用。 元件 化不一定是插件化,元件化是一個更大的概念:把子產品解耦,元件之間代碼不依 賴,宿主可以依賴元件;而插件化則具體到了技術點上,宿主通過 動态加載 來 調用元件,宿主不依賴元件,達到 完全解耦 的目的(比如圖檔緩存就可以看成 一個元件被多個 App 共用)。
**适合于項目大 但是功能相對集中。**比如 一個金融類的 App 裡面隻包含金融的 功能,金融功能又會有 借貸,理财,線下交易,把這些子產品抽成單獨的元件 二、插件化 Android 程式每次更新都要下載下傳一個完整的 apk,而很多時候軟體隻是更新了一 個小功能而已,這樣的話,就顯得很麻煩。如果把 android 程式做成主程式+插 件化的形式呢,這樣才利于小功能的擴充(比如一般 App 的皮膚樣式就可以看 成一個插件)。
二、插件化
Android 程式每次更新都要下載下傳一個完整的 apk,而很多時候軟體隻是更新了一 個小功能而已,這樣的話,就顯得很麻煩。如果把 android 程式做成主程式+插 件化的形式呢,這樣才利于小功能的擴充(比如一般 App 的皮膚樣式就可以看 成一個插件)。
通過 gradle 配置的方式,将打 debug 包和 release 包分開。這 樣會有一個好處,開發一個子產品,在 debug 的時候,可以打成一 個 apk ,獨立運作測試,可以完全獨立于整個宿主 APP 的其他 所有元件;待到要打 release 包的時候,再把這個子產品作為一個 library ,打成 aar ,作為整個宿主 APP 的一部分。而 debug 和 release 的切換都是通過 gradle 配置,可以做到無縫切換。至于 子產品之間的跳轉,可以用别名的方式,而不是用 Activity 和 Fragment 類名。這樣所有的子產品和宿主 APP 都是完全解耦的, 徹底解決了并行開發的可能造成的交叉依賴等問題
主要原理是:主要利用 Java ClassLoader 的原理,如 Android 的 DexClassLoader,可動态加載的内容包括 apk、dex、jar 等。如下
插件化的優勢:
- 适應并行開發,解耦各個子產品,避免子產品之間的交叉依賴,加快編譯速度, 進而提高并行開發效率。
- 滿足産品随時上線的需求
- 修複因為我們對自己要求不嚴格而寫出來的 bug。
- 插件化的結果:分為穩定的 release 版本和不穩定的 snapshot 版本,每 個子產品都高度解耦,沒有交叉依賴,不會出現一個子產品依賴了另一個子產品, 其中一個人改了這個子產品的代碼,對另一個子產品造成影響。
淘寶的架構是用了 osgi 的 bundle 概念,整個應用架構生命周期完整。 **适合于項目超級大 但是功能相對不集中。**比如 一個支付寶 App 裡面即包 含共享單車 也包含 電影票。這種與本業務完全不同的 可以做成插件的形式 插件化弊端: 每一個插件都是一個 apk,插件多的時候管理起來也麻煩。
說下元件之間的跳轉群組件通信原理機制
面試官: 說下元件之間的跳轉群組件通信原理機制
**心理分析:**面試官從架構層次 了解求職者是否對元件化有深入研 究。是否使用過元件化,使用有多深。通過該問題一目了然。如果 能說出項目的演進 元件通信選型 絕對是一個加分項
**求職者:**應該從為什麼會用到元件化 群組件定義,元件通信的 演進說起
我們公司的一個單體項目進行元件化架構改造,我們最開始從以下 7 個方面入手:
- 代碼解耦。如何将一個龐大的工程分成有機的整體?
- 元件單獨運作。因為每個元件都是高度内聚的,是一個完整的整體,如何 讓其單獨運作和調試?
- 元件間通信。由于每個元件具體實作細節都互相不了解,但每個元件都需 要給其他調用方提供服務,那麼主項目與元件、元件與元件之間如何通信 就變成關鍵?
- UI 跳轉。UI 跳轉指的是特殊的資料傳遞,跟元件間通信差別有什麼不 同?
- 元件生命周期。這裡的生命周期指的是元件在應用中存在的時間,元件是 否可以做到按需、動态使用、是以就會涉及到元件加載、解除安裝等管理問題。
- 內建調試。在開發階段如何做到按需編譯元件?一次調試中可能有一兩個 元件參與內建,這樣編譯時間就會大大降低,提高開發效率。
- 代碼隔離。元件之間的互動如果還是直接引用的話,那麼元件之間根本沒 有做到解耦,如何從根本上避免元件之間的直接引用,也就是如何從根本 上杜絕耦合的産生?
今天則會從更小細粒度入手,主要講講在元件化架構下元件與元件之間通信機制 是如何、包括所謂的 UI 跳轉,其實也是元件化通信,隻不過它稍微特殊點,單 獨抽取出來而已。學習知識的過程很常見的一個思路就是從整體概況入手,首先 對整體有個粗略的印象,然後再深入細節,抽絲剝繭般去挖掘其中的内在原理, 一個點一個不斷去突破,這樣就能建立起自己整個知識樹,是以今天我們就從通 信機制這個點入手,看看其中内在玄機有哪些。
思維導圖
同樣,在每寫一篇文章之前,放個思維導圖,這樣做的好處對于想寫的内容有很 好的梳理,邏輯和結構上顯得清晰點。
總所周知,Android 提供了很多不同的資訊的傳遞方式,比如在四大元件中本地 廣播、程序間的 AIDL、匿名間的記憶體共享、Intent Bundle 傳遞等等,那麼在這 麼多傳遞方式,哪種類型是比較适合元件與元件直接的傳遞呢。
- 本地廣播,也就是 LoacalBroadcastRecevier。更多是用在同一個應用内的不同系 統規定的元件進行通信,好處在于:發送的廣播隻會在自己的 APP 内傳播,不 會洩漏給其他的 APP,其他 APP 無法向自己的 APP 發送廣播,不用被其他 APP 幹擾。本地廣播好比對講通信,成本低,效率高,但有個缺點就是兩者通信機制 全部委托與系統負責,我們無法幹預傳輸途中的任何步驟,不可控制,一般在組 件化通信過程中采用比例不高。
- 程序間的 AIDL。這個粒度在于程序,而我們元件化通信過程往往是線上程中, 況且 AIDL 通信也是屬于系統級通信,底層以 Binder 機制,雖說 Android 提供模 闆供我們實作,但往往使用者不好了解,互動比較複雜,往往也不适用應用于組 件化通信過程中。
- 匿名的記憶體共享。比如用 Sharedpreferences,在處于多線程場景下,往往會線 程不安全,這種更多是存儲一一些變化很少的資訊,比如說元件裡的配置資訊等 等。
- Intent Bundle 傳遞。包括顯性和隐性傳遞,顯性傳遞需要明确包名路徑,元件 與元件往往是需要互相依賴,這背離元件化中 SOP(關注點分離原則),如果走 隐性的話,不僅包名路徑不能重複,需要定義一套規則,隻有一個包名路徑出錯, 排查起來也稍顯麻煩,這個方式往往在元件間内部傳遞會比較合适,元件外與其 他元件打交道則使用場景不多。
說了這麼多,那元件化通信什麼機制比較适合呢?既然元件層中的子產品是互相獨 立的,它們之間并不存在任何依賴。沒有依賴就無法産生關系,沒有關系,就無 法傳遞消息,那要如何才能完成這種交流?
目前主流做法之一就是引入第三者,比如圖中的 Base Module。
基礎元件化架構
元件層的子產品都依賴于基礎層,進而産生第三者聯系,這種第三者聯系最終會編 譯在 APP Module 中,那時将不會有這種隔閡,那麼其中的 Base Module 就是 跨越元件化層級的關鍵,也是子產品間資訊交流的基礎。比較有代表性的元件化開 源架構有得到 DDComponentForAndroid、阿裡 Arouter、聚美 Router 等等。
除了這種以通過引入第三者方式,還有一種解決方式是以事件總線方式,但這種 方式目前開源的架構中使用比例不高,如圖:
事件總線
事件總線通過記錄對象,使用監聽者模式來通知對象各種事件,比如在現實生活 中,我們要去找房子,一般都去看小區的公告欄,因為那邊會經常釋出一些出租 資訊,我們去檢視的過程中就形成了訂閱的關系,隻不過這種是被動去訂閱,因 為隻有自己需要找房子了才去看,平時一般不會去看。小區中的公告欄可以想象 成一個事件總線釋出點,監聽者則是哪些想要找房子的人,當有房東在公告欄上 貼上出租房資訊時,如果公告欄有訂閱資訊功能,比如引入門衛保安,已經把之 前來這個公告欄要檢視的找房子人一一進行電話登記,那麼一旦有新出租消息産 生,則門衛會把這條消息一一進行短信群發,那麼找房子人則會收到這條消息進 行後續的操作,是馬上過來看,還是延遲過來,則根據自己的實際情況進行處理。 在目前開源庫中,有 EventBus、RxBus 就是采用這種釋出/訂閱模式,優點是簡 化了 Android 元件之間的通信方式,實作解耦,讓業務代碼更加簡潔,可以動态 設定事件處理線程和優先級,缺點則是每個事件需要維護一個事件類,造成事件 類太多,無形中加大了維護成本。那麼在元件化開源架構中有 ModuleBus、CC 等 等。
這兩者模式更詳細的對比,可以檢視這篇文章多個次元對比一些有代表性的開源 android 元件化開發方案
實作方案
事件總線,又可以叫做元件總線,路由+接口,則相對好了解點,今天從閱讀它 們架構源碼,我們來對比這兩種實作方案的不同之處。
元件總線
這邊選取的是 ModuleBus 架構,這個方案特别之處在于其借鑒了 EventBus 的思 想,元件的注冊/登出群組件調用的事件發送都跟 EventBus 類似,能夠傳遞一些 基礎類型的資料,而并不需要在 Base Moudel 中添加額外的類。是以不會影響 Base 子產品的架構,但是無法動态移除資訊接收端的代碼,而自定義的事件資訊 類型還是需要添加到 Base Module 中才能讓其他功能子產品索引。
其中的核心代碼是在與 ModuleBus 類,其内部維護了兩個 ArrayMap 鍵對值列 表,如下:
private static ArrayMap<Object,ArrayMap<String,MethodInfo>>
moduleEventMethods = new ArrayMap<>();
private static ArrayMap<Class<?>,ArrayMap<String,ArrayList<Object>>>
moduleMethodClient = new ArrayMap<>()
在使用方法上,在 onCreate()和 onDestroy()中需要注冊和解綁,比如
ModuleBus.getInstance().register(this);
ModuleBus.getInstance().unregister(this);
最終使用類似 EventBus 中 post 方法一樣,進行兩個元件間的通信。這個架構 的封裝的 post 方法如下
public void post(Class<?> clientClass,String methodName,Object...args){
if(clientClass == null || methodName == null ||methodName.length() == 0) return;
ArrayList<Object> clientList = getClient(clientClass,methodName)
for(Object c: clientList){ ArrayMap<String,MethodInfo> methods = moduleEventMethods.get(c);
Method method = methods.get(methodName).m;
method.invoke(c,args);
}
可以看到,它是通過周遊之前内部的 ArrayMap,把注冊在裡面的方法找出,根據傳入的參數進行比對,使用反射調用。
接口+路由
接口+路由實作方式則相對容易了解點,我之前實踐的一個項目就是通過這種方 式實作的。具體位址如下:DemoComponent 實作思路是專門抽取一個 LibModule 作為路由服務,每個元件聲明自己提供的服務 Service API,這些 Service 都是一些接口,元件負責将這些 Service 實作并注冊到一個統一的路由 Router 中去,如果要使用某個元件的功能,隻需要向 Router 請求這個 Service 的實作,具體的實作細節我們全然不關心,隻要能傳回我們需要的結果就可以了。 比如定義兩個路由位址,一個登陸元件,一個設定元件,核心代碼:
public class RouterPath {
public static final String ROUTER_PATH_TO_LOGIN_SERVICE = "/login/service";
public static final String ROUTER_PATH_TO_SETTING_SERVICE = "/setting/service"; }
那麼就相應着就有兩個接口 API,如下:
public interface ILoginProvider extends IProvider {
void goToLogin(Activity activity);
}
public interface ISettingProvider extends IProvider {
void goToSetting(Activity activity);
}
}
這兩個接口 API 對應着是向外暴露這兩個元件的能提供的通信能力,然後每個組 件對接口進行實作,如下:
@Override
public void init(Context context) {
}
@Override
public void goToLogin(Activity activity) {
Intent loginIntent = new Intent(activity, LoginActivity.class);
activity.startActivity(loginIntent);
}
}
這其中使用的到了阿裡的 ARouter 頁面跳轉方式,内部本質也是接口+實作方式 進行元件間通信。
調用則很簡單了,如下:
ILoginProvider loginService = (ILoginProvider)
ARouter.getInstance().build(RouterPath.ROUTER_PATH_TO_LOGIN_SERVICE).naviga tion();
if(loginService != null){
loginService.goToLogin(MainActivity.this);
}
還有一個元件化架構,就是 ModularizationArchitecture ,它本質實作方式也是 接口+實作,但是封裝形式稍微不一樣點,它是每個功能子產品中需要使用注解建 立 Action 事件,每個 Action 完成一個事件動作。invoke 隻是方法名為反射,并 未用到反射,而是使用接口方式調用,參數是通過 HashMap 傳遞的,無法傳遞 對象。具體詳解可以看這篇文章 Android 架構思考(子產品化、多程序)。,string>
頁面跳轉
頁面跳轉也算是一種元件間的通信,隻不過它相對粒度更細化點,之前我們描述 的元件間通信粒度會更抽象點,頁面跳轉則是定位到某個元件的某個頁面,可能 是某個 Activity,或者某個 Fragment,要跳轉到另外一個元件的 Activity 或 Fragment,是這兩者之間的通信。甚至在一般沒有進行元件化架構的工程項目 中,往往也會封裝頁面之間的跳轉代碼類,往往也會有路由中心的概念。不過一 般 UI 跳轉基本都會單獨處理,一般通過短鍊的方式來跳轉到具體的 Activity。 每個元件可以注冊自己所能處理的短鍊的 Scheme 和 Host,并定義傳輸資料的 格式,然後注冊到統一的 UIRouter 中,UIRouter 通過 Scheme 和 Host 的匹 配關系負責分發路由。但目前比較主流的做法是通過在每個 Activity 上添加注 解,然後通過 APT 形成具體的邏輯代碼。
下面簡單介紹目前比較主流的兩個架構核心實作思路:
ARouter
ARouter 核心實作思路是,我們在代碼裡加入的@Route 注解,會在編譯時期通 過 apt 生成一些存儲 path 和 activityClass 映射關系的類檔案,然後 app 程序啟 動的時候會拿到這些類檔案,把儲存這些映射關系的資料讀到記憶體裡(儲存在 map 裡),然後在進行路由跳轉的時候,通過 build()方法傳入要到達頁面的路由 位址,ARouter 會通過它自己存儲的路由表找到路由位址對應的 Activity.class(activity.class = map.get(path)),然後 new Intent(),當調用 ARouter 的 withString()方法它的内部會調用 intent.putExtra(String name, String value), 調用 navigation()方法,它的内部會調用 startActivity(intent)進行跳轉,這樣便可 以實作兩個互相沒有依賴的 module 順利的啟動對方的 Activity 了。
ActivityRouter ActivityRouter
核心實作思路是,它是通過路由 + 靜态方法來實作,在靜态方 法上加注解來暴露服務,但不支援傳回值,且參數固定位(context, bundle),基 于 apt 技術,通過注解方式來實作 URL 打開 Activity 功能,并支援在 WebView 和外部浏覽器使用,支援多級 Activity 跳轉,支援 Bundle、Uri 參數注入并轉換 參數類型。它實作相對簡單點,也是比較早期比較流行的做法,不過學習它也是 很有參考意義的。