美團組件路由原理與實現詳解

原文連接javascript

前言

一、用通俗易懂的講解方式,講解一門技術的實用價值
二、詳細書寫源碼的追蹤,源碼截圖,繪製類的結構圖,儘可能詳細地解釋原理的探索過程
三、提供Github 的 可運行的Demo工程,可是我所提供代碼,更可能是提供思路,拋磚引玉,請酌情cv
四、集合整理原理探索過程當中的一些坑,或者demo的運行過程當中的注意事項
五、用gif圖,最直觀地展現demo運行效果*php

若是以爲細節太細,直接跳過看結論便可。
本人能力有限,如若發現描述不當之處,歡迎留言批評指正。css

最好結合視頻和源碼以及PPT圖片來深刻學習本篇文章內容
java

學到老活到老,路漫漫其修遠兮。與衆君共勉 !node

引子

最近得高人指點,恰巧工做中作過一個移植其餘app的某個功能模塊的任務,過程簡直痛不欲生。
而後思考如何對app的各個功能模塊進行靈活拔插配置,最大程度減小移植代碼出錯的可能性,保證功能模塊的完整移植*android

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

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

獨立開發git

更改gradle.properties的配置,使得每一個功能模塊都成爲application, 能夠獨立打包成apk,單獨運行。單個模塊,獨立測試。github

集成打包編程

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

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

故事背景

當大家公司有多個安卓開發人員,開發出核心業務相同,可是UI不一樣,其餘業務不一樣的一系列App時(若是核心業務是X,大家有5個開發人員,作出了A,B,C,D,E 5個app,都包含核心業務X,可是除了X以外,其餘的業務模塊各不相同)這時候,若是領導要把A裏面的一個非核心功能,挪到B裏面...

現狀

開發B的程序猿可能要罵娘,由於他在從移植A的代碼中剝離代碼 遇到了不少高耦合,低內聚 的類結構,挪過來以後,牽一髮而動全身,動一點小地方,整個代碼滿江紅。

理想

若是這個時候,咱們經過代碼框架的配置,可以把A裏面的一個模塊,

做爲一個module
移植到 工程內部,而後主module 來引用這個module,略微寫一些代碼來使得這個功能模塊在app中生效。那麼不管是多少個功能模塊,均可以做爲總體來 給其餘app複用。這樣開發人員也不用相互罵娘了,若是挪過來的模塊存在bug或者其餘問題,也不用甩鍋,模塊本來是誰開發的,找誰就行了。

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

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

正文大綱

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

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

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

四、使用組件api化,在模塊不少的狀況下優化公共模塊的結構

正文

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

先來看兩張圖

現狀

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

理想:

理想化的話,參照:理想.png; 項目結構井井有條,脈絡清晰

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

外殼層:app module

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

下方有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文件:

註解應該很清晰了,經過一個全局變量,就能夠控制當前是要 模塊化單元測試呢?仍是要集成打包apk測試。

那麼,只寫一個isModule就完事了嗎?固然不是,還有
一堆瑣事
須要咱們處理,咱們要使用這個全局變量。

一堆瑣事
,分爲兩類:

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配置便可。參考下圖:

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

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

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

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

2)setting.gradle 的include 去掉它

3)app module 裏面,改動代碼,再也不使用這個模塊。(這個我就不截圖了,由於app module的UI框架代碼不是一句話說得清的。請運行個人demo源碼本身看吧)

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

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

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

一、在app實現了組件化以後,因爲組件之間因爲代碼隔離,不容許相互引用,致使 相互不能直接溝通,那麼,就須要一個

「中間人角色」
來幫忙
" 帶話"
了.

二、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 {···}

複製代碼

設計思路

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

說明一下本人 閱讀源碼的方法。也許不少人都和曾經的我同樣,看到一份第三方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東西的代碼只有這一句:

/** * 反射執行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 ("路由註冊"接口類 ,用於反射建立對象,從而進行路由的註冊)

上面用大量篇幅詳述了 追蹤源碼, 追查框架結構的方法,那麼下面的篇幅就直接說結論了:
路由框架的結構,能夠用一張圖表示:

針對這張圖 簡單說兩句:
路由框架必然有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 註解+反射+自動生成代碼 是徹底同樣的。
因此說,打蛇打七寸,看框架要看核心,拿住核心以後,其餘的東西,就算代碼量再大,也是狐假虎威。

四、使用組件api化,在模塊不少的狀況下優化公共模塊的結構

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

背景

這裏的功能組件層 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.

不羅嗦,直接上乾貨:

這個名叫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對應,也能夠修改

![](https://upload-images.jianshu.io/upload_images/4100513-2d57590761b290bb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/211)
複製代碼

這個api.gradle並不會在本模塊被編譯的時候起做用,可是它最終會變成 _api 新模塊的build.gradle,並保持徹底同樣的代碼。 新的_api模塊只是一個library,因此,要去掉 本模塊裏面的build.gradle裏面針對isModule的斷定。

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

理想實現了

如今不用把全部的數據接口都放到function公共模塊內,而只須要在本模塊以內將數據接口文件後綴改爲.api,而後在setting.gradle裏面使用自定義的方法進行include。 就能夠只引用本模塊須要的 數據接口module,而不須要引用多餘的module,並且,防止了function模塊的無心義的膨脹。簡直破費。

結語

組件化的全攻略+Demo 已經所有放送完畢。

視頻和源碼能夠關注我我的介紹

特別說明: Demo只是提供一種組件化的全攻略,可能demo的代碼並無十分完善,好比:原ARouter源碼內的帶參數的跳轉,或者startActivityForResult,因爲時間關係我都去除了。一些輔助向的設計思路,我也並無徹底遵守ARouter源碼。
相關文章
相關標籤/搜索