1. App項目組件化
做移動開發(fā)的同學(xué)都會發(fā)現(xiàn)這兩年在移動開發(fā)圈子里最火的就是組件化了,組件化不同的實現(xiàn)方案也引起了各派技術(shù)大拿的爭吵,技術(shù)人員的一個通病就是,總覺得自己的方案是最好的,吵歸吵但是大家的目標(biāo)其實是一致的,都是實現(xiàn)APP的功能組件化,為什么這兩年大家都開始考慮做組件化了呢?這也是因為隨著這幾年APP的快速發(fā)展,各個活下來的APP安裝包都由原先的幾兆到了幾十兆,甚至有些應(yīng)用超過了100M,每個APP的開發(fā)團(tuán)隊也有原先的2,3個人到了幾十人的團(tuán)隊,這就帶來了兩個問題,我的項目里實施組件化主要也是為了解決這兩個問題,
1)隨著代碼量的增加,每次編譯都需要10分鐘以上,寫完代碼真機(jī)調(diào)試每次launch都是個痛苦的過程,用團(tuán)隊里同學(xué)的話來說一天8個小時超過5個小時都是在等待,在上家公司因為單應(yīng)用代碼量太大,甚至出現(xiàn)了開發(fā)同學(xué)在AS里點了run后,就出去抽煙的現(xiàn)象,抽完一支煙回來也差不多編譯完了;
2)第二個問題就是單應(yīng)用代碼量太大了以后,如果組內(nèi)成員開發(fā)水平層次不齊就有可能造成代碼的高度耦合,修改代碼的影響會非常大,可能修改了一個很小的地方,但是卻引起了很多地方出現(xiàn)了問題,并且代碼的復(fù)用性也下降的很厲害。
組件化能很好的解決這兩個開發(fā)痛點,通過組件化可以將android每個功能module都編譯成aar,并統(tǒng)一放到maven私有倉庫里進(jìn)行統(tǒng)一管理,ios的每個功能module可以都打包放入cocoapods里,這樣每次編譯工程的時候開發(fā)人員只需要編譯自己的模塊進(jìn)行開發(fā)調(diào)試了,那將是非常快的,如果需要全工程運(yùn)行,那也會非常快,因為組件都是事先編譯好的執(zhí)行碼,不需要重新編譯,只需要對殼工程進(jìn)行編譯就可以了,速度也是非常快的,這就解決了編譯速度的問題;
對于優(yōu)秀的coder,一般都是有代碼潔癖的,這里的潔癖不單單指代碼整潔,更多的指的是應(yīng)用項目中的模塊高度復(fù)用,沒有冗余代碼,最討厭的就是看到有開發(fā)人員在項目中ctrl c ctrl v,組件化就能很好的解決這個問題,但是前提是你的功能組件規(guī)劃切分的比較好,能夠方便的在其它模塊中進(jìn)行復(fù)用。
2. ARouter解決了什么問題
2.1 組件化技術(shù)問題
上面介紹了那么多組件化的好處,那么了解過組件化的同學(xué)應(yīng)該知道組件化實施的難點和需要攻克的問題,不然組件化實施還是有一定技術(shù)門檻的,做的不好反而會增加團(tuán)隊的開發(fā)成本,下面我們先討論下組件化實施會遇到的技術(shù)問題:
1)如何解決組件單獨(dú)編譯的問題;
在組件化過程中,我們面臨的第一個問題就是如果將組件單獨(dú)編譯調(diào)試,并且可以方便的與其它組件一起打包成一個應(yīng)用;目前解決組件單獨(dú)編譯編譯問題一般都是通過修改module的gradle文件的apply plugin
來實現(xiàn),application和module的切換,做的好一些就是在gradle.properties中定義一個debug開關(guān),在gradle中增加一個if else判斷,如果當(dāng)前是debug模式,則module的gradle定義的apply plugin是application,如果非debug模式,那么module定義的apply plugin是module,這樣項目編譯的時候就會自動根據(jù)設(shè)置的debug參數(shù)來動態(tài)選擇是要單獨(dú)編譯模塊還是要編譯成整個app;
gradle.properties
IsDebug = false
module的build.gradle
if (IsDebug.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
2)如何解決組件件通訊問題;
組件化實施后必然存在各個組件之間的調(diào)用,目前的方案大部分都是通過intent進(jìn)行傳遞,這樣的話會造成傳遞的對象依賴,而且每個組件之間的調(diào)用也會比較混亂;在使用ARouter之前我們使用的方案是每個組件之間通過uri傳遞json,每個組件在被調(diào)用后從uri中獲取json,然后對json進(jìn)行反序列化成對象,再進(jìn)行后續(xù)動作,每個組件提供的服務(wù)都通過集中配置統(tǒng)一處理,在啟動的時候全部進(jìn)行初始化,在內(nèi)存中保存這些服務(wù)uri路由信息,之前casa提出的ios組件化方案是通過runtime反射出需要調(diào)用的組件,參數(shù)傳遞是通過Map來傳遞,實現(xiàn)了組件之間的實體依賴耦合;ARouter提出了類似目前服務(wù)端SOA的服務(wù)化概念,將每個組件都看成一個服務(wù)系統(tǒng),對其它組件都是通過提供服務(wù)的方式,讓其它組件完成調(diào)用,這樣可以做到每個組件的服務(wù)都是可管理的,而且組件之間的邊界是清晰的,耦合是松散的;個人覺得ARouter這種服務(wù)化的概念更理想化,在底層的服務(wù)注冊管理中心進(jìn)行統(tǒng)一管理,負(fù)責(zé)服務(wù)初始化、服務(wù)路由、服務(wù)調(diào)用、服務(wù)降級、服務(wù)資源回收等;
3)如何解決組件件頁面調(diào)用問題;
組件頁面的調(diào)用,按照服務(wù)化的理念,其實可以理解為服務(wù)的調(diào)用,原先我們實現(xiàn)的方案中,組件之間調(diào)用,如果是通過在自定義uri后面通過json傳遞需要傳遞的實體內(nèi)容,但對于非頁面的調(diào)用,就會導(dǎo)致產(chǎn)生組件間依賴是一種不干凈的實現(xiàn)方案,但是解決組件間的頁面調(diào)用用傳統(tǒng)的uri帶參數(shù)的方式是完全沒有問題的,這樣還可以在服務(wù)端統(tǒng)一管理所有的頁面調(diào)度策略,可以方便的做灰度發(fā)布,可以做到每個用戶的頁面調(diào)度策略都是由服務(wù)端下發(fā),對于需要進(jìn)行進(jìn)行灰度的用戶,可以使用灰度版本的頁面調(diào)度策略,非常方便;ARouter底層其實也是通過uri的方式來實現(xiàn),只不過又重新做了更好的封裝,能夠比較方便的使用,并且對非頁面的功能組件調(diào)用更加簡單,只需要定義好服務(wù)uri,然后在另一個組件中直接調(diào)用定義的uri就可以了;
4)如何解決組件間解耦問題;
組件間的解耦是組件化的另一個核心問題,評判一個組件化方案好不好,一個要看該組件化方案是否對工程的性能會造成影響,另一方面就是能夠很好的將組件化后的各個組件完美解耦;目前比較好的解耦方案一個是通過uri進(jìn)行服務(wù)化定義,另一個就是通過runtime反射來實現(xiàn)組件的調(diào)用,第一種是目前用的比較多的方案,第二種在部分方案中也有使用,反射方案上手會存在一定的難度,而且查詢問題會比較困難,但是性能方面反射方案會比uri方案要好,反正各有優(yōu)缺點,各家按照自己的實際情況對比后可以自行選擇;
2.2 ARouter功能介紹
https://github.com/alibaba/ARouter
- 支持直接解析標(biāo)準(zhǔn)URL進(jìn)行跳轉(zhuǎn),并自動注入?yún)?shù)到目標(biāo)頁面中
- 支持多模塊工程使用
- 支持添加多個攔截器,自定義攔截順序
- 支持依賴注入,可單獨(dú)作為依賴注入框架使用
- 支持InstantRun
- 支持MultiDex(Google方案)
- 映射關(guān)系按組分類、多級管理,按需初始化
- 支持用戶指定全局降級與局部降級策略
- 頁面、攔截器、服務(wù)等組件均自動注冊到框架
- 支持多種方式配置轉(zhuǎn)場動畫
- 支持獲取Fragment
- 完全支持Kotlin以及混編(配置見文末 其他#5)
2.3 ARouter典型應(yīng)用
- 從外部URL映射到內(nèi)部頁面,以及參數(shù)傳遞與解析
- 跨模塊頁面跳轉(zhuǎn),模塊間解耦
- 攔截跳轉(zhuǎn)過程,處理登陸、埋點等邏輯
- 跨模塊API調(diào)用,通過控制反轉(zhuǎn)來做組件解耦
從上面介紹的ARouter功能,可以發(fā)現(xiàn),第一節(jié)提到的這些組件化需要解決的技術(shù)問題,用ARouter基本都可以相對比較完美的解決, 如果項目使用ARouter后可以比較好的實現(xiàn)組件化,之前在2014年準(zhǔn)備實施組件化的時候,當(dāng)時還沒有特別好的組件化方案,所以項目是自己實現(xiàn)的組件化方案,實現(xiàn)的比較簡單,只是實現(xiàn)了組件頁面的跳轉(zhuǎn),參數(shù)的傳遞,將代碼從功能層面進(jìn)行了拆分,當(dāng)時拆了十幾個子工程,當(dāng)時拆分的過程還是比較順利的,當(dāng)時也希望能有一個比較完美的解決方案能在社區(qū)中冒出來,到了15年各種會議都有公司提出自己APP的組件化方案,其實也都是大同小異,而且也都沒有成體系,筆者也一直很關(guān)注這塊,直到阿里云的組件化方案ARouter發(fā)布,發(fā)現(xiàn)這就是我要的組件化方案,所以在新項目中也使用了該方案,目前所在公司的開發(fā)人員規(guī)模不是很大,沒有足夠的人力去自研組件化框架,所以使用阿里云的組件化方案還是比較方便穩(wěn)妥的,下面就講下怎么接入。
3. 快速接入ARouter
1)gradle中增加庫依賴
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.1.0'
testCompile 'junit:junit:4.12'
//下面兩行就是需要添加的ARouter的依賴,arouter-api這個庫是arouter的核心庫
//arouter-compiler是annnotation的定義庫依賴,對于組件中使用到arouter注解的情況,一定要增加該依賴
compile 'com.alibaba:arouter-api:1.2.1.1'
annotationProcessor 'com.alibaba:arouter-compiler:1.0.3'
}
2)增加uri路徑注解
/**
* https://m.shrb.com/modulea/activity1
*/
@Route(path = "/modulea/activity1")
public class Activity1 extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_1);
}
}
ARouter管理的頁面都需要通過@Route
注解進(jìn)行標(biāo)注,在注解中需要定義path來表示uri的路徑,忘記從哪個版本開始,必須至少使用兩級目錄,第一級目錄代表group,group的概念后面會闡述下,在ARouter中對所有發(fā)布的服務(wù)做了懶加載,只有g(shù)roup中的任意一個服務(wù)第一次被調(diào)用的時候才會去一次行把該group下的服務(wù)統(tǒng)一加載到內(nèi)存,這樣可以避免啟動的時候初始化過多的可能不會用到的組件服務(wù);
3)ARouter初始化
if (isDebug()) { // 這兩行必須寫在init之前,否則這些配置在init過程中將無效
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 開啟調(diào)試模式(如果在InstantRun模式下運(yùn)行,必須開啟調(diào)試模式!線上版本需要關(guān)閉,否則有安全風(fēng)險)
}
ARouter.init(mApplication); // 盡可能早,推薦在Application中初始化
官方是建議將ARouter的初始化放在自定義的Application中初始化,這樣可以避免在某個頁面中或者service中初始化,后期資源被回收的問題,導(dǎo)致所有的組件服務(wù)全部失效,放在Application中就可以保證ARouter事例在內(nèi)存中的生命周期和APP保持一致,不會存在資源被誤回收的可能;在我后續(xù)的例子中為了方便我將初始化放在了demo主Activity中,這只是為了演示,實際項目使用過程中大家一定要放到Application中去初始化,防止app在使用過程中出現(xiàn)一些莫名其妙的問題;
4)頁面路由
//組件無參數(shù)跳轉(zhuǎn)
ARouter.getInstance()
.build("/modulea/activity1")
.navigation();
//組件攜帶參數(shù)跳轉(zhuǎn)
ARouter.getInstance()
.build("/modulea/activity1")
.withString("name", "老王")
.withInt("age", 18)
.navigation();
定義的activity1是和上面調(diào)用的activity不在一個module中,我們這里將activity1定義在了moduleA下面,activity1獲取參數(shù),可以像spring一樣定義Autowired注解,但是這里的Autowired注解可不是spring類庫下的自動綁定注解類,而是arouter庫下的Autowired類,在activity定義了參數(shù)的同名局部變量后就可以在activity中通過ARouter.getInstance().inject(this);
來自動獲取到傳遞的參數(shù),arouter會自動注入到變量中,這樣整個過程是不是看起來很簡單,很清晰。
/**
* https://m.shrb.com/modulea/activity1?name=老王&age=23
*/
@Route(path = "/modulea/activity1")
public class Activity1 extends Activity {
@Autowired
public String name;
@Autowired
public int age;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_1);
//傳遞參數(shù)注入
ARouter.getInstance().inject(this);
Log.d("param", name + age);
}
}
4. 深入學(xué)習(xí)ARouter
上面我們介紹了如果簡單的引入ARouter并且進(jìn)行不同組件間的頁面路由,下面我們再介紹下ARouter一些高級技能。
1)解析URI中的參數(shù)
// 為每一個參數(shù)聲明一個字段,并使用 @Autowired 標(biāo)注
// URL中不能傳遞Parcelable類型數(shù)據(jù),通過ARouter api可以傳遞Parcelable對象
@Route(path = "/modulea/activity1")
public class Activity1 extends Activity {
@Autowired
public String name;
@Autowired
int age;
@Autowired(name = "girl") // 通過name來映射URL中的不同參數(shù)
boolean boy;
@Autowired
TestObj obj; // 支持解析自定義對象,URL中使用json傳遞
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ARouter.getInstance().inject(this);
// ARouter會自動對字段進(jìn)行賦值,無需主動獲取
Log.d("param", name + age + boy);
}
}
- 定義攔截器
這里所指的攔截器和j2ee中里常說的攔截器是一個概念,就是在處理這個服務(wù)前需要處理的動作,也類似jsp中的filter,和okhttp中的攔截器也是一樣的概念,可以在攔截器中實現(xiàn)切面公共的功能,這樣這些切面公共功能就不會和業(yè)務(wù)服務(wù)代碼耦合在一起,是一種比較好的AOP實現(xiàn),ARouter實現(xiàn)的攔截器也可以對攔截器設(shè)置優(yōu)先級,這樣可以對攔截器的處理優(yōu)先順序進(jìn)行處理;但是這個攔截器整體功能還是比較弱,目前的版本實現(xiàn)的是全服務(wù)攔截,沒有參數(shù)可以定義pointcut攔截點,所以如果要對指定頁面進(jìn)行處理只能在
// 比較經(jīng)典的應(yīng)用就是在跳轉(zhuǎn)過程中處理登陸事件,這樣就不需要在目標(biāo)頁重復(fù)做登陸檢查
// 攔截器會在跳轉(zhuǎn)之間執(zhí)行,多個攔截器會按優(yōu)先級順序依次執(zhí)行
@Interceptor(priority = 8, name = "測試用攔截器")
public class TestInterceptor implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
Log.d("Interceptor","攔截器測試");
callback.onContinue(postcard); // 處理完成,交還控制權(quán)
// callback.onInterrupt(new RuntimeException("我覺得有點異常"));
// 覺得有問題,中斷路由流程
// 以上兩種至少需要調(diào)用其中一種,否則不會繼續(xù)路由
}
@Override
public void init(Context context) {
// 攔截器的初始化,會在sdk初始化的時候調(diào)用該方法,僅會調(diào)用一次
}
}
- 外部通過URL跳轉(zhuǎn)APP的內(nèi)部頁面
定義SchemaFilterActitity,所有外部來調(diào)用本APP的請求,都會先到該SchemeFilterActivity,由該Activity獲取uri后再通過Arouter進(jìn)行轉(zhuǎn)發(fā),這樣就可以實現(xiàn)幾種效果,1.同一部手機(jī)上可以通過自定義url來訪問我們app對外暴露的頁面并接收外部應(yīng)用傳遞過來的值,這樣對于集團(tuán)內(nèi)應(yīng)用交叉營銷非常方便;2.對外分享的二維碼直接掃碼后打開app,對于在推廣的app,可以實現(xiàn)掃碼識別url后直接從外部手機(jī)瀏覽器跳轉(zhuǎn)到我們的app某個頁面,支付寶、微信支付都是這樣實現(xiàn)在支付的時候喚起原生頁面的;
public class SchameFilterActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Uri uri = getIntent().getData();
ARouter.getInstance().build(uri).navigation();
finish();
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.commonlib">
<application
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true">
<activity android:name=".SchameFilterActivity">
<!-- Schame -->
<intent-filter>
<!--自定義host和scheme -->
<data
android:host="m.hop.com"
android:scheme="shrb" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
</activity>
<activity android:name=".HopWebview"></activity>
</application>
</manifest>
4)定義全局降級頁面
相信我們之前遇到過這樣的問題,如果跳轉(zhuǎn)的頁面不存在,或者調(diào)用的組件服務(wù)不存在就會報錯甚至crash,這里ARouter為我們提供了默認(rèn)降級服務(wù),一旦路由找不到頁面或者服務(wù)就會調(diào)用該降級service,我們可以在繼承自DegradeService類的onLost方法中實現(xiàn)降級需要實現(xiàn)的動作;
// 實現(xiàn)DegradeService接口,并加上一個Path內(nèi)容任意的注解即可
@Route(path = "/xxx/xxx")
public class DegradeServiceImpl implements DegradeService {
@Override
public void onLost(Context context, Postcard postcard) {
Log.d("DegradeService","降級服務(wù)啟動!");
}
@Override
public void init(Context context) {
}
}
5)組件服務(wù)注冊與調(diào)用
前面我們說了組件間的互相調(diào)用都是通過暴露接口,或者服務(wù)來實現(xiàn),筆者原來公司服務(wù)之間的互相調(diào)用都是通過直接調(diào)用依賴接口,所以調(diào)用方需要依賴被調(diào)用組件的接口,ARouter是將組件的服務(wù)對外暴露,調(diào)用方直接使用組件暴露的服務(wù)uri就可以了,使用起來和spring調(diào)用遠(yuǎn)程服務(wù)接口很像,使用起來很簡潔;
服務(wù)注冊
// 聲明接口,其他組件通過接口來調(diào)用服務(wù)
public interface HelloService extends IProvider {
String sayHello(String name);
}
// 實現(xiàn)接口
@Route(path = "/service/hello", name = "測試服務(wù)")
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "hello, " + name;
}
@Override
public void init(Context context) {
}
}
服務(wù)調(diào)用:
public class Test {
@Autowired
HelloService helloService;
@Autowired(name = "/service/hello")
HelloService helloService2;
HelloService helloService3;
HelloService helloService4;
public Test() {
ARouter.getInstance().inject(this);
}
public void testService() {
// 1. (推薦)使用依賴注入的方式發(fā)現(xiàn)服務(wù),通過注解標(biāo)注字段,即可使用,無需主動獲取
// Autowired注解中標(biāo)注name之后,將會使用byName的方式注入對應(yīng)的字段,不設(shè)置name屬性,會默認(rèn)使用byType的方式發(fā)現(xiàn)服務(wù)(當(dāng)同一接口有多個實現(xiàn)的時候,必須使用byName的方式發(fā)現(xiàn)服務(wù))
helloService.sayHello("monkey0l");
helloService2.sayHello("monkey01");
// 2. 使用依賴查找的方式發(fā)現(xiàn)服務(wù),主動去發(fā)現(xiàn)服務(wù)并使用,下面兩種方式分別是byName和byType
helloService3 = ARouter.getInstance().navigation(HelloService.class);
helloService4 = (HelloService) ARouter.getInstance().build("/service/hello").navigation();
helloService3.sayHello("monkey01");
helloService4.sayHello("monkey01");
}
}
5. 現(xiàn)有項目ARouter改造實踐
上面介紹了組件化的一些方案和ARouter組件化方案的使用,下面我們就拿筆者真實的項目例子去講解下實施的一些經(jīng)驗。
對于手上有項目需要進(jìn)行組件化的小伙伴,下面的這些內(nèi)容應(yīng)該會對你有所幫助,會給大家實施的一個思路,其實組件化的思路很重要,具體使用什么框架去實施其實是次要的。
1)對現(xiàn)有項目進(jìn)行業(yè)務(wù)功能拆分;
項目實施組件化一般都是因為項目太復(fù)雜或者代碼耦合問題比較嚴(yán)重,一修改代碼就出現(xiàn)問題,所以首先我們要做的就是先對整個APP的功能進(jìn)行拆分,對各個組件功能進(jìn)行邊界劃分,這個工作其實是整個組件化過程的核心,組件化好不好全看組件功能拆分的好不好;當(dāng)時我們拆分的過程主要分為幾部分:
1.殼工程
殼工程主要用于將各個組件組合起來和做一些工程初始化,初始化包含了后續(xù)各個組件會用到的一些庫的初始化,也包括ApplicationContext的初始化工作;
2.基礎(chǔ)類庫
基礎(chǔ)類庫主要還是將各個組件中都會用到的一些基礎(chǔ)庫統(tǒng)一進(jìn)行封裝,例如網(wǎng)絡(luò)請求、圖片緩存、sqllite操作、數(shù)據(jù)加密等基礎(chǔ)類庫,這樣可以避免各個組件都在自己的組件中單獨(dú)引用,而且引用的版本可能都不一樣,導(dǎo)致整個工程外部庫混亂,統(tǒng)一了基礎(chǔ)類庫后,基礎(chǔ)類庫保持相對的穩(wěn)定,這樣各個組件對外部庫的使用是相對可控的,防止出現(xiàn)一些外部庫引出的極端問題,而且這樣的話對于庫的升級也比較好管理;
3.基礎(chǔ)工程
對于每個組件都有一些是公共的抽象,例如我們工程中自己定義的BaseActivity、BaseFragment、自定義控件等,這些對于每個組件都是一樣的,每個組件都基于一樣的基礎(chǔ)工程開發(fā),一方面可以減少開發(fā)工作,另一方面也可以讓各個組件的開發(fā)人員能夠統(tǒng)一架構(gòu)框架,這樣每個組件的技術(shù)代碼框架看起來都是一樣的,也便于后期代碼維護(hù)和人員互備;
4.業(yè)務(wù)模塊
最大的一塊體力工作就是業(yè)務(wù)模塊的實現(xiàn)了,上面的幾部分都實現(xiàn)以后,剩余的主要體力工作就是實現(xiàn)各個拆分出來的業(yè)務(wù)模塊;
2)基礎(chǔ)類庫抽離;
對于原先工程中,由于開發(fā)人員技術(shù)參差不齊、開發(fā)趕進(jìn)度、沒有代碼復(fù)查等原因,導(dǎo)致代碼基礎(chǔ)類庫管理混亂,一個網(wǎng)絡(luò)請求居然都有好幾個外部庫,在組件化過程中我們對常用的幾個公共類庫進(jìn)行了統(tǒng)一,并且都封裝在了一個基礎(chǔ)類庫中。
組件化:ARouter
MVVM:google Databinding
圖片:picasso
網(wǎng)絡(luò):Retrofit
異步框架:RxJava
數(shù)據(jù)庫:greenorm
Hybrid:jsBridge
日志管理:Logger、timber
消息Event:EventBus
消息推送:JPush
APM埋點:友盟
熱更新:Tinker
3)基礎(chǔ)工程抽離;
對于基礎(chǔ)工程抽離其實比較簡單,因為我們原先的工程對于公共類這已經(jīng)抽象的比較好了,主要是在基礎(chǔ)工程中抽象出了BaseActiviry、BaseFragment、BaseApplication、BaseViewModel等公共類,在每個Base類中都已經(jīng)定義了一些必須要實現(xiàn)的抽象類和已經(jīng)定義好的基礎(chǔ)函數(shù)功能,這樣以后每個定義的Activity或者Fragment都繼承了這些Base類的基礎(chǔ)功能,能夠減少很多公共代碼的開發(fā)工作,也可以在基礎(chǔ)類中實現(xiàn)統(tǒng)一異常處理,統(tǒng)一消息捕獲,切面攔截等等,只要是組件中公共的功能都可以在這里實現(xiàn);
4)殼工程開發(fā);
殼工程其實比較簡單,一般殼工程中主要承擔(dān)了項目初始化的工作,在殼的Application中需要對后續(xù)其它組件用到的全局庫進(jìn)行統(tǒng)一初始化,也要承擔(dān)一些例如版本檢測、配置文件加載、刷新、組件加載、熱更新等工作。殼工程主要做的都是些基礎(chǔ)的臟活累活,殼工程開發(fā)好后,就可以進(jìn)行業(yè)務(wù)功能開發(fā)了,每次開發(fā)單模塊功能的時候,可以在殼工程中只引用自己模塊需要的功能進(jìn)行開發(fā)編譯調(diào)試了,不需要全部組件引用,這樣可以大大縮減每次編譯的時間,如果打開instance run那么編譯的速度就更快了;
5)業(yè)務(wù)功能開發(fā);
每個組件的業(yè)務(wù)功能開發(fā)這里就沒什么好說的了,主要就是堆人去實施;
6)全回歸測試;
在整個組件化過程中,最大的問題就是怎么保證組件化后原功能沒有問題,這就要求我們有良好的CI來保障,持續(xù)集成是個很大的話題,后面我們單獨(dú)寫幾篇文章來說下持續(xù)集成的實施經(jīng)驗,這里就不詳細(xì)說了,通過CI不斷的迭代提交代碼,不斷的自動化回歸來保證代碼的質(zhì)量,如果沒有好的CI,實施這么大的一個組件化重構(gòu),風(fēng)險還是非常大的!這里沒有危言聳聽,我們的組件化和CI基本是同步實施的,沒有CI那就意味著,所有組件化后的測試都要求手工回歸去測試,那么測試的工作量是巨大的,而且不能很好的保證重構(gòu)后一定沒有問題,因為是一個線上版本,一旦出了問題那就意味著大量用戶的差評和流失,后果不堪設(shè)想,所以如果開發(fā)團(tuán)隊對于現(xiàn)有產(chǎn)品只是為了趕新潮而去實施組件化,那就算了不要去組件化,如果遇到上面我提到的那些問題了,那就去組件化,但是要切記通過CI來保證質(zhì)量,不然會死的很慘。
小結(jié):
筆者目前一共實施過3個項目的組件化,第一個是傳統(tǒng)國有銀行APP的組件化重構(gòu),使用的是自研的簡單deeplink URL組件化方案,組件化核心代碼量非常小,幾個核心類加起來大概也就1000多行代碼,整個實施過程有個比較好的前提就是,團(tuán)隊已經(jīng)具備比較好的CI能力,而且公司預(yù)留了充足的人力和時間(3個月)讓我們?nèi)嵤┙M件化,期間沒有新需求,估計大部分公司都不會有這樣的待遇,當(dāng)時因為是第一次實施,期間也遇到了很多問題,當(dāng)時還沒有那么多資料可以參考,現(xiàn)在組件化的資料真的是鋪天蓋地,最后經(jīng)過十幾名同事的努力終于完成了整體項目的組件化實施并順利上線,組件化以后再增加新功能那叫一個舒服。。
第二個組件化項目是電商項目,這個項目和上一個銀行的組件化項目最大區(qū)別就在于,互聯(lián)網(wǎng)電商項目是不可能給你3個月完全沒有新需求的,這就要求我們要給在高速飛行的飛機(jī)換零件,一旦出問題那就是機(jī)毀人亡,所以在這個項目的實施過程中,我們主要解決了如何在現(xiàn)有的項目上實施增量的組件化,實施增量組件化的核心思路就是在開發(fā)出殼工程后,將原工程作為一個big組件,一步步的將里面的功能拆分出來,等big組件消失了,那也就拆完了,這個項目用的組件化方案也是自研的一套URL方案,和上一個銀行的組件化方案非常像,只是在其中還實現(xiàn)了組件功能的服務(wù)化發(fā)布,但是沒有實現(xiàn)ARouter的服務(wù)注冊、治理整套機(jī)制,這個項目實施下來就比第一個項目組件化順利的多;
第三個組件化的項目是一個O2O的項目,這個項目是新項目使用組件化實施,完全沒有舊項目的負(fù)擔(dān),而且那個時候ARouter也出來了,我們在項目中也首次使用了ARouter,有了前面兩次的磕磕盼盼,整體實施起來可謂是順風(fēng)順?biāo)疫€是新項目,很快就組件化實施上線。
組件化還是個比較龐大的工程,尤其對于線上項目進(jìn)行組件化重構(gòu),還是存在很大的風(fēng)險,這篇文章對于想實施組件化的同學(xué)應(yīng)該有一定的參考意義,大家看完了如果覺得好就點個贊吧。