模組化通訊方式對比

你的使用者名稱發表於2019-01-22

前面介紹了一個自定義的簡單Router元件,是使用了元件化的思想,那麼我們經常說到的元件化和模組化的區別是什麼呢?其實這兩個概念很相近,可以這麼說元件化是模組化,但是模組化不一定是元件化。

元件化的核心是角色的轉換。 在打包時是library,而在除錯時是application,可以這麼理解,元件是可以單獨執行的模組,具體的轉換方式在之前的文章已經介紹過了。那麼在抽出好模組以後各模組之間應該如何進行通訊呢?有人說想用哪個模組就直接引用,這是非常不好的方式,作者非常不推薦這種做法。因為如果其他專案需要使用當前專案的某一個模組的時候,如果該模組有依賴其他模組就必須把該模組依賴的所有模組都複製過去,就出現了所謂的"拖油瓶"現象,靈活性太差,所以我們應該避免出現這種雜亂無章的迴圈依賴。

模組化通訊方式對比

今天我們是在模組被app依賴的前提下,也就是各元件是library的情況,講解一下各個模組之間的通訊方式。作者能想到的使用較多的方式至少有三種,分別是EventBus、SPI、ARouter,當然還有其他的方式,這裡就不一一介紹了。接下來作者帶領大家一起分析一下這3種方式的優缺點,如果有更好的方式也希望大家分享出來。

EventBus

EventBus能夠簡化各元件間的通訊,有效的分離事件傳送方和接收方(解耦),避免複雜和容易出錯的依賴性和生命週期問題。並且最重要的是使用起來非常簡單,具體的使用方式就不詳細介紹了,網上關於它的使用方式非常多,這裡就簡單貼個圖吧。

模組化通訊方式對比

缺點:EventBus可以傳送訊息到其他模組,但是並不能直接傳值回來。這裡如果一定要實現傳值也不是沒有辦法,比如再反發射一個事件回去,又或者通過介面回撥的形式來實現。就拿介面回撥來舉例子吧,每一個事件都要寫一個回撥介面,而不是直接獲取返回值,這樣就會覺得用起來很不舒服。更重要的是如果要傳送多個訊息的話,每個訊息需要定義一個event類,導致用起來就更不爽了。而且eventbus是傳送給所有的訂閱者,而不是一對一,如果要實現一對一還要編寫更多額外程式碼,整個程式碼框架將會很冗餘。初次之外,EventBus傳送的地方如果要尋找到使用的地方,或者使用的地方想要了解傳送方,只能通過程式碼全域性搜尋的方式,整個架構非常亂很影響閱讀效率。


SPI技術

Service Provider Interface,是Java提供的一套用來被第三方實現或者擴充套件的API,它可以用來啟用框架擴充套件和替換元件。比如JDBC就是通過SPI機制來載入不同的資料庫(mysql、oracle等)驅動,大致的機制如下圖。SPI分為服務提供方和呼叫方,服務提供方需要提供好呼叫方需要的介面和呼叫路徑,另外需要在公共模組定義好一個介面用來銜接兩個模組。另外需要在base層定義一個介面作為橋樑,一個寫一個讀。

模組化通訊方式對比

接下來我們以A模組需要呼叫B模組的資料為例來講解一下整個過程,首先需要在B模組中提供好A模組需要的資料的返回方法以及介面實現類的呼叫路徑。注意呼叫路徑的提供方式是在src/main目錄下新建resources資料夾,然後再新建META-INF.services資料夾,然後在其中新建一個檔案,注意這個檔名不能隨便取,一定要和base中定義的介面路徑一模一樣

模組化通訊方式對比

模組化通訊方式對比

然後在base模組中定義一個介面,介面路徑和名稱和上面的檔名保持一致,介面中內容為約定好的資料傳輸方法。

模組化通訊方式對比

模組化通訊方式對比

最後在A模組中呼叫即可,注意這裡介面實現類是一個迭代器,說明可以接收到多個實現的方法,所有實現了TestService介面,並且在META-INF下的檔案裡寫入了的類都會被迭代器給遍歷出來,執行裡面的介面實現方法

模組化通訊方式對比

