組件化框架設計之手寫組件化架構(五)


阿里P7移動互聯網架構師進階視頻(每日更新中)免費學習請點擊:https://space.bilibili.com/474380680
本篇文章將通過手寫組件化架構;路由框架原理與實現來闡述組件化框架設計

此次手寫架構,解決的問題是:

1、讓 App內 各個功能模塊能夠獨立開發單元測試,也可以 所有模塊集成打包,統一測試

獨立開發
更改gradle.properties的配置,使得每個功能模塊都成為application, 可以獨立打包成apk,單獨運行。單個模塊,獨立測試。

集成打包
更改gradle.properties的配置,使得原先每個單獨模塊,都變成library,被 主模塊引用,這時候只有主模塊能夠打包apk,所有功能都集成在這個apk內。

2、實現 功能模塊的整體移植,靈活拔插

故事背景
當你們公司有多個安卓開發人員,開發出核心業務相同,但是UI不同,其他業務不同的一系列App時(如果核心業務是X,你們有5個開發人員,做出了A,B,C,D,E 5個app,都包含核心業務X,但是除了X之外,其他的業務模塊各不相同)這時候,如果領導要把A里面的一個非核心功能,挪到B里面...

現狀
開發B的程序猿可能要罵娘,因為他在從移植A的代碼中剝離代碼 遇到了很多高耦合,低內聚 的類結構,挪過來之后,牽一發而動全身,動一點小地方,整個代碼滿江紅。

理想
如果這個時候,我們通過代碼框架的配置,能夠把A里面的一個模塊,作為一個module 移植到 工程內部,然后主module 來引用這個module,略微寫一些代碼來使得這個功能模塊在app中生效。那么無論是多少個功能模塊,都可以作為整體來 給其他app復用。這樣開發人員也不用相互罵娘了,如果挪過來的模塊存在bug或者其他問題,也不用甩鍋,模塊原本是誰開發的,找誰就好了。

3、保證App內 業務模塊的相互隔離,但是又不妨礙業務模塊之間的數據交互

我們開發app的功能模塊,一個業務,可能是通過一個Activity或者 一個Fragment 作為對外的窗口,也可能是。所謂窗口,就是這個業務,相對於其他模塊,"有且只有"一個入口,沒有任何其他可以觸達到這個業務的途徑。業務代碼之間相互隔離,絕對不可以有相互引用。那么,既然相互不會引用,那A模塊一定要用到B模塊的數據,怎么辦呢?下文提供解決方案。

正文大綱

1、代碼結構現狀以及理想狀態一覽

2、功能組件化的實現思路,實現組件移植拔插

3、參考ARouter源碼,寫出自己的Router框架,統一通過Router來進行模塊的切換 以及 組件之間數據的交互

4、使用組件api化,在模塊很多的情況下優化公共模塊的結構

正文

1、代碼結構現狀以及理想狀態一覽

現狀;

 
4100513-31814b6a97280e0a.png
 

代碼有模塊化的跡象,但是沒有對業務模塊進行非常明顯的模塊化(不明白啥意思是吧?不明白就對了,app這個module里面其實還有很多東西沒有展示出來,請看下圖:試想,把所有的模塊集中到一個module的一個包里面,當你要移植某一個功能的時候,想想那酸爽....當然如果你口味別致,那當我沒說)

 
4100513-d57e1c97d11fe368.png
 

理想:

 
4100513-ca0a1e182103f69b.png
image

理想化的話,參照:理想.png; 項目結構層次分明,脈絡清晰

按照圖中的分層,詳細解釋一下:

外殼層:app module

內部代碼只寫 app的骨骼框架,比如說,你的app是這個樣子的結構:

 
4100513-2a92ae505483170f.png
 

下方有N個TAB,通過Fragment來進行切換模塊。這種架構肯定不少見。

這個時候,外殼層 app module,就只需要寫上 上面這種UI架構的框架代碼就行了,至於有多少個模塊,需要代碼去讀取配置進行顯示。你問我怎么寫這種UI框架嗎?網上一大把的,如果實在找不到,來我的 github項目地址

