美团组件路由原理与实现详解

原文连接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源码。
相关文章
相关标签/搜索