至此SPI實現模組間通訊已經實現完成,估計大家也發現了,使用SPI來實現是非常麻煩的一件事情,每傳一類值就要新建介面對映,而且使用處還要進行解析,如果業務大了需要傳的值太多的情況下,這種方式一樣將成為傳值噩夢。還有介面需要定義在base層,這其實有違base的建立初衷。base層的初衷是一些固定的程式碼,在穩定後不需要修改,也和業務程式碼沒有任何關係。SPI方式來傳值每次都需要在base建立介面,就意味著base層一直是需要修改的,而且將會越來越臃腫,這不是我們想要看到的,所以作者也不推薦使用這種方式來進行模組間傳值。那麼是否有其他的更優秀的傳值框架呢?這就是我們的終極武器——ARouter了,當然還有其他大廠的Router框架,這裡就以ARouter為例來講解一下ARouter來實現模組傳值。


ARouter

模組化通訊方式對比

如上圖,由於其他模組需要呼叫使用者模組的資料,所以需要對本屬於使用者模組的AccountService進行介面下沉,將其放入base中。使用者模組實現AccountService,其他模組由於擁有base的許可權,所以可以呼叫使用者模組中的實現類程式碼。我們假設在一個模組化專案中,module1需要呼叫module2中的方法獲取到資料,只需要按以下步驟。

步驟一 引入ARouter包

在使用註解的module2中按照ARouter的github文件走一遍就好了,github地址:https://github.com/alibaba/ARouter/blob/master/README_CN.md

api 'com.alibaba:arouter-api:1.4.1'
複製程式碼
annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'
複製程式碼
javaCompileOptions {    
      annotationProcessorOptions {       
          arguments = [AROUTER_MODULE_NAME: project.getName()]    
       }
 }複製程式碼


步驟二 在base模組定義資料傳輸介面

模組化通訊方式對比

注意一下,這裡需要繼承IProvider,用來引入Arouter的支援,相應的在TransDataInterface實現類中也需要實現init方法來初始化一些資料,沒有資料的話也可以不管。


步驟三 在module2中實現該介面

模組化通訊方式對比

注意,這裡需要用@Route(path="")來標註,用來給註解處理器生成類檔案,編譯後生成的檔案如下:

模組化通訊方式對比


步驟四 其他模組使用該介面獲取資料

模組化通訊方式對比

至此,按照文件簡單地使用ARouter傳值已完成,但是我們發現由於需要介面下沉,業務介面需要在base層裡進行定義,這樣等業務越來越多的時候,base層又會越來越臃腫,我們設計base初衷是穩定了就可以不用動了,那麼是否有辦法可以避免這個問題呢?答案就是建立中介api模組。比如支付建立支付api模組,使用者建立使用者api模組,建立出來的api模組可以被任何一個大模組引用。接下來以module1需要獲取module2中的值為例,使用建立模組的方式來實現模組間傳值。

步驟一 建立module2_api模組

新建一個module2_api的module,將資料傳值的介面複製到該模組,注意需要在gradle中引入arouter,用以繼承provider類

步驟二 讓module1、module2全都依賴module2_api模組

步驟三 在module2中引入arouter的支援、arouter註解處理器、arouter類名等,然後需要寫好資料傳輸的實現類方法,使用@router進行註解標記,這樣在編譯時就會生成類檔案了。

步驟四 在module1中呼叫arouter的方法來獲取資料即可


那麼分離出api模組的方式是否有缺點呢?答案是肯定的,假如一個大專案中有20個模組,那麼就需要手動建立20個api模組,模組數量瞬間翻倍了,使得專案變得更復雜。那麼微信是如何解決這個問題呢?答案就是微信.api化技術。微信是使用了自定義檔案字尾名的方法,將介面的檔名修改為.api(這裡其實隨便改個名字都行,比如.abc都行),改成.api後編譯時通過gradle指令碼去讀取工程中所有.api檔案,讀取到之後自動建立module_api工程,並且將介面檔案自動複製到建立好的module_api工程中,並且再將名字修改回.java,具體的指令碼大致如下:


模組化通訊方式對比

模組化通訊方式對比


總結:今天主要是對模組化通訊的幾種方式進行了對比,由於eventbus很難維護,所以我們考慮了使用spi,然後spi又太麻煩,最終使用到了ARouter。使用以後發現每次都要新建api模組進行通訊,所以微信就研究出來.api化技術,通過gradle指令碼來操作每個元件自動生成api模組,這也是目前比較前沿的技術。當然技術沒有最好,只有更好,相信經過大家的努力,以後肯定會出現更好的模組化通訊框架,讓我們拭目以待。

相關文章