Android组件化

1、组件化和插件化区别:

组件化开发:

(1)、组件化是将一个app分成多个Module,每个Module都是一个组件;
(2)、组件化在发布的时候,所有组件以lib的形式被主app工程依赖,并打包成1个apk,不支持动态加载。

插件化开发:

(1)、插件化是将整个app拆分成很多模块,这些模块包括一个宿主和多个插件,每个模块都是一个apk;
(2)插件化在发布的时候,可以将宿主apk和插件apk,分开或者联合打包,在运行时,宿主apk可以动态加载某个插件apk。

相同点:

(1)、两者都是为了使项目模块化解耦;
(2)、二者中组件或者插件都可以单独调试。

2、组件化的优点和缺点:

2.1、组件化的有点

(1)、业务隔离;
(2)、组件单独运行提高编译速度
(3)、组件化之后,很容易地实现一些组件层面的AOP,比如打开页面的时候进行异步数据加载;

2.2组件化的缺点:

(1)、每个组件都需要依赖公用库,会拖慢整体编译速度;

3、CC组件化原理

1、组件化优点之一是解耦,cc如何实现解耦
2、解耦之后的问题是组件之间如何进行通信(app之内的通信,和跨app通信)
3、既然后跨app通信,如何将组件单独运行,以及在A,L之间智能切换
4、组件注册和反注册过程繁琐,如何实现自动注册
5、如何监控调用超时以及取消,如何将调用和activity生命周期关联

cc组件化中,组件之间通信采用了组件总线通信的方式,在组件管理类(ComponentMananger)中注册了所有组件对象,当请求某个组件服务时,ComponentMananger通过查找映射表找到组件对象并调用并返回结果,它支持同步异步调用组件和同步异步实现组件,并且支持跨app调用组件。

3.1、app内部调用流程是

Android组件化_第1张图片
app内部调用流程.png

当组件a需要请求组件b的服务时,它就通过cc去调用cm,然后cm查找路由表,找到对应组件,进行调用,最后将结果返回给组件a。

3.2、跨app调用流程

Android组件化_第2张图片
跨app调用流程.png

当app1中的组件a请求app2中的组件B的服务时,它依然是通过cc调用cm,cm路由表中不存在组件B,它会通过BroadReceiver+Localsocket和app2建立通信,然后app2中的cm去查找自身的路由表,如果存在组件B,就就行调用,然后将结果通过LocalSocke返回给组件a。

3.3、app内部,组件之间的具体调用流程

Android组件化_第3张图片
app内部组件调用流程.png

3.4、跨app调用流程

Android组件化_第4张图片
1.0版本跨app组件调用流程
Android组件化_第5张图片
2.0版本跨app组件调用流程

这里有一个问题是如何知道组件在哪个进程?

前提:组件注册时会将组件名称及其对应的进程记录在一个map中

情况1:同一个app中,跨进程调用时,直接(调用ComponentManager.getComponentProcessName(componentName),)查询这个map,就可以获取组件所对应的进程。

