您的位置:首頁(yè) >  新聞中心 > 行業(yè)動(dòng)態(tài)
  行業(yè)動(dòng)態(tài)
 

安卓架構(gòu)之Android 模塊化/模塊化探索與實(shí)踐

來(lái)源:原創(chuàng)    時(shí)間:2017-10-19    瀏覽:0 次

一、前語(yǔ)

萬(wàn)維網(wǎng)發(fā)明人 Tim Berners-Lee 談到規(guī)劃原理時(shí)說(shuō)過(guò):“簡(jiǎn)略性和模塊化是軟件工程的柱石;分布式和容錯(cuò)性是互聯(lián)網(wǎng)的生命。” 由此可見(jiàn)模塊化之于軟件工程范疇的重要性。

從 2016 年開(kāi)端,模塊化在 Android 社區(qū)越來(lái)越多的被提及。跟著移動(dòng)渠道的不斷發(fā)展,移動(dòng)渠道上的軟件漸漸走向雜亂化,體積也變得臃腫巨大;為了下降大型軟件雜亂性和耦合度,一起也為了習(xí)慣模塊重用、多團(tuán)隊(duì)并行開(kāi)發(fā)測(cè)驗(yàn)等等需求,模塊化在 Android 渠道上變得勢(shì)在必行。阿里 Android 團(tuán)隊(duì)在年初開(kāi)源了他們的容器化結(jié)構(gòu) Atlas 就很大程度闡明了當(dāng)時(shí) Android 渠道開(kāi)發(fā)大型商業(yè)項(xiàng)目所面對(duì)的問(wèn)題。

二、什么是模塊化

那么什么是模塊化呢?《 Java 運(yùn)用架構(gòu)規(guī)劃:模塊化形式與 OSGi 》一書(shū)中對(duì)它的界說(shuō)是:模塊化是一種處理雜亂體系分解為更好的可辦理模塊的辦法。

上面這種描繪過(guò)分生澀難明,不行直觀。下面這種類比的辦法則可能加簡(jiǎn)略了解。

我們能夠把軟件看做是一輛轎車,開(kāi)發(fā)一款軟件的進(jìn)程就是出產(chǎn)一輛轎車的進(jìn)程。一輛轎車由車架、發(fā)動(dòng)機(jī)、變數(shù)箱、車輪等一系列模塊組成;相同,一款大型商業(yè)軟件也是由各個(gè)不同的模塊組成的。

轎車的這些模塊是由不同的工廠出產(chǎn)的,一輛 BMW 的發(fā)動(dòng)機(jī)可能是由坐落德國(guó)的工廠出產(chǎn)的,它的主動(dòng)變數(shù)箱可能是 Jatco(國(guó)際三大變速箱廠商之一)坐落日本的工廠出產(chǎn)的,車輪可能是我國(guó)的工廠出產(chǎn)的,終究交給華晨寶馬的工廠一致組裝成一輛完好的轎車。這就相似于我們?cè)谲浖こ谭懂犂镎f(shuō)的多團(tuán)隊(duì)并行開(kāi)發(fā),終究將各個(gè)團(tuán)隊(duì)開(kāi)發(fā)的模塊一致打包成我們可運(yùn)用的 App 。

一款發(fā)動(dòng)機(jī)、一款變數(shù)箱都不行能只運(yùn)用于一個(gè)車型,比方同一款 Jatco 的 6AT 主動(dòng)變速箱既可能被安裝在 BMW 的車型上,也可能被安裝在 Mazda 的車型上。這就好像軟件開(kāi)發(fā)范疇里的模塊重用。

到了冬季,特別是在北方我們可能需求開(kāi)著車走雪路,為了安全起見(jiàn)往往我們會(huì)將轎車的公路胎晉級(jí)為雪地胎;輪胎能夠很簡(jiǎn)略的替換,這就是我們?cè)谲浖_(kāi)發(fā)范疇談到的低耦合。一個(gè)模塊的晉級(jí)替換不會(huì)影響到其它模塊,也不會(huì)受其它模塊的約束;一起這也相似于我們?cè)谲浖_(kāi)發(fā)范疇說(shuō)到的可插拔。

