美文网首页Android开发Android架构师Android开发经验谈
手写组件化架构;路由框架原理与实现

手写组件化架构;路由框架原理与实现

作者: 西柚9102 | 来源:发表于2019-06-25 22:20 被阅读25次

    前言

    本篇组件化架构文章,参考了很多资料以及大牛的指点,整理了蛮久,所以可能过分详细,因为想把所有的经验和想法全部分享出来,希望能对一些朋友会有帮助。
    如果觉得细节太细,直接跳过看结论和视频就好啦~

    引子

    最近恰巧工作中遇到一个移植其他app的某个功能模块的任务,过程简直痛不欲生!有幸得到高人的指点~
    然后思考如何对app的各个功能模块进行灵活拔插配置,最大程度减少移植代码出错的可能性,保证功能模块的完整移植。

    此次手写架构,解决的问题是:

    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、代码结构现状以及理想状态一览

    先来看两张图
    现状;

    代码有模块化的迹象,但是没有对业务模块进行非常明显的模块化(不明白啥意思是吧?不明白就对了,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来进行模块的切换 以及组件之间数据的交互

    说到路由框架的使用价值,有两点:

    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东西的代码只有这一句:

        /**
         * 反射执行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 注解+反射+自动生成代码 是完全一样的。

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


    4、使用组件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对应,也可以修改

    这个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源码。

    这篇文章学习参考的组件化架构视频教程;https://pan.baidu.com/s/1PKUjSBixd3IyuR4VoBK2pQ
    提取码:z21d

    都看到最后了,关注一波小姐姐的公众号,和小姐姐一起学Android吧~

    相关文章

      网友评论

        本文标题:手写组件化架构;路由框架原理与实现

        本文链接:https://www.haomeiwen.com/subject/lnnxcctx.html