情况2:跨app调用时,没法直接(调用ComponentManager.getComponentProcessName(componentName)查询调用方的这个map,去获取组件对应的进程名。因为组件没有在调用方的app中注册。

跨app调用时,获取组件对应进程的具体过程是:
1、app启动时,获取到手机中所有应用了cc框架的app,以及它对应的包名(这个包名即主进程名)
2、获取通过ContentProvider获取这些app主进程中的IRemoteCCService。
3、通过这个主进程的IRemoteCCService去查询对应app中,组件对应进程的map,就可以获取组件对应的进程名。

如何知道哪些app应用了cc框架?
在cc框架中内置一个RemoteConnectionActivity,并给其指定一个intent-filter,
在调用方app启动的时候,使用PackageManager去查询这个activity对应的activityinfo,继而通过这个ActivityInfo获取到对应packageName。

另外会通过广播监听app的安装,可直接获取app对应的包名,通过判断是否能启动RemoteConnectionActivity,判断是否应用了cc框架。如果是就继续进行2,3两步流程。

3.5、cc监控策略

在ChainProcessor开启拦截器链的之前是调用CCMonitor.addMonitorFor(cc);将此次调用流程加入到监控队列中。

超时监控

在CCMonitor中维护这一个ConcurrentHashMap,并且使用minTimeoutAt记录ConcurrentHashMap中cc的最短超时时间,当ConcurrentHashMap不为空时,就会开启一个监控线程去检测这个map中的cc,具体的过程就是监控线程先阻塞minTimeoutAt秒,然后去遍历map,超时cc从map中去除并调用cc.timeout,终止调用流程(设置将finished设为ture,每个拦截器调用chain.proceed之前会检测finished,如果为true就终止流程),然后在遍历的过程中筛选出队列中剩余最短的超时时间minTimeoutAt,再次循环检测。直到map为空,监控线程停止。(注意:当新加入的cc超时时间比minTimeoutAt小时,主动唤醒监控线程)。

在CCMonitor中会将所有cc加入ConcurrentHashMap,并开启一个异步线程去监控cc超时:
map中有监控对象时,循环遍历map,统计出下次需要进行超时调用操作距现在的时间间隔,进入wait状态,时间到后,被唤醒进行超时调用,并且将超时cc从map中移除,然后再次计统计出下次需要进行超时调用操作距现在的时间间隔,再次进入状态,以此循环。
直到map为空,跳出循环,当有监控对象加入时再次开启上述循环

和Activity、Fragment的生命周期关联

通过application.registerActivityLifecycleCallbacks(new CCMonitor.ActivityMonitor())注册一个自定义的
ActivityLifecycleCallbacks,在其回调方法onActivityDestroyed方法中遍历ConcurrentHashMap,如果当前Destroye的activity和cc中传入的cancelOnDestroyActivity相同就调用cc的cancel方法,取消整个调用流程。

Fragment类似
通过FragmentManager.registerFragmentLifecycleCallbacks注册一个自定义的FragmentLifecycleCallbacks。

4、cc-register

4.1、作用1:将module在application和library之间自动切换

4.1.1、现象:

所有应用cc-register插件的module,
(1)、可直接点击运行按钮单独运行;
(2)、当moduleA依赖moduleB,给moduleA打包时, moduleB自动变成library被依赖。

4.1.2、原理:

判断当前执行的task:
(1、如果当前module是主app(在主app中使用ext.mainapp=true声明),则应用application插件;)
(2、否则,如果当前module是library或者在local.properties声明打包,则应用library插件)
3、否则,如果当前task是对当前module集成打包或者当前task不是集成打包的task,则应用application插件,否则应用library插件
这样就保证了
1、 如果当前task是集成打包的task,并且不是为该module打包,那么该module是应用的library插件,可以被依赖;
2、如果当前task不是集成打包的task,module默认都是应用的application插件,可以直接运行 )

如何判断当前task是为本module做集成打包?

读取当前task,如果当前任务是ASSEMBLE|(BUILD)|(INSTALL)中的一种,并且前缀是当前module名,则是为当前module打包。

有些集成打包task名称前缀没有module名称,比如build,但是主app的build task本身是给主app打包,所以这里也需要给主app默认应用application插件(在主app的build.gradle的ext块中声明一个字段,然后读取这字段判断)。

然后根据module应用的是application插件还是library插件,使用sourceSets来给module应用不同的menifest文件。

4.1.3、如何做到module完全隔离
提供了addComponent dependencymodule 方法,只有在给当前module集成打包的时候才去依赖dependencymodule(dependencymodule没有在localProperties文件中声明排除),保证了组件化完全隔离。

4.2、作用2:组件自动注册

使用Transform API 结合ASM进行字节码插桩,在打包过程中,class文件转换dex的阶段,会经历多个Transform,我们可以自定义一个Transform得到所有的class文件,然后使用ASM对其进行操作。
在plugin的apply注册一个自定义的Transform类,在其transform方法中去进行扫描和插入。

4.2.1、扫描过程:

扫描所有jar文件中的class文件和本地目录中的class文件,
1、 根据类名比较找到要被插入代码的类并记录;
2、通过ASM中的classvisitor找到其实现了Icomponet接口的子类,并记录。
通过构造一个自定义的ScanClassVisitor,然后将class文件传入,(调用ClassReader的accept方法),就会回调自定义的ClassVisitor中的visit方法,在visit方法中我们可以得到当前类的父类和接口,然后进行判断即可。

4.2.2、插入过程

如果要被插入代码的类在jar文件中:

建立一个(optJar的)临时文件,)然后用这个文件构建一个JarOutputStream,)遍历Jar文件中的class文件,如果是被注入注册代码的类,就获取代码插入之后的字节码数组,写入(optJar)临时文件,如果不是就直接写入,最后将原jar文件删除,临时文件(optJar)重命名为原jar文件的名称。

如果要被插入代码的类是class文件

建立一个(optJar的)临时文件,获取代码插入之后的字节码数组,然后写入临时文件(optJar),最后删除原class文件,将临时文件(optJar)重命名为原class文件的名称。

