评论

收藏

[Unix] 腾讯Android研发岗必刷真题:说下组件之间的跳转和组件通信原理机制

服务系统 服务系统 发布于:2021-12-26 14:45 | 阅读数:244 | 评论:0

面试官: 说下组件之间的跳转和组件通信原理机制
心理分析:面试官从架构层次 了解求职者是否对组件化有深入研究。是否使用过组件化,使用有多深。通过该问题一目了然。如果能说出项目的演进 组件通信选型 绝对是一个加分项
求职者:应该从为什么会用到组件化 和组件定义,组件通信的演进说起
我们公司的一个单体项目进行组件化架构改造,我们最开始从以下7个方面入手:

  • 代码解耦。如何将一个庞大的工程分成有机的整体?
  • 组件单独运行。因为每个组件都是高度内聚的,是一个完整的整体,如何让其单独运行和调试?
  • 组件间通信。由于每个组件具体实现细节都互相不了解,但每个组件都需要给其他调用方提供服务,那么主项目与组件、组件与组件之间如何通信就变成关键?
  • UI 跳转。UI 跳转指的是特殊的数据传递,跟组件间通信区别有什么不同?
  • 组件生命周期。这里的生命周期指的是组件在应用中存在的时间,组件是否可以做到按需、动态使用、因此就会涉及到组件加载、卸载等管理问题。
  • 集成调试。在开发阶段如何做到按需编译组件?一次调试中可能有一两个组件参与集成,这样编译时间就会大大降低,提高开发效率。
  • 代码隔离。组件之间的交互如果还是直接引用的话,那么组件之间根本没有做到解耦,如何从根本上避免组件之间的直接引用,也就是如何从根本上杜绝耦合的产生?
今天则会从更小细粒度入手,主要讲讲在组件化架构下组件与组件之间通信机制是如何、包括所谓的UI跳转,其实也是组件化通信,只不过它稍微特殊点,单独抽取出来而已。学习知识的过程很常见的一个思路就是从整体概况入手,首先对整体有个粗略的印象,然后再深入细节,抽丝剥茧般去挖掘其中的内在原理,一个点一个不断去突破,这样就能建立起自己整个知识树,所以今天我们就从通信机制这个点入手,看看其中内在玄机有哪些。
思维导图
同样,在每写一篇文章之前,放个思维导图,这样做的好处对于想写的内容有很好的梳理,逻辑和结构上显得清晰点。
DSC0000.jpg

主流方式
总所周知,Android提供了很多不同的信息的传递方式,比如在四大组件中本地广播、进程间的AIDL、匿名间的内存共享、Intent Bundle传递等等,那么在这么多传递方式,哪种类型是比较适合组件与组件直接的传递呢。

  • 本地广播,也就是LoacalBroadcastRecevier。更多是用在同一个应用内的不同系统规定的组件进行通信,好处在于:发送的广播只会在自己的APP内传播,不会泄漏给其他的APP,其他APP无法向自己的APP发送广播,不用被其他APP干扰。本地广播好比对讲通信,成本低,效率高,但有个缺点就是两者通信机制全部委托与系统负责,我们无法干预传输途中的任何步骤,不可控制,一般在组件化通信过程中采用比例不高。
  • 进程间的AIDL。这个粒度在于进程,而我们组件化通信过程往往是在线程中,况且AIDL通信也是属于系统级通信,底层以Binder机制,虽说Android提供模板供我们实现,但往往使用者不好理解,交互比较复杂,往往也不适用应用于组件化通信过程中。
  • 匿名的内存共享。比如用Sharedpreferences,在处于多线程场景下,往往会线程不安全,这种更多是存储一一些变化很少的信息,比如说组件里的配置信息等等。
  • Intent Bundle传递。包括显性和隐性传递,显性传递需要明确包名路径,组件与组件往往是需要互相依赖,这背离组件化中SOP(关注点分离原则),如果走隐性的话,不仅包名路径不能重复,需要定义一套规则,只有一个包名路径出错,排查起来也稍显麻烦,这个方式往往在组件间内部传递会比较合适,组件外与其他组件打交道则使用场景不多。
说了这么多,那组件化通信什么机制比较适合呢?既然组件层中的模块是相互独立的,它们之间并不存在任何依赖。没有依赖就无法产生关系,没有关系,就无法传递消息,那要如何才能完成这种交流?
目前主流做法之一就是引入第三者,比如图中的Base Module。
DSC0001.jpg

组件层的模块都依赖于基础层,从而产生第三者联系,这种第三者联系最终会编译在APP Module中,那时将不会有这种隔阂,那么其中的Base Module就是跨越组件化层级的关键,也是模块间信息交流的基础。比较有代表性的组件化开源框架有得到DDComponentForAndroid、[阿里Arouter]( )、[聚美Router]( ) 等等。
除了这种以通过引入第三者方式,还有一种解决方式是以事件总线方式,但这种方式目前开源的框架中使用比例不高,如图:
DSC0002.jpg

