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

 

 本文由用戶 xqnt6222 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!