使用asm插入字节码的具体过程

自定义一个classvisitor,将我们的要插入字节码class文件传入,在其visitMethod方法中判断是否是要被插入的方法,如果是就返回一个自定义的MethodVisitor,在自定义的MethodVisitor 的回调方法visitInsn中就可以将我们收集的Icomponet的子类构造一个对象调用相应方法进行插入。

总结:
添加一个组件分为两步:
(1)、在build.gradle对组件对应的module进行依赖
(2)、调用ComponetManager.registerComponet(Icomponent c)方法注册我们的组件。
一个前提是:cc中组件的概念是自定义一个类实现Icomponent接口就是一个组件。
这里的注册组件就是注册我们自定义的Icomponent的子类。

cc为了减少后期维护成本。它使用AOP的方式帮我们自动注册了组件。
具体的过程是在打包过程中有一个阶段是class文件转换dex的阶段,所有class文件会经历多个Transform进行处理,我们可以自定义一个Transform得到所有的class文件,然后扫描得到所有IComponent的子类,使用ASM操作字节码去调用注册方法进行注册。
1、怎么识别IComponent的子类
2、asm插入字节码的具体过程

5、cc组件化框架的优势

cc,DDComponentForAndroid,ModularizationArchitecture,arouter
从组件化的几个好处来看
1、代码解耦,业务隔离:
业务隔离其实不需要组件化框架,把一个组件的代码拷贝到一个module中,然后可以人为控制两个组件不耦合,但是这种控制可能是不可靠的,比如,
cc和DDComponentForAndroid在代码层面做限制,避免了各个模块之间的耦合,原理是,
arouter和ModularizationArchitecture并没有提供提这种代码层面的解耦限制。

2、加快编译速度-单独运行
2.1、各个模块可以单独运行以及被依赖
让module单独运行的原理,就是让它应用application插件以及配置对应的menifest;
组件有单独运行,必然也有可被依赖。它要被依赖的时候又要去给它应用library插件,以及配置library对应的menifest,过程繁琐。
cc和DDComponentForAndroid都做到了自动切换。现象是。。。
原理是。。。
ModularizationArchitecture和arouter都没有提供这种切换方式,由开发者自行解决。
2.2、加快编译速度-联合调试
module A 依赖 module B, 此时在module B
上开发一个功能,但是必须通过module A才能进入module B看到这个功能,,调试时,一种方式是将module A
和module B集成打包,一种方式是将module A和module B分开打包,module A通过跨app的方式和module B进行通信。由于我们只在module B开发功能,需要反复编译的是module B。集成打包所消耗的编译时间是(module A+module B)n,分开打包的编译时间是moduleA+module Bn。可以看出分开打包编译时间比集成打包编译时间缩减了(n-1)*module A。

比如首页和商户,首页单独运行调试没问题,但是调试商户时,需要从首页进去,这时就有两个选择,一个是首页商户联合打包,一个是首页商户分开打包,调试商户时,从首页组件跨app调用商户组件。在编译时间方面显然是后一种方式比较好。

然后在看哪些组件化框架支持跨app通信呢?
cc和ModularizationArchitecture,都是通过aidl实现跨进程通信,原理是,
DDComponentForAndroid无法跨app通信,
arouter使用URLScheme结合一个SchemeFilterActivity可以做到跨进程通信(通过URLScheme启动另外一个app中的SchemeFilterActivity,在这个activity中使用arouter进行分发),它的缺点是1、需要中转activity;2、不可利于数据回传;3、如果设备上安装了多个相同URLScheme的app,会弹出选择框;4、无法进行权限设置,无法进行开关设置,任意app都可调用,存在安全性风险

另外的亮点:
1、 cc是基于拦截器模式,容易扩展或实现一些组件层面的aop,比如页面跳转时异步请求数据。
2、使用字节码插桩自动注册组件,代替手动注册
3、支持超时,取消,并且和activity的生命周期关联

6、自己思考的组件化方案-缩短编译时间

不需要将组件单独打成apk,
1、自定义一个gradle插件,功能是可以将组件打成aar包,然后上传到maven库
2、然后各方向负责人只需要维护一个app壳module,和对应负责业务的module,其他的组件从maven依赖。
3、这样我们的编译时间就是app的编译时间加上一个业务module的编译时间。
4、发版上线的时候所有业务负责人发版最新的aar,然后集成到app中进行打包。

优点:
1、避免了跨进程通信的麻烦
2、不需要每个组件都依赖公共库

你可能感兴趣的:(Android组件化)