Android进阶

Android组件化—3、组件化具体实现

2019-06-25  本文已影响0人  VZzzzzz

参考文章:

https://www.jianshu.com/p/1b1d77f58e84

https://github.com/JessYanCoding/ArmsComponent

要实现组件化,不论采用什么样的技术路径,需要从业务角度上去考虑如何拆分组件,做到将一个庞大的工程拆分成N个有机的整体(组件),

例如梅沙教育App根据大的业务粒度我们这样划分:

(当然,根据其他维度划分也是可以的,这里只是举例说明):

这种拆分是比较直观的,从图上看,销售、动态、学员中心等都已经拆分组件,并共同依赖于公共的依赖库(简单起见只画了一个),然后这些组件都被主项目所引用。销售和动态等组件之间没有直接的联系,我们可以认为已经做到了组件之间的解耦

除了上面说的业务划分,还需要考虑的几点技术性问题:

代码解耦:

每个组件就是上面所指的一个有机完整的整体(模块),如何做到可以让其单独运行和调试?

组件单独运行:

如何从根本上避免组件之间的直接引用,完全的杜绝代码耦合是重中之重,只有做到这一点才能被称为真正的组件化。

 数据传递:

既然必须要做到代码完全隔壁解耦,每个组件都可能会给其他组件提供的服务,那么主项目(宿主)与组件、组件与组件之间如何传递数据?

页面跳转:

UI跳转可以认为是一种特殊的数据传递,在实现思路上有啥不同?

组件的生命周期:

我们的目标是可以做到对组件可以按需、动态的使用,因此就会涉及到组件加载、卸载和降维的生命周期。

集成编译:

在开发阶段如何做到按需的编译组件?一次调试中可能只有一两个组件参与集成,这样编译的时间就会大大降低,提高开发效率。

代码隔离:

组件之间的交互如果还是直接引用的话,那么组件之间根本没有做到解耦,如何从根本上避免组件之间的直接引用呢?也就是如何从根本上杜绝耦合的产生呢?只有做到这一点才是彻底的组件化。

注:“代码解耦”和“代码隔离”的区别:代码解耦指的是将项目按业务或某种划分维度划分模块,代表的是组件化思想,而代码隔离指的是在多个组件间集成开发时防止开发团队人员有意或无意间直接引用到其他组件的类或方法,造成灾难性的耦合从而致使组件化失败。所以代码隔离更多的指的是组件化开发过程中的防直接引用机制。

这些问题我们一个个来看。

1、代码解耦:

要想达到代码间的解耦首先需要解决以下几个问题:

1、组件作为一个独立的整体,怎么做到单独的调试,如何让组件单独运行呢?

2、如何在代码隔离的情况之下,组件与组件之间进行交互以及数据传输呢(跨组件通信)?

3、主项目可以直接引用组件吗,例如:compile project(:xiaoshou),答案是不可以的,如果是这样的话,那么主项目和组件之间的耦合就没有消除。正如前面所说,组件是可以动态管理的,如果我们删掉销售(销售)这个组件,那么主项目就不能编译了,谈何动态管理呢?所以主项目对组件的直接引用是不可以的,如何解决这个问题呢?

首先先看代码解耦要做到什么效果,像上面的直接引用并使用其中的类肯定是不行的了。所以我们认为代码解耦的首要目标就是组件之间的完全隔离,我们不仅不能直接使用其他组件中的类,最好能根本不了解其中的实现细节。只有这种程度的解耦才是我们需要的。

2、组件单独运行调试

单独调试比较简单,只需要把 apply plugin: 'com.android.library'切换成apply plugin:'com.android.application'就可以,但是我们还需要修改一下AndroidManifest文件,因为一个单独调试需要有一个入口的actiivity。

我们可以设置一个变量isBuildModule,标记当前是否需要单独调试,根据isBuildModule的取值,使用不同的gradle插件和AndroidManifest文件,甚至可以添加Application等Java文件,以便可以做一下初始化的操作。

为什么要使用不同的AndroidManifest文件?

由于组件在独立运行时和集成到宿主时可能需要AndroidManifest配置不一样的参数,

比如组件在独立运行时需要将其中一个Activity配置<actionandroid:name="android.intent.action.MAIN"/>作为app启动入口,

而当组件集成到宿主中时, 则依赖于宿主的入口, 所以当然不需要配置<actionandroid:name="android.intent.action.MAIN"/>,这时我们就需要两个不同的AndroidManifest应对不同的情况。

同时,为了避免不同组件之间资源名重复,在每个组件的build.gradle中增加resourcePrefix "xxx_",从而固定每个组件的资源前缀。下面是某个组件build.gradle的示例:

3、 跨组件通信

3.1 为什么需要跨组件通信?