業務層

我們的業務模塊,對外接口可能是一個Activity* 比如說,登錄模塊,只對外提供一個LoginActivity,有且僅有這一個窗口)或者 是一個Fragment,就像上圖(典型的app架構.png), 如果app的UI框架是通過切換Fragment來卻換業務模塊的話。business*這個目錄,將所有的業務模塊包含進去,每個模塊又是獨立的module,這樣既實現了業務代碼隔離,又能一眼看到所有的業務模塊,正所謂,一目了然。

功能組件層

每一個業務模塊,不可避免的需要用到一些公用工具類,有的是第三方SDK的再次封裝,有的是自己的工具類,或者自己寫的自定義控件,還有可能是 所有業務模塊都需要的 輔助模塊,都放在這里。

路由框架層

設計這一層,是想讓app內的所有Activity,業務模塊Fragment,以及模塊之間的數據交互,都由 這一層開放出去的接口來負責

gradle統一配置文件

工程內部的一些全局gradle變量,放在這里,整個工程都有效

module編譯設置

setting.gradle 配置要編譯的module; 也可以做更復雜的操作,比如,寫gradle代碼去自動生成一些module,免除人為創建的麻煩.


2. 功能組件化的實現思路,實現組件移植拔插

能夠兼顧 每個模塊的單獨開發,單獨測試 和 整體打包,統一測試。 聽起來很神奇的樣子,但是其實就一個核心:gradle編程。

打開gradle.properties文件:

 
4100513-5d831d5db7d4e00f.png
 

注解應該很清晰了,通過一個全局變量,就可以控制當前是要 模塊化單元測試呢?還是要集成打包apk測試。

那么,只寫一個isModule就完事了嗎?當然不是,還有一堆雜事 需要我們處理,我們要使用這個全局變量。

一堆雜事,分為兩類;

 
4100513-80afd6f0d18b1fa4.png
 

1- app 外殼層module 的build.gradle(注意:寫在dependencies)

if (isModule.toBoolean()) { 
   implementation project(":business:activity_XXX")
   //...在這里引用更多業務模塊
}

2- 每個業務module的build.gradle

第一處:判定 isModule,決定當前module是要當成library還是application

if (isModule.toBoolean()) {
    apply plugin:'com.android.library'
} else {
    apply plugin:'com.android.application'*
}

第二處:更改defaultConfig里面的部分代碼,為什么要改?因為當當前module作為library的時候,不能有applicationId "XXXX"這一句

defaultConfig {
    if (!isModule.toBoolean()) {
       applicationId"study.hank.com.XXXX"*
    }  
    ....
}