事件总线通过记录对象,使用监听者模式来通知对象各种事件,比如在现实生活中,我们要去找房子,一般都去看小区的公告栏,因为那边会经常发布一些出租信息,我们去查看的过程中就形成了订阅的关系,只不过这种是被动去订阅,因为只有自己需要找房子了才去看,平时一般不会去看。小区中的公告栏可以想象成一个事件总线发布点,监听者则是哪些想要找房子的人,当有房东在公告栏上贴上出租房信息时,如果公告栏有订阅信息功能,比如引入门卫保安,已经把之前来这个公告栏要查看的找房子人一一进行电话登记,那么一旦有新出租消息产生,则门卫会把这条消息一一进行短信群发,那么找房子人则会收到这条消息进行后续的操作,是马上过来看,还是延迟过来,则根据自己的实际情况进行处理。在目前开源库中,有EventBus、RxBus就是采用这种发布/订阅模式,优点是简化了Android组件之间的通信方式,实现解耦,让业务代码更加简洁,可以动态设置事件处理线程和优先级,缺点则是每个事件需要维护一个事件类,造成事件类太多,无形中加大了维护成本。那么在组件化开源框架中有ModuleBus、[CC]( ) 等等。
这两者模式更详细的对比,可以查看这篇文章[多个维度对比一些有代表性的开源android组件化开发方案]( )
实现方案
事件总线,又可以叫做组件总线,路由+接口,则相对好理解点,今天从阅读它们框架源码,我们来对比这两种实现方案的不同之处。
组件总线
这边选取的是ModuleBus框架,这个方案特别之处在于其借鉴了[EventBus]( )的思想,组件的注册/注销和组件调用的事件发送都跟EventBus类似,能够传递一些基础类型的数据,而并不需要在Base Moudel中添加额外的类。所以不会影响Base模块的架构,但是无法动态移除信息接收端的代码,而自定义的事件信息类型还是需要添加到Base Module中才能让其他功能模块索引。
其中的核心代码是在与 ModuleBus 类,其内部维护了两个ArrayMap键对值列表,如下:
/**

  • Object methodClass
  • String methodName;
  • MethodInfo method info
    */
    private static ArrayMap<Object,ArrayMap<String,MethodInfo>> moduleEventMethods = new ArrayMap<>();
/**

  • Class IBaseClient.class
  • String methodName
  • Object methodClass
    */
    private static ArrayMap<Class<?>,ArrayMap<String,ArrayList<Object>>> moduleMethodClient = new ArrayMap<>();
在使用方法上,在onCreate()和onDestroy()中需要注册和解绑,比如
ModuleBus.getInstance().register(this);
ModuleBus.getInstance().unregister(this);
最终使用类似EventBus 中 post 方法一样,进行两个组件间的通信。这个框架的封装的post 方法如下
public void post(Class<?> clientClass,String methodName,Object...args){
if(clientClass == null || methodName == null ||methodName.length() == 0) return;
ArrayList<Object> clientList = getClient(clientClass,methodName);
if(clientList == null) return;
try{
for(Object c: clientList){
try{
ArrayMap<String,MethodInfo> methods = moduleEventMethods.get(c);
Method method = methods.get(methodName).m;
if(method == null){
Log.e(TAG,"cannot find client method"+methodName +"for args["+args.length+"]" + Arrays.toString(args));
return;
}else if(method.getParameterTypes() == null){
Log.e(TAG,"cannot find client method param:"+method.getParameterTypes() +"for args["+args.length+"]" + Arrays.toString(args));
return;
}else if(method.getParameterTypes().length != args.length){
Log.e(TAG,"method "+methodName +" param number not matched:method("+method.getParameterTypes().length+"), args(" + args.length+")");
return;
}
method.invoke(c,args);
}catch (Throwable e){
Log.e(TAG,"Notifiy client method invoke error.",e);
}
}
}catch (Throwable e){
Log.e(TAG,"Notify client error",e);
}
}
可以看到,它是通过遍历之前内部的ArrayMap,把注册在里面的方法找出,根据传入的参数进行匹配,使用反射调用。
接口+路由
接口+路由实现方式则相对容易理解点,我之前实践的一个项目就是通过这种方式实现的。具体地址如下:[DemoComponent]( ) 实现思路是专门抽取一个LibModule作为路由服务,每个组件声明自己提供的服务 Service API,这些 Service 都是一些接口,组件负责将这些 Service 实现并注册到一个统一的路由 Router 中去,如果要使用某个组件的功能,只需要向Router 请求这个 Service 的实现,具体的实现细节我们全然不关心,只要能返回我们需要的结果就可以了。
比如定义两个路由地址,一个登陆组件,一个设置组件,核心代码:
public class RouterPath {
//注意路由的命名,路径第一个开头需要不一致,保证唯一性
//Login Service
public static final String ROUTER_PATH_TO_LOGIN_SERVICE = "/login/service";

最后
今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。
最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司19年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
Android学习PDF+架构视频+面试文档+源码笔记
【Android核心高级技术PDF文档,BAT大厂面试真题解析】
DSC0003.jpg

【算法合集】

【延伸Android必备知识点】

【Android部分高级架构视频学习资源】
Android精讲视频领取学习后更加是如虎添翼!进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!