三、模塊化分層規(guī)劃

上面的類比很明晰的闡明的模塊化帶來(lái)的優(yōu)點(diǎn):

多團(tuán)隊(duì)并行開(kāi)發(fā)測(cè)驗(yàn);
模塊間解耦、重用;
可獨(dú)自編譯打包某一模塊,提高開(kāi)發(fā)功率。
在《安居客 Android 項(xiàng)目架構(gòu)演進(jìn)》這篇文章中,我介紹了安居客 Android 端的模塊化規(guī)劃方案,這兒我仍是拿它來(lái)舉例。但首要要對(duì)本文中的組件和模塊做個(gè)差異界說(shuō)

組件:指的是單一的功用組件,如地圖組件(MapSDK)、付出組件(AnjukePay)、路由組件(Router)等等;

模塊:指的是獨(dú)立的事務(wù)模塊,如新房模塊(NewHouseModule)、二手房模塊(SecondHouseModule)、即時(shí)通訊模塊(InstantMessagingModule)等等;模塊相關(guān)于組件來(lái)說(shuō)粒度更大。

具體規(guī)劃方案如下圖:


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

Basic Component Layer: 根底組件層,望文生義就是一些根底組件,包含了各種開(kāi)源庫(kù)以及和事務(wù)無(wú)關(guān)的各種自研東西庫(kù);

Business Component Layer: 事務(wù)組件層,這一層的一切組件都是事務(wù)相關(guān)的,例如上圖中的付出組件 AnjukePay、數(shù)據(jù)模仿組件 DataSimulator 等等;

Business Module Layer: 事務(wù) Module 層,在 Android Studio 中每塊事務(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í)就是將事務(wù)模塊層的各個(gè)功用事務(wù)拆分層獨(dú)立的事務(wù)模塊。所以我們進(jìn)行模塊化的第一步就是事務(wù)模塊區(qū)分,可是模塊區(qū)分并沒(méi)有一個(gè)業(yè)界通用的規(guī)范,因而區(qū)分的粒度需求依據(jù)項(xiàng)目狀況進(jìn)行合理把控,這就需求對(duì)事務(wù)和項(xiàng)目有較為透徹的了解。拿安居客來(lái)舉例,我們會(huì)將項(xiàng)目區(qū)分為新房模塊、二手房模塊、IM 模塊等等。


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


