偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Android模塊化探索與實(shí)踐

移動(dòng)開發(fā) Android
萬維網(wǎng)發(fā)明人 Tim Berners-Lee 談到設(shè)計(jì)原理時(shí)說過:“簡(jiǎn)單性和模塊化是軟件工程的基石;分布式和容錯(cuò)性是互聯(lián)網(wǎng)的生命?!?由此可見模塊化之于軟件工程領(lǐng)域的重要性。

前言

萬維網(wǎng)發(fā)明人 Tim Berners-Lee 談到設(shè)計(jì)原理時(shí)說過:“簡(jiǎn)單性和模塊化是軟件工程的基石;分布式和容錯(cuò)性是互聯(lián)網(wǎng)的生命。” 由此可見模塊化之于軟件工程領(lǐng)域的重要性。

從 2016 年開始,模塊化在 Android 社區(qū)越來越多的被提及。隨著移動(dòng)平臺(tái)的不斷發(fā)展,移動(dòng)平臺(tái)上的軟件慢慢走向復(fù)雜化,體積也變得臃腫龐大;為了降低大型軟件復(fù)雜性和耦合度,同時(shí)也為了適應(yīng)模塊重用、多團(tuán)隊(duì)并行開發(fā)測(cè)試等等需求,模塊化在 Android 平臺(tái)上變得勢(shì)在必行。阿里 Android 團(tuán)隊(duì)在年初開源了他們的容器化框架 Atlas 就很大程度說明了當(dāng)前 Android 平臺(tái)開發(fā)大型商業(yè)項(xiàng)目所面臨的問題。

什么是模塊化

那么什么是模塊化呢?《 Java 應(yīng)用架構(gòu)設(shè)計(jì):模塊化模式與 OSGi 》一書中對(duì)它的定義是:模塊化是一種處理復(fù)雜系統(tǒng)分解為更好的可管理模塊的方式。

上面這種描述太過生澀難懂,不夠直觀。下面這種類比的方式則可能加容易理解。

我們可以把軟件看做是一輛汽車,開發(fā)一款軟件的過程就是生產(chǎn)一輛汽車的過程。一輛汽車由車架、發(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)域里說的多團(tuán)隊(duì)并行開發(fā),***將各個(gè)團(tuán)隊(duì)開發(fā)的模塊統(tǒng)一打包成我們可使用的 App 。

一款發(fā)動(dòng)機(jī)、一款變數(shù)箱都不可能只應(yīng)用于一個(gè)車型,比如同一款 Jatco 的 6AT 自動(dòng)變速箱既可能被安裝在 BMW 的車型上,也可能被安裝在 Mazda 的車型上。這就如同軟件開發(fā)領(lǐng)域里的模塊重用。

到了冬天,特別是在北方我們可能需要開著車走雪路,為了安全起見往往我們會(huì)將汽車的公路胎升級(jí)為雪地胎;輪胎可以很輕易的更換,這就是我們?cè)谲浖_發(fā)領(lǐng)域談到的低耦合。一個(gè)模塊的升級(jí)替換不會(huì)影響到其它模塊,也不會(huì)受其它模塊的限制;同時(shí)這也類似于我們?cè)谲浖_發(fā)領(lǐng)域提到的可插拔。

模塊化分層設(shè)計(jì)

上面的類比很清晰的說明的模塊化帶來的好處:

  • 多團(tuán)隊(duì)并行開發(fā)測(cè)試;
  • 模塊間解耦、重用;
  • 可單獨(dú)編譯打包某一模塊,提升開發(fā)效率。

在《安居客 Android 項(xiàng)目架構(gòu)演進(jìn)》這篇文章中,我介紹了安居客 Android 端的模塊化設(shè)計(jì)方案,這里我還是拿它來舉例。但首先要對(duì)本文中的組件和模塊做個(gè)區(qū)別定義

  • 組件:指的是單一的功能組件,如地圖組件(MapSDK)、支付組件(AnjukePay)、路由組件(Router)等等;
  • 模塊:指的是獨(dú)立的業(yè)務(wù)模塊,如新房模塊(NewHouseModule)、二手房模塊(SecondHouseModule)、即時(shí)通訊模塊(InstantMessagingModule)等等;模塊相對(duì)于組件來說粒度更大。

具體設(shè)計(jì)方案如下圖:

Android模塊化探索與實(shí)踐