因为各个业务模块之间是各自独立的, 并不会存在相互依赖的关系, 所以一个业务模块是访问不了其他业务模块的代码的, 如果想从A业务模块的A页面跳转到B业务模块的B页面, 光靠模块自身是不能实现的, 所以这时必须依靠外界的其他媒介提供这个跨组件通信的服务。

3.2 跨组件通信场景

跨组件通信主要有以下两种场景:

第一种是组件之间的页面跳转 (Activity到Activity,Fragment到Fragment,Activity到Fragment,Fragment到Activity) 以及跳转时的数据传递 (基础数据类型和可序列化的自定义类类型)

第二种是组件之间的自定义类和自定义方法的调用(组件向外提供服务)

3.3 跨组件通信方案

其实以上两种通信场景甚至其他更高阶的功能在ARouter中都已经被实现,ARouter是Alibaba开源的一个Android路由中间件, 可以满足很多组件化的需求, 也是组件化方案中比较重要的一环。

3.4 跨组件通信方案分析

第一种组件之间的页面跳转不需要过多描述了, 算是ARouter中最基础的功能,API也比较简单, 跳转时想传递不同类型的数据也提供有相应的API(如果想通过在URL中使用Json参数传递自定义对象, 需要实现SerializationService, 详情可以查阅ARouter 文档);

第二种组件之间的自定义类和自定义方法的调用要稍微复杂点, 需要ARouter配合架构中的 公共服务(CommonService) 实现, 主要流程在上面的“公共服务”中已有介绍, 示意图:

当然,除了ARouter,EventBus也可以作为服务提供的另一种方式, 但是由于EventBus因为其解耦的特性, 如果被滥用的话会使项目调用层次结构混乱, 不便于维护和调试, 所以EventBus可以充当辅助的组件通信工具,但不建议过多使用。

注:每个跨组件通信框架提供服务的方式都不同,ARouter只是其中一种比较优先的方案,除了ARouter也可以选择其他不同的服务提供方式。

4、 代码隔离

上面的我们使用了接口+实现的架构,组件之间必须针对接口编程,但是一旦我们引入了xiaoshou.aar,那我们就完全可以直接使用到其中的实现类啊,我们前面的工作就白费了。

我们希望只在assembleDebug或者assembleRelease编译运行的时候把aar引入进来,而在开发阶段,所有组件都是看不到的,这样就从根本上杜绝了引用实现类的问题。我们把这个问题交给gradle的

runtimeOnly来解决,我们创建一个gradle插件,然后每个组件都apply这个插件,插件的配置代码也比较简单:

ok,至此代码解耦、组件单独运行、数据传输、页面跳转、代码隔离的问题都一一解决了。。。

最后两个问题。。

5、组件的生命周期以及集成调试

前面提到了每个组件(模块) 在测试阶段都可以独立运行, 在独立运行时每个组件都可以指定自己的Application, 这时组件自己管理生命周期就轻而易举,

比如想在onCreate中初始化一些代码都可以轻松做到, 但是当进入集成调试阶段, 组件自己的Application已不可用,

每个组件都只能依赖于宿主的生命周期, 这时每个组件如果需要初始化自己独有的代码, 该怎么办?

5.1 问题分析

在集成调试阶段, 宿主依赖所有组件, 但是每个组件却不能依赖宿主, 意思是每个组件根本不知道自己的宿主是谁, 当然也就不能通过访问代码的方式直接调用宿主的方法, 从而在宿主的生命周期里加入自己的逻辑代码。

如果直接将每个模块的初始化代码直接复制进宿主的生命周期里, 这样未免过于暴力, 不仅代码耦合不易扩展, 而且代码还极易冲突, 所以修改宿主源码的方式也不可行。

所以有没有什么方法可以让每个组件在集成调试阶段都可以独自管理自己的生命周期呢?

其实解决思路很简单, 无非就是在开发时让每个组件可以独立管理自己的生命周期, 在运行时又可以让每个组件的生命周期与宿主的生命周期进行合并 (在不修改或增加宿主代码的情况下完成)。

5.2 解决方案

在基础层中提供一个用于管理组件生命周期的管理类, 每个组件都手动将自己的生命周期实现类注册进这个管理类, 在集成调试时, 宿主在自己的Application对应生命周期方法中通过管理类去遍历调用注册的所有生命周期实现类即可。

大概就是在基础层中定义有生命周期方法(attachBaseContext(), onCreate()...) 的接口, 每个组件实现这个接口, 然后将实现类注册进基础层的管理器,

宿主通过管理器在对应的生命周期方法中调用所有的接口实现类, 典型的观察者模式, 类似注册点击事件。

至此,问题全部得到解决,具体实践中可能会有更多的问题,只有一一踩过了我们才能真正的理解组件化。

上一篇下一篇

猜你喜欢

热点阅读