關(guān)于模塊化項(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 中界說(shuō):

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"
>
       

           

 
android:name
=
"android.intent.action.MAIN"
 
/>
           

 
android:name
=
"android.intent.category.LAUNCHER"
 
/>
       

   


realease 形式下的 AndroidManifest.xml :


   ...
   
>
   

       
android:name
=
"com.baronzhang.android.newhouse.NewHouseMainActivity"
       
android:label
=
"@string/new_house_label_home_page"
>
       

           

 
android:name
=
"android.intent.category.DEFAULT"
 
/>
           

 
android:name
=
"android.intent.category.BROWSABLE"
 
/>
           

 
android:name
=
"android.intent.action.VIEW"
 
/>
           

 
android:host
=
"com.baronzhang.android.newhouse"
               
android:scheme
=
"router"
 
/>
       

   


一起針對(duì)模塊化我們也界說(shuō)了一些自己的游戲規(guī)矩:

關(guān)于 Business Module Layer,各事務(wù)模塊之間不允許存在相互依靠聯(lián)系,它們之間的跳轉(zhuǎn)通訊選用路由結(jié)構(gòu) Router 來(lái)完成(后邊會(huì)介紹 Router 結(jié)構(gòu)的完成);

關(guān)于 Business Component Layer,單一事務(wù)組件只能對(duì)應(yīng)某一項(xiàng)具體的事務(wù),個(gè)性化需求對(duì)外部供給接口讓調(diào)用方定制;

合理操控各組件和各事務(wù)模塊的拆分粒度,太小的公有模塊不足以構(gòu)成獨(dú)自組件或許模塊的,我們先放到相似于 CommonBusiness 的組件中,在后期不斷的重構(gòu)迭代中視狀況進(jìn)行進(jìn)一步的拆分;

上層的公有事務(wù)或許功用模塊能夠逐漸下放到基層,合理掌握好度就好;

各 Layer 間禁止反向依靠,橫向依靠聯(lián)系由各事務(wù) Leader 和技能小組參議決議。

四、模塊間跳轉(zhuǎn)通訊(Router)

對(duì)事務(wù)進(jìn)行模塊化拆分后,為了使各事務(wù)模塊間解耦,因而各個(gè) Bussiness Module 都是獨(dú)立的模塊,它們之間是沒(méi)有依靠聯(lián)系。那么各個(gè)模塊間的跳轉(zhuǎn)通訊怎么完成呢?

比方事務(wù)上要求從新房的列表頁(yè)跳轉(zhuǎn)到二手房的列表頁(yè),那么由所以 NewHouseModule 和 SecondHouseModule 之間并不相互依靠,我們經(jīng)過(guò)想如下這種顯式跳轉(zhuǎn)的辦法來(lái)完成 Activity 跳轉(zhuǎn)顯然是不行能的完成的。

Intent
 intent = 
new
 
Intent
(
NewHouseListActivity
.
this

SecondHouseListActivity
.
class
);
startActivity(intent);
有的同學(xué)可能會(huì)想到用隱式跳轉(zhuǎn),經(jīng)過(guò) Intent 匹配規(guī)矩來(lái)完成:

Intent
 intent = 
new
 
Intent
(
Intent
.ACTION_VIEW, 
"://:/"
);
startActivity(intent);
可是這種代碼寫起來(lái)比較繁瑣,且簡(jiǎn)略犯錯(cuò),犯錯(cuò)也不太簡(jiǎn)略定位問(wèn)題。因而一個(gè)簡(jiǎn)略易用、解放開(kāi)發(fā)的路由結(jié)構(gòu)是有必要的了。


我自己完成的路由結(jié)構(gòu)分為路由(Router)和參數(shù)注入器(Injector)兩部分:


Router 供給 Activity 跳轉(zhuǎn)傳參的功用;Injector 供給參數(shù)注入功用,經(jīng)過(guò)編譯時(shí)生成代碼的辦法在 Activity 獲取獲取傳遞過(guò)來(lái)的參數(shù),簡(jiǎn)化開(kāi)發(fā)。
4.1 Router

路由(Router)部分經(jīng)過(guò) Java 注解結(jié)合動(dòng)態(tài)署理來(lái)完成,這一點(diǎn)和 Retrofit 的完成原理是一樣的。

首要需求界說(shuō)我們自己的注解(篇幅有限,這兒只列出少部分源碼)。

用于界說(shuō)跳轉(zhuǎn) URI 的注解 FullUri:

@Target
(
ElementType
.METHOD)
@Retention
(
RetentionPolicy
.RUNTIME)
public
 
@interface
 
FullUri
 {
    
String
 value();
}
用于界說(shuō)跳轉(zhuǎn)傳參的 UriParam( UriParam 注解的參數(shù)用于拼接到 URI 后邊):

@Target
(
ElementType
.PARAMETER)
@Retention
(
RetentionPolicy
.RUNTIME)
public
 
@interface
 
UriParam
 {
    
String
 value();
}
用于界說(shuō)跳轉(zhuǎn)傳參的 IntentExtrasParam( IntentExtrasParam 注解的參數(shù)終究經(jīng)過(guò) Intent 來(lái)傳遞):

@Target
(
ElementType
.PARAMETER)
@Retention
(
RetentionPolicy
.RUNTIME)
public
 
@interface
 
IntentExtrasParam
 {
    
String
 value();
}
然后完成 Router ,內(nèi)部經(jīng)過(guò)動(dòng)態(tài)署理的辦法來(lái)完成 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
<
String

Object
> 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 傳參處理
                        ...
                    }
                }
                