整個(gè)項(xiàng)目分為三層,從下至上分別是:

  • Basic Component Layer: 基礎(chǔ)組件層,顧名思義就是一些基礎(chǔ)組件,包含了各種開源庫(kù)以及和業(yè)務(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ù)模塊劃分,但是模塊劃分并沒有一個(gè)業(yè)界通用的標(biāo)準(zhǔn),因此劃分的粒度需要根據(jù)項(xiàng)目情況進(jìn)行合理把控,這就需要對(duì)業(yè)務(wù)和項(xiàng)目有較為透徹的理解。拿安居客來舉例,我們會(huì)將項(xiàng)目劃分為新房模塊、二手房模塊、IM 模塊等等。

每個(gè)業(yè)務(wù)模塊在 Android Studio 中的都是一個(gè) Module ,因此在命名方面我們要求每個(gè)業(yè)務(wù)模塊都以 Module 為后綴。如下圖所示:

Android模塊化探索與實(shí)踐

對(duì)于模塊化項(xiàng)目,每個(gè)單獨(dú)的 Business Module 都可以單獨(dú)編譯成 APK。在開發(fā)階段需要單獨(dú)打包編譯,項(xiàng)目發(fā)布的時(shí)候又需要它作為項(xiàng)目的一個(gè) Module 來整體編譯打包。簡(jiǎn)單的說就是開發(fā)時(shí)是 Application,發(fā)布時(shí)是 Library。因此需要在 Business Module 的 build.gradle 中加入如下代碼:

 

  1. if(isBuildModule.toBoolean()){ 
  2.     apply plugin: 'com.android.application' 
  3. }else
  4.     apply plugin: 'com.android.library' 

isBuildModule 在項(xiàng)目根目錄的 gradle.properties 中定義:

 

  1. > isBuildModule=false 