第三處:當業務模塊module作為library的時候,不可以在 AndroidManifest.xml中寫 Launcher Activity,否則,你打包app module的時候,安裝完畢,手機桌面上將會出現不止一個icon。而,當業務模塊module 作為application單獨運行的時候,必須有一個Launcher Activity ,不然...launcher都沒有,你測個什么鬼 ``` 所以這里針對manifest文件進行區分對待。

sourceSets {
        main {
            if (isModule.toBoolean()) {
                manifest.srcFile 'src/main/module/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
            }
        }
    }

由於要區分對待,我們就需要另外創建一個manifest文件,移除launcher配置即可。參考下圖:

 
4100513-ce1fabb1626ed3c9.png
 
 
4100513-78664e6ce0a6099d.png
 

這就是業務模塊組件化的秘密了。

什么,你問我怎么 進行功能拔插嗎?

當你不需要某一個模塊的時候,

1)在app的build.gradle里面 把 引用該模塊的配置去掉;

 
4100513-b3079b38c248963b.png
 

2)setting.gradle 的include 去掉它

 
4100513-c987f27afdf0dd70.png
 

3)app module 里面,改動代碼,不再使用這個模塊。(這個我就不截圖了,因為app module的UI框架代碼不是一句話說得清的。請運行我的demo源碼自己看吧)

功能的插入,同理,上面的過程倒過來走一遍,就不浪費篇幅了。


3. 參考ARouter源碼,寫出自己的Router框架,統一通過Router來進行模塊的切換 以及組件之間數據的交互

說到路由框架的使用價值,有兩點:

1、在app實現了組件化之后,由於組件之間由於代碼隔離,不允許相互引用,導致 相互不能直接溝通,那么,就需要一個 “中間人角色” 來幫忙*" 帶話"了.

2、app內部,不可避免地要進行Activity跳轉,Fragment切換。把這些重復性的代碼,都統一讓路由來做吧。省了不少代碼行數。

閱讀了阿里巴巴ARouter的源碼,參照阿里大神的主要思路,簡化了一些流程,去掉了一些我不需要的功能,增加了一些我獨有的功能,加入了一些自己的想法,寫出了自己的 ZRouter 路由 框架。那就不羅嗦了,上干貨吧。

基礎知識

如果以下基礎知識不具備,建議先去學習基礎知識。 或者 也可以跟着筆者的思路來看代碼,慢慢理解這些知識的實用價值。

java反射機制(路由框架里大量地使用了 class反射創建 對象)

APT 注解,注解解析機制(注解解析機制貫穿了整個路由框架)

javapoet , java類的元素結構(一些人為寫起來很麻煩的代碼,一些臟活累活,就通過自動生成代碼來解決)

如何使用

1- 在app module的自定義Application類里面,進行初始化, ZRouter准備就緒

public class FTApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        ZRouter.getInstance().initRegister(this);
    }
}

2- 就緒之后才可以直接使用(RouterPathConst 里面都是我自己定義的String常量):

切換Fragment

ZRouter.getInstance().build(RouterPathConst.PATH_FRAGMENT_MINE).navigation();

跳轉Activity

ZRouter.getInstance().build(RouterPathConst.PATH_ACTIVITY_CHART).navigation();

組件之間的通信,取得Mine模塊的 accountNo 然后 toast出來


String accountNo = ZRouter.getInstance().navigation(MineOpenServiceApi.class).accountNo();

Toast.makeText(getActivity(), accountNo, Toast.LENGTH_LONG).show();

如我們之前所設想的,切換Fragment,跳轉Activity,組件之間的通信 全部只能通過 ZRouter框架來執行。

3- 退出app時,要釋放ARouer的資源(主要是靜態變量)

ZRouter.getInstance().release();

4- 每個業務模塊,在將要暴露出去的Fragment或者Activity上,要加上注解

@ZRoute(RouterPathConst.PATH_ACTIVITY_CHART)//注冊Activity
public class ChartActivity extends AppCompatActivity {···}

或者

@ZRoute(RouterPathConst.PATH_FRAGMENT_HOME)//注冊Fragment
public class HomeFragment extends Fragment {···}

或者

@ZRoute(RouterPathConst.PATH_PROVIDER_MINE) // 注冊數據接口
public class MineServiceImpl implements MineOpenServiceApi  {···}

設計思路

講解設計思路,必須用源碼進行參照,請務必參照源碼 。
源碼地址為:https://github.com/18598925736/EvolutionPro

說明一下我本人 閱讀源碼的方法。也許很多人都和曾經的我一樣,看到一份第三方SDK的源碼,不知道從何下手,要么看了半天還在原地打轉轉,要么就是這里看一點,那里看一點,沒有中心思想,看了半天毫無收獲。

干貨:看源碼要思路清晰,目的明確。一切技術的價值都只有一個,那就是解決實際問題。既然是解決實際問題,那我們就從這個SDK暴露出來的最外圍接口為起點,看這個接口的作用是什么,解決了什么問題,順藤摸瓜,找找它解決問題的核心方法,至於順藤摸瓜道路上遇到的枝枝脈脈,要分清哪些是輔助類(每個人寫輔助類的習慣可能都不同,所以不必太在意),哪些是核心類(核心思想一般都是大同小異)。找到了核心思想,再從頭重新過幾遍,SDK的設計思路就了然於胸了哈.

按照我的上面提供的“干貨”,如果你現在下載了我的Demo源碼,那么我們繼續:

如果把看源碼的結構,理解為 警察查案。那么就要從最表層的現象開始着手,慢慢查找根源。

HomeFragment.java的54行, 這里要進行Activity跳轉。

ZRouter.getInstance().build(RouterPathConst.PATH_ACTIVITY_CHART).navigation();

這里有getInstance()方法,build()方法,還有navigation()方法,一個一個看

  • getInstance()是處在ZRouter類內部,是ZRouter的單例模式的get方法,單例模式就不贅述了,我寫了注釋
  • build()方法也是在ZRouter類內部,邏輯很簡單,就是new Postcard(path) 參數path是一個string,方法返回值是一個Postcard對象
  • navigation()方法是在Postcard類內部,但是,具體的執行邏輯,依然是在ZRouter類里面
    getInstance()build()方法都很簡單,不需要花太多精力。下面繼續跟隨ZRouternavigation()方法“追查”

ZRouternavigation() 方法內容如下:

Object navigation(Postcard postcard) {
        LogisticsCenter.complete(postcard);
        switch (postcard.getRouteType()) {
            case ACTIVITY://如果是Activity,那就跳吧
                return startActivity(postcard);
            case FRAGMENT://如果是Fragment,那就切換吧
                return switchFragment(postcard);
            case PROVIDER://如果是Provider,那就執行業務邏輯
                return postcard.getProvider();//那就直接返回provider對象
            default:
                break;
        }
        return null;
    }

發現一個可疑的代碼:LogisticsCenter.complete(postcard); 看方法名,應該是對postcard對象進行完善。
進去追查

/**
     * Postcard字段補全
     *
     * @param postcard
     */
    public static void complete(Postcard postcard) {
        if (null == postcard) {
            throw new RuntimeException("err:postcard 是空的,怎么搞的?");
        }

        RouteMeta routeMeta = Warehouse.routeMap.get(postcard.getPath());//
        if (null == routeMeta) {//如果路由meta是空,說明可能這個路由沒注冊,也有可能路由表沒有去加載到內存中
            throw new RuntimeException("err:路由尋址失敗,請檢查是否path寫錯了");
        } else {
            postcard.setDestination(routeMeta.getDestination());
            postcard.setRouteType(routeMeta.getRouteType());

            ···
        }
    }

這段代碼,從一個map中,用path作為keyget出了一個RouteMeat對象,然后用這個對象的字段值,對參數postcard的屬性進行賦值。好像有點莫名其妙。看不太懂。不着急,繼續。

剛才的navigation()方法這里存在switch分支,分支設計到ACTIVITY,FRAGMENT,PROVIDER,由於我們這次追查的只是activity相關,所以,忽略掉其他分支,只追查startActivity(postcard); 下面是該方法的代碼:

private Object startActivity(Postcard postcard) {
        Class<?> cls = postcard.getDestination();
        if (cls == null) {
            if (cls == null)
                throw new RuntimeException("沒找到對應的activity,請檢查路由尋址標識是否寫錯");
        }
        final Intent intent = new Intent(mContext, cls);
        if (Postcard.FLAG_DEFAULT != postcard.getFlag()) {//如果不是初始值,也就是說,flag值被更改過,那就用更改后的值
            intent.setFlags(postcard.getFlag());
        } else {//如果沒有設定啟動模式,即 flag值沒有被更改,就用常規模式啟動
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//常規模式啟動Activity
        }
        //跳轉只能在主線程中進行
        runInMainThread(new Runnable() {
            @Override
            public void run() {
                mContext.startActivity(intent);
            }
        });
        return null;
    }

這里只是一個簡單的跳轉操作,但是,發現了一個關鍵點,跳轉的“目的地”class是來自 postcarddestination . 發現規律了,原來剛才在 LogisticsCenter.complete(postcard); 里面進行postcard“完善”的時候,set進去的destination 原來在這里被使用到。

那么問題的關鍵點就發生了轉移了, 這個destination Class是從map里面get出來的,那么,又是什么時候被put進去的呢?

開始追蹤這個map : Warehouse.routeMap ,通過代碼追蹤,可以發現,唯一可能往map里面put東西的代碼只有這一句:

 
4100513-649a3c67cf448858.png
image
    /**
     * 反射執行APT注冊文件的注冊方法
     */
    private static void registerComm() {
        try {
            Set<String> classNames = ClassUtils.getFileNameByPackageName(mContext, RouterConst.GENERATION_PACKAGE_NAME);//找到包名下的所有class
            for (String className : classNames) {
                Class<?> clz = Class.forName(className);
                if (IRouterZ.class.isAssignableFrom(clz)) {
                    IRouterZ iRouterComm = (IRouterZ) clz.getConstructor().newInstance();
                    iRouterComm.onLoad(Warehouse.routeMap);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            Warehouse.traversalCommMap();
        }
    }

利用java反射機制,反射創建類的實例,然后執行onLoad方法,參數,正是這個map

OK,關於查看源碼的詳細步驟,就寫到這么多,再羅嗦,大佬們要打人啦。

目前為止的結論:通過追蹤ZRouter.getInstance().build(RouterPathConst.PATH_ACTIVITY_CHART).navigation();

我們一路上遭遇了這些類或接口:
核心類 :

ZRouter(提供Activity跳轉的接口);

輔助類或接口

Postcard (“明信片”,封裝我們要執行操作,這次的操作是 跳Activity)
RouteMeta (“路由參數”,Postcard的基類)
RouteType (“路由類型”,我們要執行的操作,用枚舉來進行區分)
LogisticsCenter ("物流中心",主要封裝ZRouter類的一些特殊邏輯,比如對Postcard對象進行完善補充 )
Warehouse (“貨艙”,用hashMap來存儲“路由”對象)
IRouterZ ("路由注冊"接口類 ,用於反射創建對象,從而進行路由的注冊)

上面用大量篇幅詳述了 追蹤源碼, 追查框架結構的方法,那么下面的篇幅就直接說結論了:

路由框架的結構,可以用一張圖表示:

 
4100513-87057e485595ad01.png
 

針對這張圖 簡單說兩句:

路由框架必然有3個部分,注解定義,注解解析,以及路由對外接口。
demo中我把這3個部分定義成了3個module.
其中,每個部分的核心代碼是:
zrouter-annotation模塊的 ZRoute @interface,IRouterZ 接口
zrouter-api模塊的ZRouter
zrouter-compiler 模塊的RouterProcessor
具體的代碼,不加以說明了。

如何用路由進行Activity跳轉,我寫了詳細步驟,相信沒人看不懂了。那么Fragment的切換,是我自定義的方法,可能有點粗糙,但是也是通俗易懂,就點到為止。但是,我們組件化的思想,就是要隔離所有的業務模塊,彼此之間不能進行直接通信,如果A模塊一定要使用B模塊的一些數據,通過路由框架也能實現。

HomeFragment類的第72行代碼:
String accountNo = ZRouter.getInstance().navigation(MineOpenServiceApi.class).accountNo();
這句代碼的意義是:在Home模塊中,通過路由框架,調用Mine模塊對外開放的接口accountNo();

追蹤這句代碼的navigation()方法,找到真正的執行邏輯 ZRouter類141行起:

public <T> T navigation(String serviceName) {
        Postcard postcard = LogisticsCenter.buildProvider(serviceName);
        if (null == postcard)
            return null;
        LogisticsCenter.complete(postcard);//補全postcard字段值
        return (T) postcard.getProvider();
    }

這里:最終返回了一個Provider對象.

LogisticsCenter類又有了戲份:LogisticsCenter.buildProvider(serviceName)LogisticsCenter.complete(postcard);

分別點進去看:

buildProvider(String) 方法,其實就是從map中找出RouteMeta對象,然后返回一個Postcard.

public static Postcard buildProvider(String name) {
        RouteMeta routeMeta = Warehouse.routeMap.get(name);
        if (null == routeMeta) {
            return null;
        } else {
            return new Postcard(routeMeta.getPath());
        }
    }

complete(Postcard)方法,其實就是完善postcard的字段,且,針對Provider,進行特別處理,反射創建Provider對象,並建立Provider的緩存機制,防止多次進行數據交互時進行無意義的反射創建對象。

/**
     * Postcard字段補全
     *
     * @param postcard
     */
    public static void complete(Postcard postcard) {
        if (null == postcard) {
            throw new RuntimeException("err:postcard 是空的,怎么搞的?");
        }

        RouteMeta routeMeta = Warehouse.routeMap.get(postcard.getPath());//
        if (null == routeMeta) {//如果路由meta是空,說明可能這個路由沒注冊,也有可能路由表沒有去加載到內存中
            throw new RuntimeException("err:路由尋址失敗,請檢查是否path寫錯了");
        } else {
            postcard.setDestination(routeMeta.getDestination());
            postcard.setRouteType(routeMeta.getRouteType());

            switch (routeMeta.getRouteType()) {
                case PROVIDER://如果是數據接口Provider的話
                    Class<? extends IProvider> clz = (Class<? extends IProvider>) routeMeta.getDestination();
                    //從map中找找看
                    IProvider provider = Warehouse.providerMap.get(clz);
                    //如果沒找到
                    if (null == provider) {
                        //執行反射方法創建,並且存入到map
                        try {
                            provider = clz.getConstructor().newInstance();
                            provider.init(mContext);
                            Warehouse.providerMap.put(clz, provider);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                    postcard.setProvider(provider);
                    break;
                default:
                    break;
            }
        }
    }

看到這里,整個路由框架,包括模塊間的通信,就講解完畢了。

做下結論吧:

使用路由框架的目的,是 在項目代碼組件化的背景之下,優化Activity跳轉,Fragment切換的重復代碼的編寫,而統一使用路由框架的對外接口執行跳轉或者切換。同時,通過路由框架的對外接口,實現組件之間的無障礙通信,保證組件的獨立性。

在探索框架的過程中,我們遇到了很多輔助類,但是輔助類怎么寫,完全看個人習慣,我是看了阿里巴巴的ARtouer框架之后得到啟發,按照它的思路來寫自己的路由框架,但是很多輔助類的寫法,我並完全按它的意思來。但是,核心思想,APT 注解+反射+自動生成代碼 是完全一樣的。

所以說,打蛇打七寸,看框架要看核心,拿住核心之后,其他的東西,就算代碼量再大,也是狐假虎威。


4、使用組件api化,在模塊很多的情況下優化公共模塊的結構

回顧一下理想中的項目結構:

 
4100513-0321a1adaf98d2b4.png
 

背景

這里的功能組件層 function,是存放各個業務模塊都需要的公共類或者接口。這里說的公共類,也包含了剛才所提及的 業務模塊之間進行通信所需要的接口。

舉例說明:A模塊,需要調用B模塊的test()接口,由於A不能直接引用B模塊,那這個test接口,只能放在function這個公共模塊內,然后A,B同時引用,B對test接口進行實現並通過注解進行路由注冊,A通過路由對外接口調用B的test方法。

現狀

誠然,這種做法沒毛病,能夠實現功能。但是隨着項目模塊的增多,function 里面會存在很多的業務模塊數據接口。有一種情況:如果存在A,B,C,D,E 5個模塊,它們都在function內存放了 數據接口,並且5個模塊都引用了function模塊。那么,當A需要,並且只需要B的數據接口,而不需要C,D,E的接口時,它還是不得不去引用這些用不着的接口。A不需要這些接口,但是,還不得不引用!這顯然會不合邏輯。並且這種 全部業務數據接口都塞到function模塊里面的做法,會導致function出現不必要的臃腫。

理想

每個業務模塊的數據接口,只和本模塊的業務有關,所以最好是放在本模塊之內,但是,如果放在本模塊之內,又會導致組件之間不能通信. 那么就創建一個專門的 Module來存放每個業務模塊的接口。想法可行,但是每個業務模塊的module數量一下子加倍了,又會造成維護困難的問題。那么有沒有方法可以自動生成這些數據接口模塊呢? 還真有~ 神奇的gradle編程 >_<*

關鍵詞

組件API化技術 使用gradle配置,對module內的特殊后綴文件進行檢索,並以當前module為基礎,自動生成新的module.

上干貨:

 
4100513-849fdf06841cccf4.png
 

這個名叫MineOpenServiceApi的接口,原本是.java后綴,現在改成.api

打開demo的setting.gradle文件:
找到下面的代碼:

include_with_api(':business:fragment_mine')

def include_with_api(String moduleName) {
    include(moduleName)
    //獲得工程根目錄
    String originDir = project(moduleName).projectDir
    //制作的 SDK 工程的目錄
    String targetDir = "${originDir}_api"
    //制作的 SDK 工程的名字
    String sdkName = "${project(moduleName).name}_api"
    System.out.println("-------------------------------------SDK name:" + sdkName)
    //刪除掉 SDK 工程目錄 除了 iml
    FileTree targetFiles = fileTree(targetDir)
    targetFiles.exclude "*.iml"
    targetFiles.each { File file ->
        file.delete()
    }
    //從待制作SDK工程拷貝目錄到 SDK工程 只拷貝目錄
    copy {
        from originDir
        into targetDir
        //拷貝文件
        include '**/*.api'
        include '**/AndroidManifest.xml'
        include 'api.gradle'
    }
    //讀取實現模塊的manifest並將package的值后加 .api 作為API工程的manifest package
    FileTree manifests = fileTree(targetDir).include("**/AndroidManifest.xml")
    manifests.each {
        File file ->
            def parser = new XmlParser().parse(file)
            def node = parser.attribute('package')
            parser.attributes().replace('package', "${node}.api")
            new XmlNodePrinter(new PrintWriter(file)).print(parser)
    }

    //將api.gradle改為build.gradle
    File build = new File(targetDir + "/api.gradle")
    if (build.exists()) {
        build.renameTo(new File(targetDir + "/build.gradle"))
    }

    // 將.api 文件改為 .java
    FileTree files = fileTree(targetDir).include("**/*.api")
    files.each {
        File file ->
            file.renameTo(new File(file.absolutePath.replace(".api", ".java")))
    }
    //加入 SDK工程
    include ":business:" + "$sdkName"
}

這段代碼來自一位很厲害的大神,它的作用是,檢索指定模塊里面,有沒有指定后綴名(.api)的文件,有的話,找出來,經過一系列處理(注解很詳細,應該能看懂),自動生成一個module. 生成的module名字比原來的module多一個_api. 表示這個模塊,包含原模塊的所有對外數據接口

有幾處細節需要注意:

  • 數據接口的.java后綴需要改成.api(整個.api完全和setting.gradle代碼里的.api對應,你可以都換成其他后綴,比如.apixxxxx)
  • 原模塊里面,會多出一個api.gradle,這個文件的名字也和 setting.gradle里的api.gradle對應,也可以修改
 
4100513-2d57590761b290bb.png
 

這個api.gradle並不會在本模塊被編譯的時候起作用,但是它最終會變成 _api 新模塊的build.gradle,並保持完全一樣的代碼。 新的_api模塊只是一個library,所以,要去掉 本模塊里面的build.gradle里面針對isModule的判定。

OK,感受一下組件API化的成果:

 
4100513-a69a7982d6f66b3a.png
 

理想實現了

現在不用把所有的數據接口都放到function公共模塊內,而只需要在本模塊之內將數據接口文件后綴改成.api,然后在setting.gradle里面使用自定義的方法進行include。 就可以只引用本模塊需要的 數據接口module,而不需要引用多余的module,而且,防止了function模塊的無意義的膨脹。簡直破費。

原文鏈接:https://www.jianshu.com/p/e73bc515ab53
阿里P7移動互聯網架構師進階視頻(每日更新中)免費學習請點擊:https://space.bilibili.com/474380680


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM