Android組件化開發框架
打造自己的組件化開發框架
為什么會有這篇文章:長時間的build,不斷重復出現的bug,多次重復開發的煩躁
一、組件化介紹
0、什么是組件化
組件化就是將工程按照不同的屬性拆分成各個獨立的子工程的過程。
組件是組件化的輸出產物,不同的組件最終進行組裝就是完整的工程。
1、為什么要做組件化
用產品思維的一個理念就是, 為了更容易 讓用戶(開發人員以及測試人員) 做正確的事情 。因為隨著業務越來越復雜,代碼量越來越盤大的時候,開發人員在開發新功能或修改bug的時候有時候會不小心將其他正常的功能修改出新bug。而組件化之后,每一個組件都是獨立的互不影響。開發完成后,也能對單個組件進行單獨發布,測試人員就能對該組件進行單獨的測試,而不用受其他不相關的組件的影響。
2、組件化與模塊化和插件化的區別
組件化與模塊化的差別
組件化與模塊化的區別.png
(圖片來源: http://www.cnblogs.com/mfc-itblog/p/5385773.html )
組件的粒度大于模塊,組件由數個不同的模塊按需組合而來。
組件化與插件化的差別
組件化與插件化的區別.png
差別在于,組件化是編譯時合并到主工程,插件化是運行時加載到主工程
3、組件化的優點和缺點
優點:
單獨編譯,便于開發,提升編譯速度。
組件分離,便于維護,提高重用效率。
單獨發布,便于測試,提升測試準度。
缺點:
對開發管理的要求更高
二、組件化的架構設計
0、設計要求
1. 穩定高可用
2. 靈活
3. 簡單通用
1、整體架構設計圖
首先我們先看一下常規的架構設計
架構圖-常規的App.png
再看業務模塊化后的架構設計
架構圖-模塊化App.png
最后是組件化的架構設計
架構圖-組件化App.png
2、組件交互
數據路由.png
因為組件與組件之間不能相互引用,所以組件與組件的交互需要一個中間件Router,組件進行交互之前需要通過Router進行尋址中轉傳遞彼此的接口。
3、工程發布流圖
工程發布流程.png
與正常發布流程相同,先打包組件,然后最終在App中將組件合并后打包為apk文件發布。
三、組件化的技術難點
0、熱插拔的實現
熱插拔不是一個新詞,這里的熱插拔的與常規的還是有一定的區別。
熱插拔:添加依賴(插),則這個組件的數據庫、組件初始化、組件接口自動可用,去除依賴(拔),則這個組件的所有功能全部移除,并且不影響其他組件的編譯和發布。詳細看下面的數據庫和App初始化的工作流程
1、數據庫合并與拆分
實現方式:
方式1、在App中添加配置文件 db_libraries.properties ,配置文件中添加對應的組件的TableSetHelper,數據庫初始化時讀取配置文件中的內容通過反射調用組件中的TableSetHelper的方法。
兼容方式2、(如果沒有配置文件才執行)數據庫初始化時 掃描所有的Class ,找出所有 extends TableSetHelper 并有 @TableSetLibrary 注解的Class,反射調用其中的方法。
數據庫設計uml類圖
DBLibrary.png
數據庫合并及升級流程圖
組件化數據庫合并.png
2、組件Application生命周期
實現方式:
方式1:在App中添加配置文件 app_libraries.properties ,配置文件中添加對應的組件的Application,App初始化時讀取配置文件中的內容通過反射調用組件中的Application的生命周期方法。
兼容方式2:(如果沒有配置文件才執行)App初始化時 掃描所有的Class ,找出所有 extends Application 并有 @Applibrary 注解的Class,反射調用其中的生命周期方法。
App初始化流程設計uml類圖
Applibrary.png
App初始化流程
App初始化流程.png
3、組件之間的解耦與交互
為了達到組件之間的解耦,組件之間不能直接依賴,采用Router中間件的方式進行轉接。
組件之間交互流圖
數據路由訪問流圖.png
使用方法:
Router module中添加對應的接口
public interface ISum{
int sum(int a , int b);
}
在實現的Module中(ModuleA)添加對應的實現
@Data
public class SumImp implements ISum{
int sum(int a , int b){
return a+b;
}
}
編譯時IOC框架會掃描獲取到Data注解,自動在對應的ModuleA中生成一個類
public class ISumImpFactory {
public ISumImpFactory() {
}
public static final ISum generator() {
return new SumImp();
}
}
在另一個ModuleB中要使用這個接口時調用
DataRouter.findApi(ISum.class)
DataRouter會嘗試調用
ISum sum = ISumImpFactory.generator();
返回給ModuleB。
ModuleB 就能使用 sum 與ModuleA進行交互。
4、android.library依賴注入問題
在Library類型的Module中,R文件的ID并不是常量,這將導致ioc注入框架無法正常使用,這里的解決辦法是利用Gradle動態復制一份R類生成新的R文件(K.java),使用的時候使用新生成的K文件即可。
if (tsk.name.endsWith("Resources") && tsk.name.startsWith("process")) {
def taskName = tsk.name.replace("process", "").replace("Resources", "")
def taskR2 = task("build" + taskName + "K", dependsOn: tsk) {}
taskR2.doLast {
GeneroteK.autoGenerotaK(project)
}
tsk.doLast {
println "doLast:" + name
GeneroteK.autoGenerotaK(project)
}
}
5、Activity交互回調
Activity、Service等之間的數據交互均是使用Bundle傳遞,而Bundle只能傳遞基礎數據類型以及Serializable序列化的對象,這樣有比較大的局限性,尤其是某個Activity需要接收多種數據回調的時候尤其不方便。
這里研究了一種新的方式:使用Java的動態代理傳遞一個代理的接口回調給Bundle,然后使用代理的回調對象與調用者進行交互。
代碼示例:
public <T> T newProxy(Class<T> t, T o) {
MyHandle handle = new MyHandle(toString());
T callBack = (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{t},
handle);
ProxyStore.callBack.put(toString(), new WeakReference<Object>(o));
return callBack;
}
private class MyHandle implements InvocationHandler, Serializable {
String generator;
MyHandle(String generator) {
this.generator = generator;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(ProxyStore.callBack.get(generator).get(), args);
}
}
四、開發管理
說了這么多,看起來這么復雜,開發的時候會不會很難,發布的時候會不會很麻煩?其實跟正常發布沒有任何區別。
開發流程.png
后面會對其中的技術問題進行一一詳解。
來自:http://www.jianshu.com/p/3ed9f4c87990