新聞中心
前言

萬(wàn)維網(wǎng)發(fā)明人 Tim Berners-Lee 談到設(shè)計(jì)原理時(shí)說(shuō)過(guò):“簡(jiǎn)單性和模塊化是軟件工程的基石;分布式和容錯(cuò)性是互聯(lián)網(wǎng)的生命?!?由此可見(jiàn)模塊化之于軟件工程領(lǐng)域的重要性。
從 2016 年開(kāi)始,模塊化在 Android 社區(qū)越來(lái)越多的被提及。隨著移動(dòng)平臺(tái)的不斷發(fā)展,移動(dòng)平臺(tái)上的軟件慢慢走向復(fù)雜化,體積也變得臃腫龐大;為了降低大型軟件復(fù)雜性和耦合度,同時(shí)也為了適應(yīng)模塊重用、多團(tuán)隊(duì)并行開(kāi)發(fā)測(cè)試等等需求,模塊化在 Android 平臺(tái)上變得勢(shì)在必行。阿里 Android 團(tuán)隊(duì)在年初開(kāi)源了他們的容器化框架 Atlas 就很大程度說(shuō)明了當(dāng)前 Android 平臺(tái)開(kāi)發(fā)大型商業(yè)項(xiàng)目所面臨的問(wèn)題。
什么是模塊化
那么什么是模塊化呢?《 Java 應(yīng)用架構(gòu)設(shè)計(jì):模塊化模式與 OSGi 》一書中對(duì)它的定義是:模塊化是一種處理復(fù)雜系統(tǒng)分解為更好的可管理模塊的方式。
上面這種描述太過(guò)生澀難懂,不夠直觀。下面這種類比的方式則可能加容易理解。
我們可以把軟件看做是一輛汽車,開(kāi)發(fā)一款軟件的過(guò)程就是生產(chǎn)一輛汽車的過(guò)程。一輛汽車由車架、發(fā)動(dòng)機(jī)、變數(shù)箱、車輪等一系列模塊組成;同樣,一款大型商業(yè)軟件也是由各個(gè)不同的模塊組成的。
汽車的這些模塊是由不同的工廠生產(chǎn)的,一輛 BMW 的發(fā)動(dòng)機(jī)可能是由位于德國(guó)的工廠生產(chǎn)的,它的自動(dòng)變數(shù)箱可能是 Jatco(世界三大變速箱廠商之一)位于日本的工廠生產(chǎn)的,車輪可能是中國(guó)的工廠生產(chǎn)的,***交給華晨寶馬的工廠統(tǒng)一組裝成一輛完整的汽車。這就類似于我們?cè)谲浖こ填I(lǐng)域里說(shuō)的多團(tuán)隊(duì)并行開(kāi)發(fā),***將各個(gè)團(tuán)隊(duì)開(kāi)發(fā)的模塊統(tǒng)一打包成我們可使用的 App 。
一款發(fā)動(dòng)機(jī)、一款變數(shù)箱都不可能只應(yīng)用于一個(gè)車型,比如同一款 Jatco 的 6AT 自動(dòng)變速箱既可能被安裝在 BMW 的車型上,也可能被安裝在 Mazda 的車型上。這就如同軟件開(kāi)發(fā)領(lǐng)域里的模塊重用。
到了冬天,特別是在北方我們可能需要開(kāi)著車走雪路,為了安全起見(jiàn)往往我們會(huì)將汽車的公路胎升級(jí)為雪地胎;輪胎可以很輕易的更換,這就是我們?cè)谲浖_(kāi)發(fā)領(lǐng)域談到的低耦合。一個(gè)模塊的升級(jí)替換不會(huì)影響到其它模塊,也不會(huì)受其它模塊的限制;同時(shí)這也類似于我們?cè)谲浖_(kāi)發(fā)領(lǐng)域提到的可插拔。
模塊化分層設(shè)計(jì)
上面的類比很清晰的說(shuō)明的模塊化帶來(lái)的好處:
- 多團(tuán)隊(duì)并行開(kāi)發(fā)測(cè)試;
- 模塊間解耦、重用;
- 可單獨(dú)編譯打包某一模塊,提升開(kāi)發(fā)效率。
在《安居客 Android 項(xiàng)目架構(gòu)演進(jìn)》這篇文章中,我介紹了安居客 Android 端的模塊化設(shè)計(jì)方案,這里我還是拿它來(lái)舉例。但首先要對(duì)本文中的組件和模塊做個(gè)區(qū)別定義
- 組件:指的是單一的功能組件,如地圖組件(MapSDK)、支付組件(AnjukePay)、路由組件(Router)等等;
- 模塊:指的是獨(dú)立的業(yè)務(wù)模塊,如新房模塊(NewHouseModule)、二手房模塊(SecondHouseModule)、即時(shí)通訊模塊(InstantMessagingModule)等等;模塊相對(duì)于組件來(lái)說(shuō)粒度更大。
具體設(shè)計(jì)方案如下圖:
整個(gè)項(xiàng)目分為三層,從下至上分別是:
- Basic Component Layer: 基礎(chǔ)組件層,顧名思義就是一些基礎(chǔ)組件,包含了各種開(kāi)源庫(kù)以及和業(yè)務(wù)無(wú)關(guān)的各種自研工具庫(kù);
- Business Component Layer: 業(yè)務(wù)組件層,這一層的所有組件都是業(yè)務(wù)相關(guān)的,例如上圖中的支付組件 AnjukePay、數(shù)據(jù)模擬組件 DataSimulator 等等;
- Business Module Layer: 業(yè)務(wù) Module 層,在 Android Studio 中每塊業(yè)務(wù)對(duì)應(yīng)一個(gè)單獨(dú)的 Module。例如安居客用戶 App 我們就可以拆分成新房 Module、二手房 Module、IM Module 等等,每個(gè)單獨(dú)的 Business Module 都必須準(zhǔn)遵守我們自己的 MVP 架構(gòu)。
我們?cè)谡勀K化的時(shí)候,其實(shí)就是將業(yè)務(wù)模塊層的各個(gè)功能業(yè)務(wù)拆分層獨(dú)立的業(yè)務(wù)模塊。所以我們進(jìn)行模塊化的***步就是業(yè)務(wù)模塊劃分,但是模塊劃分并沒(méi)有一個(gè)業(yè)界通用的標(biāo)準(zhǔn),因此劃分的粒度需要根據(jù)項(xiàng)目情況進(jìn)行合理把控,這就需要對(duì)業(yè)務(wù)和項(xiàng)目有較為透徹的理解。拿安居客來(lái)舉例,我們會(huì)將項(xiàng)目劃分為新房模塊、二手房模塊、IM 模塊等等。
每個(gè)業(yè)務(wù)模塊在 Android Studio 中的都是一個(gè) Module ,因此在命名方面我們要求每個(gè)業(yè)務(wù)模塊都以 Module 為后綴。如下圖所示:
對(duì)于模塊化項(xiàng)目,每個(gè)單獨(dú)的 Business Module 都可以單獨(dú)編譯成 APK。在開(kāi)發(fā)階段需要單獨(dú)打包編譯,項(xiàng)目發(fā)布的時(shí)候又需要它作為項(xiàng)目的一個(gè) Module 來(lái)整體編譯打包。簡(jiǎn)單的說(shuō)就是開(kāi)發(fā)時(shí)是 Application,發(fā)布時(shí)是 Library。因此需要在 Business Module 的 build.gradle 中加入如下代碼:
- if(isBuildModule.toBoolean()){
- apply plugin: 'com.android.application'
- }else{
- apply plugin: 'com.android.library'
- }
isBuildModule 在項(xiàng)目根目錄的 gradle.properties 中定義:
- > isBuildModule=false
- >
同樣 Manifest.xml 也需要有兩套:
- sourceSets {
- main {
- if (isBuildModule.toBoolean()) {
- manifest.srcFile 'src/main/debug/AndroidManifest.xml'
- } else {
- manifest.srcFile 'src/main/release/AndroidManifest.xml'
- }
- }
- }
如圖:
debug 模式下的 AndroidManifest.xml :
- ...
- >
- android:name="com.baronzhang.android.newhouse.NewHouseMainActivity"
- android:label="@string/new_house_label_home_page">
realease 模式下的 AndroidManifest.xml :
- ...
- >
- android:name="com.baronzhang.android.newhouse.NewHouseMainActivity"
- android:label="@string/new_house_label_home_page">
- android:scheme="router" />
同時(shí)針對(duì)模塊化我們也定義了一些自己的游戲規(guī)則:
- 對(duì)于 Business Module Layer,各業(yè)務(wù)模塊之間不允許存在相互依賴關(guān)系,它們之間的跳轉(zhuǎn)通訊采用路由框架 Router 來(lái)實(shí)現(xiàn)(后面會(huì)介紹 Router 框架的實(shí)現(xiàn));
- 對(duì)于 Business Component Layer,單一業(yè)務(wù)組件只能對(duì)應(yīng)某一項(xiàng)具體的業(yè)務(wù),個(gè)性化需求對(duì)外部提供接口讓調(diào)用方定制;
- 合理控制各組件和各業(yè)務(wù)模塊的拆分粒度,太小的公有模塊不足以構(gòu)成單獨(dú)組件或者模塊的,我們先放到類似于 CommonBusiness 的組件中,在后期不斷的重構(gòu)迭代中視情況進(jìn)行進(jìn)一步的拆分;
- 上層的公有業(yè)務(wù)或者功能模塊可以逐步下放到下層,合理把握好度就好;
- 各 Layer 間嚴(yán)禁反向依賴,橫向依賴關(guān)系由各業(yè)務(wù) Leader 和技術(shù)小組商討決定。
模塊間跳轉(zhuǎn)通訊(Router)
對(duì)業(yè)務(wù)進(jìn)行模塊化拆分后,為了使各業(yè)務(wù)模塊間解耦,因此各個(gè) Bussiness Module 都是獨(dú)立的模塊,它們之間是沒(méi)有依賴關(guān)系。那么各個(gè)模塊間的跳轉(zhuǎn)通訊如何實(shí)現(xiàn)呢?
比如業(yè)務(wù)上要求從新房的列表頁(yè)跳轉(zhuǎn)到二手房的列表頁(yè),那么由于是 NewHouseModule 和 SecondHouseModule 之間并不相互依賴,我們通過(guò)想如下這種顯式跳轉(zhuǎn)的方式來(lái)實(shí)現(xiàn) Activity 跳轉(zhuǎn)顯然是不可能的實(shí)現(xiàn)的。
- Intent intent = new Intent(NewHouseListActivity.this, SecondHouseListActivity.class);
- startActivity(intent);
有的同學(xué)可能會(huì)想到用隱式跳轉(zhuǎn),通過(guò) Intent 匹配規(guī)則來(lái)實(shí)現(xiàn):
- Intent intent = new Intent(Intent.ACTION_VIEW, "://:/");
- startActivity(intent);
但是這種代碼寫起來(lái)比較繁瑣,且容易出錯(cuò),出錯(cuò)也不太容易定位問(wèn)題。因此一個(gè)簡(jiǎn)單易用、解放開(kāi)發(fā)的路由框架是必須的了。
我自己實(shí)現(xiàn)的路由框架分為路由(Router) 和參數(shù)注入器(Injector) 兩部分:
Router 提供 Activity 跳轉(zhuǎn)傳參的功能;Injector 提供參數(shù)注入功能,通過(guò)編譯時(shí)生成代碼的方式在 Activity 獲取獲取傳遞過(guò)來(lái)的參數(shù),簡(jiǎn)化開(kāi)發(fā)。
Router
路由(Router)部分通過(guò) Java 注解結(jié)合動(dòng)態(tài)代理來(lái)實(shí)現(xiàn),這一點(diǎn)和 Retrofit 的實(shí)現(xiàn)原理是一樣的。
首先需要定義我們自己的注解(篇幅有限,這里只列出少部分源碼)。
用于定義跳轉(zhuǎn) URI 的注解 FullUri:
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface FullUri {
- String value();
- }
用于定義跳轉(zhuǎn)傳參的 UriParam( UriParam 注解的參數(shù)用于拼接到 URI 后面):
- @Target(ElementType.PARAMETER)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface UriParam {
- String value();
- }
用于定義跳轉(zhuǎn)傳參的 IntentExtrasParam( IntentExtrasParam 注解的參數(shù)最終通過(guò) Intent 來(lái)傳遞):
- @Target(ElementType.PARAMETER)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface IntentExtrasParam {
- String value();
- }
然后實(shí)現(xiàn) Router ,內(nèi)部通過(guò)動(dòng)態(tài)代理的方式來(lái)實(shí)現(xiàn) Activity 跳轉(zhuǎn):
- public final class Router {
- ...
- public
T create(final Class service) { - return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service}, new InvocationHandler() {
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- FullUri fullUri = method.getAnnotation(FullUri.class);
- StringBuilder urlBuilder = new StringBuilder();
- urlBuilder.append(fullUri.value());
- //獲取注解參數(shù)
- Annotation[][] parameterAnnotations = method.getParameterAnnotations();
- HashMap
serializedParams = new HashMap<>(); - //拼接跳轉(zhuǎn) URI
- int position = 0;
- for (int i = 0; i < parameterAnnotations.length; i++) {
- Annotation[] annotations = parameterAnnotations[i];
- if (annotations == null || annotations.length == 0)
- break;
- Annotation annotation = annotations[0];
- if (annotation instanceof UriParam) {
- //拼接 URI 后的參數(shù)
- ...
- } else if (annotation instanceof IntentExtrasParam) {
- //Intent 傳參處理
- ...
- }
- }
- //執(zhí)行Activity跳轉(zhuǎn)操作
- performJump(urlBuilder.toString(), serializedParams);
- return null;
- }
- });
- }
- ...
- }
上面是 Router 實(shí)現(xiàn)的部分代碼,在使用 Router 來(lái)跳轉(zhuǎn)的時(shí)候,首先需要定義一個(gè) Interface(類似于 Retrofit 的使用方式):
- public interface RouterService {
- @FullUri("router://com.baronzhang.android.router.FourthActivity")
- void startUserActivity(@UriParam("cityName")
- String cityName, @IntentExtrasParam("user") User user);
- }
接下來(lái)我們就可以通過(guò)如下方式實(shí)現(xiàn) Activity 的跳轉(zhuǎn)傳參了:
- RouterService routerService = new Router(this).create(RouterService.class);
- User user = new User("張三", 17, 165, 88);
- routerService.startUserActivity("上海", user);
Injector
通過(guò) Router 跳轉(zhuǎn)到目標(biāo) Activity 后,我們需要在目標(biāo) Activity 中獲取通過(guò) Intent 傳過(guò)來(lái)的參數(shù):
- getIntent().getIntExtra("intParam", 0);
- getIntent().getData().getQueryParameter("preActivity");
為了簡(jiǎn)化這部分工作,路由框架 Router 中提供了 Injector 模塊在編譯時(shí)生成上述代碼。參數(shù)注入器(Injector
當(dāng)前文章:Android模塊化探索與實(shí)踐
網(wǎng)站URL:http://www.dlmjj.cn/article/dhsjggp.html


咨詢
建站咨詢