同樣 Manifest.xml 也需要有兩套:

 

  1. sourceSets { 
  2.    main { 
  3.        if (isBuildModule.toBoolean()) { 
  4.            manifest.srcFile 'src/main/debug/AndroidManifest.xml' 
  5.        } else { 
  6.            manifest.srcFile 'src/main/release/AndroidManifest.xml' 
  7.        } 
  8.    } 

如圖:

Android模塊化探索與實(shí)踐

debug 模式下的 AndroidManifest.xml :

 

  1. <application 
  2.    ... 
  3.    > 
  4.    <activity 
  5.        android:name="com.baronzhang.android.newhouse.NewHouseMainActivity" 
  6.        android:label="@string/new_house_label_home_page"
  7.        <intent-filter> 
  8.            <action android:name="android.intent.action.MAIN" /> 
  9.            <category android:name="android.intent.category.LAUNCHER" /> 
  10.        </intent-filter> 
  11.    </activity> 
  12. </application> 

realease 模式下的 AndroidManifest.xml :

 

  1. <application 
  2.    ... 
  3.    > 
  4.    <activity 
  5.        android:name="com.baronzhang.android.newhouse.NewHouseMainActivity" 
  6.        android:label="@string/new_house_label_home_page"
  7.        <intent-filter> 
  8.            <category android:name="android.intent.category.DEFAULT" /> 
  9.            <category android:name="android.intent.category.BROWSABLE" /> 
  10.            <action android:name="android.intent.action.VIEW" /> 
  11.            <data android:host="com.baronzhang.android.newhouse" 
  12.                android:scheme="router" /> 
  13.        </intent-filter> 
  14.    </activity> 
  15. </application> 

同時(shí)針對(duì)模塊化我們也定義了一些自己的游戲規(guī)則:

  • 對(duì)于 Business Module Layer,各業(yè)務(wù)模塊之間不允許存在相互依賴關(guān)系,它們之間的跳轉(zhuǎn)通訊采用路由框架 Router 來實(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ú)立的模塊,它們之間是沒有依賴關(guān)系。那么各個(gè)模塊間的跳轉(zhuǎn)通訊如何實(shí)現(xiàn)呢?

比如業(yè)務(wù)上要求從新房的列表頁跳轉(zhuǎn)到二手房的列表頁,那么由于是 NewHouseModule 和 SecondHouseModule 之間并不相互依賴,我們通過想如下這種顯式跳轉(zhuǎn)的方式來實(shí)現(xiàn) Activity 跳轉(zhuǎn)顯然是不可能的實(shí)現(xiàn)的。

 

  1. Intent intent = new Intent(NewHouseListActivity.this, SecondHouseListActivity.class); 
  2. startActivity(intent); 

有的同學(xué)可能會(huì)想到用隱式跳轉(zhuǎn),通過 Intent 匹配規(guī)則來實(shí)現(xiàn):

 

  1. Intent intent = new Intent(Intent.ACTION_VIEW, "://:/");  
  2. startActivity(intent); 

但是這種代碼寫起來比較繁瑣,且容易出錯(cuò),出錯(cuò)也不太容易定位問題。因此一個(gè)簡(jiǎn)單易用、解放開發(fā)的路由框架是必須的了。

Android模塊化探索與實(shí)踐

我自己實(shí)現(xiàn)的路由框架分為路由(Router) 和參數(shù)注入器(Injector) 兩部分:

Android模塊化探索與實(shí)踐

Router 提供 Activity 跳轉(zhuǎn)傳參的功能;Injector 提供參數(shù)注入功能,通過編譯時(shí)生成代碼的方式在 Activity 獲取獲取傳遞過來的參數(shù),簡(jiǎn)化開發(fā)。

Router

路由(Router)部分通過 Java 注解結(jié)合動(dòng)態(tài)代理來實(shí)現(xiàn),這一點(diǎn)和 Retrofit 的實(shí)現(xiàn)原理是一樣的。

首先需要定義我們自己的注解(篇幅有限,這里只列出少部分源碼)。

用于定義跳轉(zhuǎn) URI 的注解 FullUri:

 

  1. @Target(ElementType.METHOD) 
  2. @Retention(RetentionPolicy.RUNTIME) 
  3. public @interface FullUri { 
  4.     String value(); 

用于定義跳轉(zhuǎn)傳參的 UriParam( UriParam 注解的參數(shù)用于拼接到 URI 后面):

 

  1. @Target(ElementType.PARAMETER) 
  2. @Retention(RetentionPolicy.RUNTIME) 
  3. public @interface UriParam { 
  4.     String value(); 

用于定義跳轉(zhuǎn)傳參的 IntentExtrasParam( IntentExtrasParam 注解的參數(shù)最終通過 Intent 來傳遞):

 

  1. @Target(ElementType.PARAMETER) 
  2. @Retention(RetentionPolicy.RUNTIME) 
  3. public @interface IntentExtrasParam { 
  4.     String value(); 

然后實(shí)現(xiàn) Router ,內(nèi)部通過動(dòng)態(tài)代理的方式來實(shí)現(xiàn) Activity 跳轉(zhuǎn):

 

  1. public final class Router { 
  2.     ... 
  3.     public <T> T create(final Class<T> service) { 
  4.         return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service}, new InvocationHandler() { 
  5.             @Override 
  6.             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
  7.                 FullUri fullUri = method.getAnnotation(FullUri.class); 
  8.                 StringBuilder urlBuilder = new StringBuilder(); 
  9.                 urlBuilder.append(fullUri.value()); 
  10.                 //獲取注解參數(shù) 
  11.                 Annotation[][] parameterAnnotations = method.getParameterAnnotations(); 
  12.                 HashMap<String, Object> serializedParams = new HashMap<>(); 
  13.                 //拼接跳轉(zhuǎn) URI 
  14.                 int position = 0; 
  15.                 for (int i = 0; i < parameterAnnotations.length; i++) { 
  16.                     Annotation[] annotations = parameterAnnotations[i]; 
  17.                     if (annotations == null || annotations.length == 0) 
  18.                         break; 
  19.                     Annotation annotation = annotations[0]; 
  20.                     if (annotation instanceof UriParam) { 
  21.                         //拼接 URI 后的參數(shù) 
  22.                         ... 
  23.                     } else if (annotation instanceof IntentExtrasParam) { 
  24.                         //Intent 傳參處理 
  25.                         ... 
  26.                     } 
  27.                 } 
  28.                 //執(zhí)行Activity跳轉(zhuǎn)操作 
  29.                 performJump(urlBuilder.toString(), serializedParams); 
  30.                 return null
  31.             } 
  32.         }); 
  33.     } 
  34.     ... 

上面是 Router 實(shí)現(xiàn)的部分代碼,在使用 Router 來跳轉(zhuǎn)的時(shí)候,首先需要定義一個(gè) Interface(類似于 Retrofit 的使用方式):

 

  1. public interface RouterService {  
  2. @FullUri("router://com.baronzhang.android.router.FourthActivity" 
  3. void startUserActivity(@UriParam("cityName" 
  4. String cityName, @IntentExtrasParam("user"User user);  

接下來我們就可以通過如下方式實(shí)現(xiàn) Activity 的跳轉(zhuǎn)傳參了:

 

  1. RouterService routerService = new Router(this).create(RouterService.class); 
  2. User user = new User("張三", 17, 165, 88); 
  3. routerService.startUserActivity("上海"user); 

Injector

通過 Router 跳轉(zhuǎn)到目標(biāo) Activity 后,我們需要在目標(biāo) Activity 中獲取通過 Intent 傳過來的參數(shù):

 

  1. getIntent().getIntExtra("intParam", 0); 
  2. getIntent().getData().getQueryParameter("preActivity"); 

為了簡(jiǎn)化這部分工作,路由框架 Router 中提供了 Injector 模塊在編譯時(shí)生成上述代碼。參數(shù)注入器(Injector)部分通過 Java 編譯時(shí)注解來實(shí)現(xiàn),實(shí)現(xiàn)思路和 ButterKnife 這類編譯時(shí)注解框架類似。

首先定義我們的參數(shù)注解 InjectUriParam :

 

  1. @Target(ElementType.FIELD) 
  2. @Retention(RetentionPolicy.CLASS) 
  3. public @interface InjectUriParam { 
  4.     String value() default ""

然后實(shí)現(xiàn)一個(gè)注解處理器 InjectProcessor ,在編譯階段生成獲取參數(shù)的代碼:

 

  1. @AutoService(Processor.class) 
  2. public class InjectProcessor extends AbstractProcessor { 
  3.     ... 
  4.    @Override 
  5.     public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { 
  6.         //解析注解 
  7.         Map<TypeElement, TargetClass> targetClassMap = findAndParseTargets(roundEnvironment); 
  8.         //解析完成后,生成的代碼的結(jié)構(gòu)已經(jīng)有了,它們存在InjectingClass中 
  9.         for (Map.Entry<TypeElement, TargetClass> entry : targetClassMap.entrySet()) { 
  10.             ... 
  11.         } 
  12.         return false
  13.     } 
  14.     ... 

使用方式類似于 ButterKnife ,在 Activity 中我們使用 Inject 來注解一個(gè)全局變量:

  1. @Inject User user

然后 onCreate 方法中需要調(diào)用 inject(Activity activity) 方法實(shí)現(xiàn)注入:

  1. RouterInjector.inject(this); 

這樣我們就可以獲取到前面通過 Router 跳轉(zhuǎn)的傳參了。

  • 由于篇幅限制,加上為了便于理解,這里只貼出了極少部分 Router 框架的源碼。希望進(jìn)一步了解 Router 實(shí)現(xiàn)原理的可以到 GiuHub 去翻閱源碼,Router 的實(shí)現(xiàn)還比較簡(jiǎn)陋,后面會(huì)進(jìn)一步完善功能和文檔,之后也會(huì)有單獨(dú)的文章詳細(xì)介紹。源碼地址:https://github.com/BaronZ88/Router

問題及建議

資源名沖突

對(duì)于多個(gè) Bussines Module 中資源名沖突的問題,可以通過在 build.gradle 定義前綴的方式解決:

 

  1. defaultConfig { 
  2.    ... 
  3.    resourcePrefix "new_house_" 
  4.    ... 

而對(duì)于 Module 中有些資源不想被外部訪問的,我們可以創(chuàng)建 res/values/public.xml,添加到 public.xml 中的 resource 則可被外部訪問,未添加的則視為私有:

  1. <resources> 
  2.     <public name="new_house_settings" type="string"/> 
  3. </resources> 

重復(fù)依賴

模塊化的過程中我們常常會(huì)遇到重復(fù)依賴的問題,如果是通過 aar 依賴, gradle 會(huì)自動(dòng)幫我們找出新版本,而拋棄老版本的重復(fù)依賴。如果是以 project 的方式依賴,則在打包的時(shí)候會(huì)出現(xiàn)重復(fù)類。對(duì)于這種情況我們可以在 build.gradle 中將 compile 改為 provided,只在最終的項(xiàng)目中 compile 對(duì)應(yīng)的 library ;

其實(shí)從前面的安居客模塊化設(shè)計(jì)圖上能看出來,我們的設(shè)計(jì)方案能一定程度上規(guī)避重復(fù)依賴的問題。比如我們所有的第三方庫(kù)的依賴都會(huì)放到 OpenSoureLibraries 中,其他需要用到相關(guān)類庫(kù)的項(xiàng)目,只需要依賴 OpenSoureLibraries 就好了。

模塊化過程中的建議

對(duì)于大型的商業(yè)項(xiàng)目,在重構(gòu)過程中可能會(huì)遇到業(yè)務(wù)耦合嚴(yán)重,難以拆分的問題。我們需要先理清業(yè)務(wù),再動(dòng)手拆分業(yè)務(wù)模塊。比如可以先在原先的項(xiàng)目中根據(jù)業(yè)務(wù)分包,在一定程度上將各業(yè)務(wù)解耦后拆分到不同的 package 中。比如之前新房和二手房由于同屬于 app module,因此他們之前是通過隱式的 intent 跳轉(zhuǎn)的,現(xiàn)在可以先將他們改為通過 Router 來實(shí)現(xiàn)跳轉(zhuǎn)。又比如新房和二手房中公用的模塊可以先下放到 Business Component Layer 或者 Basic Component Layer 中。在這一系列工作完成后再將各個(gè)業(yè)務(wù)拆分成多個(gè) module 。

模塊化重構(gòu)需要漸進(jìn)式的展開,不可一觸而就,不要想著將整個(gè)項(xiàng)目推翻重寫。線上成熟穩(wěn)定的業(yè)務(wù)代碼,是經(jīng)過了時(shí)間和大量用戶考驗(yàn)的;全部推翻重寫往往費(fèi)時(shí)費(fèi)力,實(shí)際的效果通常也很不理想,各種問題層出不窮得不償失。對(duì)于這種項(xiàng)目的模塊化重構(gòu),我們需要一點(diǎn)點(diǎn)的改進(jìn)重構(gòu),可以分散到每次的業(yè)務(wù)迭代中去,逐步淘汰掉陳舊的代碼。

各業(yè)務(wù)模塊間肯定會(huì)有公用的部分,按照我前面的設(shè)計(jì)圖,公用的部分我們會(huì)根據(jù)業(yè)務(wù)相關(guān)性下放到業(yè)務(wù)組件層(Business Component Layer)或者基礎(chǔ)組件層(Common Component Layer)。對(duì)于太小的公有模塊不足以構(gòu)成單獨(dú)組件或者模塊的,我們先放到類似于 CommonBusiness 的組件中,在后期不斷的重構(gòu)迭代中視情況進(jìn)行進(jìn)一步的拆分。過程中***主義可以有,切記不可過度。

以上就是我在模塊化探索實(shí)踐方面的一些經(jīng)驗(yàn),不住之處還望大家指出。

責(zé)任編輯:未麗燕 來源: 碼農(nóng)網(wǎng)
相關(guān)推薦

2010-02-03 09:01:01

Java動(dòng)態(tài)模塊化

2017-08-08 16:07:57

Android 模塊化架構(gòu)

2017-08-11 16:10:36

微信Android實(shí)踐

2016-12-14 14:50:26

CSS預(yù)處理語言模塊化實(shí)踐

2019-08-28 16:18:39

JavaScriptJS前端

2021-12-16 22:02:28

webpack原理模塊化

2022-11-02 18:47:46

場(chǎng)景模塊化跨棧

2014-04-27 10:16:31

QCon北京2014Andrew Bett

2017-02-13 18:46:38

Android模塊化組件化

2016-11-08 20:31:19

同方服務(wù)器模塊化

2025-05-12 08:45:00

模塊化FastAPI路由分發(fā)

2009-12-10 11:04:08

Java模塊化OSGiJigsaw

2023-08-18 10:49:14

開發(fā)攜程

2020-09-17 10:30:21

前端模塊化組件

2013-08-20 15:31:18

前端模塊化

2017-05-18 10:23:55

模塊化開發(fā)RequireJsJavascript

2015-10-10 11:29:45

Java模塊化系統(tǒng)初探

2022-03-11 13:01:27

前端模塊

2020-09-18 09:02:32

前端模塊化

2017-06-12 10:45:36

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)