//履行Activity跳轉(zhuǎn)操作
                performJump(urlBuilder.toString(), serializedParams);
                
return
 
null
;
            }
        });
    }
    ...
}
上面是 Router 完成的部分代碼,在運(yùn)用 Router 來(lái)跳轉(zhuǎn)的時(shí)分,首要需求界說(shuō)一個(gè) Interface(相似于 Retrofit 的運(yùn)用辦法):

public
 
interface
 
RouterService
 {
    
@FullUri
(
"router://com.baronzhang.android.router.FourthActivity"
)
    
void
 startUserActivity(
@UriParam
(
"cityName"

            
String
 cityName, 
@IntentExtrasParam
(
"user"

User
 user);
}
接下來(lái)我們就能夠經(jīng)過(guò)如下辦法完成 Activity 的跳轉(zhuǎn)傳參了:

 
RouterService
 routerService = 
new
 
Router
(
this
).create(
RouterService
.
class
);
 
User
 user = 
new
 
User
(
"張三"

17

165

88
);
 routerService.startUserActivity(
"上海"
, user);
4.2 Injector

經(jīng)過(guò) Router 跳轉(zhuǎn)到方針 Activity 后,我們需求在方針 Activity 中獲取經(jīng)過(guò) Intent 傳過(guò)來(lái)的參數(shù):

getIntent().getIntExtra(
"intParam"

0
);
getIntent().getData().getQueryParameter(
"preActivity"
);
為了簡(jiǎn)化這部分作業(yè),路由結(jié)構(gòu) Router 中供給了 Injector 模塊在編譯時(shí)生成上述代碼。參數(shù)注入器(Injector)部分經(jīng)過(guò) Java 編譯時(shí)注解來(lái)完成,完成思路和 ButterKnife 這類編譯時(shí)注解結(jié)構(gòu)相似。

首要界說(shuō)我們的參數(shù)注解 InjectUriParam :

@Target
(
ElementType
.FIELD)
@Retention
(
RetentionPolicy
.CLASS)
public
 
@interface
 
InjectUriParam
 {
    
String
 value() 
default
 
""
;
}
然后完成一個(gè)注解處理器 InjectProcessor ,在編譯階段生成獲取參數(shù)的代碼:

@AutoService
(
Processor
.
class
)
public
 
class
 
InjectProcessor
 
extends
 
AbstractProcessor
 {
    ...
   
@Override
    
public
 
boolean
 process(
Set
extends
 
TypeElement
> set, 
RoundEnvironment
 roundEnvironment) {
        
//解析注解
        
Map
<
TypeElement

TargetClass
> targetClassMap = findAndParseTargets(roundEnvironment);
        
//解析完成后,生成的代碼的結(jié)構(gòu)已經(jīng)有了,它們存在InjectingClass中
        
for
 (
Map
.
Entry
<
TypeElement

TargetClass
> entry : targetClassMap.entrySet()) {
            ...
        }
        
return
 
false
;
    }
    ...
}
運(yùn)用辦法相似于 ButterKnife ,在 Activity 中我們運(yùn)用 Inject 來(lái)注解一個(gè)全局變量:

@Inject
 
User
 user;
然后 onCreate 辦法中需求調(diào)用 inject(Activity activity) 辦法完成注入:

RouterInjector
.inject(
this
);
這樣我們就能夠獲取到前面經(jīng)過(guò) Router 跳轉(zhuǎn)的傳參了。

因?yàn)槠s束,加上為了便于了解,這兒只貼出了很少部分 Router 結(jié)構(gòu)的源碼。期望進(jìn)一步了解 Router 完成原理的能夠到 GiuHub 去翻閱源碼,Router 的完成還比較粗陋,后邊會(huì)進(jìn)一步完善功用和文檔,之后也會(huì)有獨(dú)自的文章具體介紹。源碼地址:https://github.com/BaronZ88/Router
五、問(wèn)題及主張

5.1 資源名抵觸

關(guān)于多個(gè) Bussines Module 中資源名抵觸的問(wèn)題,能夠經(jīng)過(guò)在 build.gradle 界說(shuō)前綴的辦法處理:

defaultConfig {
   ...
   resourcePrefix 
"new_house_"
   ...
}
而關(guān)于 Module 中有些資源不想被外部拜訪的,我們能夠創(chuàng)立 res/values/public.xml,增加到 public.xml 中的 resource 則可被外部拜訪,未增加的則視為私有:


    

 
name
=
"new_house_settings"
 
type
=
"string"
/>

5.2 重復(fù)依靠

模塊化的進(jìn)程中我們常常會(huì)遇到重復(fù)依靠的問(wèn)題,如果是經(jīng)過(guò) aar 依靠, gradle 會(huì)主動(dòng)幫我們找出新版別,而扔掉老版別的重復(fù)依靠。如果是以 project 的辦法依靠,則在打包的時(shí)分會(huì)呈現(xiàn)重復(fù)類。關(guān)于這種狀況我們能夠在 build.gradle 中將 compile 改為 provided,只在終究的項(xiàng)目中 compile 對(duì)應(yīng)的 library ;

其實(shí)早年面的安居客模塊化規(guī)劃圖上能看出來(lái),我們的規(guī)劃方案能必定程度上躲避重復(fù)依靠的問(wèn)題。比方我們一切的第三方庫(kù)的依靠都會(huì)放到 OpenSoureLibraries 中,其他需求用到相關(guān)類庫(kù)的項(xiàng)目,只需求依靠 OpenSoureLibraries 就好了。

5.3 模塊化進(jìn)程中的主張

關(guān)于大型的商業(yè)項(xiàng)目,在重構(gòu)進(jìn)程中可能會(huì)遇到事務(wù)耦合嚴(yán)峻,難以拆分的問(wèn)題。我們需求先理清事務(wù),再著手拆分事務(wù)模塊。比方能夠先在原先的項(xiàng)目中依據(jù)事務(wù)分包,在必定程度大將各事務(wù)解耦后拆分到不同的 package 中。比方之前新房和二手房因?yàn)橥瑲w于 app module,因而他們之前是經(jīng)過(guò)隱式的 intent 跳轉(zhuǎn)的,現(xiàn)在能夠先將他們改為經(jīng)過(guò) Router 來(lái)完成跳轉(zhuǎn)。又比方新房和二手房中共用的模塊能夠先下放到 Business Component Layer 或許 Basic Component Layer 中。在這一系列作業(yè)完成后再將各個(gè)事務(wù)拆分紅多個(gè) module 。

模塊化重構(gòu)需求漸進(jìn)式的打開(kāi),不行一觸而就,不要想著將整個(gè)項(xiàng)目推翻重寫。線上老練安穩(wěn)的事務(wù)代碼,是經(jīng)過(guò)了時(shí)刻和很多用戶檢測(cè)的;悉數(shù)推翻重寫往往費(fèi)時(shí)吃力,實(shí)踐的作用一般也很不抱負(fù),各種問(wèn)題層出不窮因小失大。關(guān)于這種項(xiàng)目的模塊化重構(gòu),我們需求一點(diǎn)點(diǎn)的改善重構(gòu),能夠渙散到每次的事務(wù)迭代中去,逐漸篩選掉陳腐的代碼。

各事務(wù)模塊間必定會(huì)有共用的部分,依照我前面的規(guī)劃圖,共用的部分我們會(huì)依據(jù)事務(wù)相關(guān)性下放到事務(wù)組件層(Business Component Layer)或許根底組件層(Common Component Layer)。關(guān)于太小的公有模塊不足以構(gòu)成獨(dú)自組件或許模塊的,我們先放到相似于 CommonBusiness 的組件中,在后期不斷的重構(gòu)迭代中視狀況進(jìn)行進(jìn)一步的拆分。進(jìn)程中完美主義能夠有,牢記不行過(guò)度。


免费视频观无码一区,国内精品一区二区无码,99精品无码视频在线播放,ā片国产在线播放