前言
下面的题目都是大家在面试字节跳动或者其它大厂面试时经常遇到的,如果大家有好的题目或者好的见解欢迎分享。
参考解析:郭霖、鸿洋
内容特点:条理清晰,含图像化表示更加易懂。
内容概要:包括 Handler、Activity相关、Fragment、service、布局优化、AsyncTask相关、Android 事件分发机制、 Binder、Android 高级必备 :AMS,WMS,PMS、Glide、 Android 组件化与插件化等面试题和技术栈!
接下来我们针对字节跳动Android中高级面试展开的完整面试题
实现方案
直接依赖
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0RbIaNgP-1648368714893)(https://upload-images.jianshu.io/upload_images/24334488-9e0d26f98b01926e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
这种方式实现简单,但是耦合太严重,不方便维护与开发,当工程逐渐增大模块逐渐增多,依赖关系会非常复杂,不推荐这种方式。
事件或广播通信
EventBus: 我们非常熟悉的事件总线型的通信框架,非常灵活,采用注解方式实现,但是难以追溯事件。广播: 安卓的四大组件之一,在一个模块中发送广播设置数据,在另一个模块中注册广播接收数据,使用广播进行数据传递方式广播相对于其他的方式而言消耗资源较多。
总结: BroadcastReceiver、EventBus,非常灵活,模块之间没有任何的耦合,但是代码的可读性差,难以追溯事件,不是很推荐。
路由通信
模块与模块之间不存在依赖关系,而是各自运作,简单的来说就是映射关系的路由通信,也是目前比较主流的一种方案,比较常用的开源框架是阿里的ARouter。
ARouter典型应用
从外部URL映射到内部页面,以及参数传递与解析跨模块页面跳转,模块间解耦拦截跳转过程处理登陆、埋点等逻辑跨模块API调用,通过控制反转来做组件解耦。
面向接口通信
以上几种方式只是简单的介绍,下面就具体说下通过接口解耦通信的方式,首先先看几个问题。
什么是面向接口编程?
接口大家都很熟悉,这里所说的面向接口编程,并不只是所谓的 java 中的 interface,而是指超类型,可以是接口也可以是抽象类。
面向接口比面向对象编程是更先进一步编程思想,而是附属于面向对象编程的体系,属于其中一部分,它是面向对象编程体系中的思想精髓之一。面向接口编程它的核心思想是将抽象与实现分离,从组件的级别来设计代码,达到高内聚低耦合的目的。面向接口编程方法是,先定义底层接口模块,也就是 通信的协议与功能约定 ,是提供方实现对应的功能与能力。在架构中层次分明,不需要关注具体实现,开发中可以通过接口快速制定协议,与提供能力api,对于上层通过接口显露能力,对于下层只需要依赖接口层相当于依赖api。
面向接口编程的好处?
灵活性高没有依赖具体的实体,实现层可以任意的更改与切换。在模块化中可以相互依赖service(接口层)或依赖多个。
在模块化中的使用下面对于接口(interface)或api层统称为service,其含义为服务提供者。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OlPPMoMX-1648368714894)(https://upload-images.jianshu.io/upload_images/24334488-e4db9873990022a9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
对于,每一个 module 都一个独立的工程结构,每个 module 都有自己的 Service ,来统一暴露当前 module 所拥有能力与向外提供的服务。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NEm1RNoX-1648368714894)(https://upload-images.jianshu.io/upload_images/24334488-4d537d08c926dd88.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
对于 module 是在同一个工程里的项目结构,service 可以放到统一的一个 Module 下,我们统称为 Mediator,这样做的目的是为了减少 Module 创建与维护。假设你的工程有20个业务 Module 如果都同时增加一个 service 层就会造成 Module 数量翻一倍。由于这里存入的都一些接口类,也是每个业务 Module 向外提供的服务其体量不会太大,这里并只是一个建议并没有标准的做法。
当然也有更复杂的设计,一个 Module 又分不同的 service 实现如图,这里不在展开细说。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1DblQqBw-1648368714895)(https://upload-images.jianshu.io/upload_images/24334488-224ded712760a791.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
实际工程中使用与设计
在实际项目中有很多项目都同时开发两版本Pad与Phone,有的是两独立的工程,有的是在同一个工程内用 flavor 切换不同的工程,下面我就以通过 flavor 切换的工程结构举例。先看下工程的包的结构图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JSEWxnKa-1648368714895)(https://upload-images.jianshu.io/upload_images/24334488-185ea68cc3411b4d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
可以看到 module 结构是分为三个部分,common, pad, phone, 如果每个service 都独立将增加3倍的 Module 数量。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bv9VdkE4-1648368714896)(https://upload-images.jianshu.io/upload_images/24334488-44039205371950d7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
使用一个 Mediator Module 统一管理这这些 service 就很好控制了 module 数量。
Service 创建
在 module_mediator 业务 module 下 common,pad、pone 下分别创建ICommonService, IService(pad), IService(phone)。ICommonService:公共服务。IService(pad):pad服务并继承CommonService。IService(phone):phone服务并继承CommonService。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vKRwpmt1-1648368714896)(https://upload-images.jianshu.io/upload_images/24334488-a646da7197b03c2d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
注:这里为什么不用,PadService与PhoneService,是因为pad与phone版本同时只会存在一个,使用方只需要关心你提供的Service不用在区分版本,而且这里是一个继承关系也可以获取到共用的部分。
Service 实现
依赖 Mediator :[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nzThNr0Y-1648368714897)(https://upload-images.jianshu.io/upload_images/24334488-e495248b3185cde3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
在业务 common\pad\phone module 下分别实现,ICommonService, Service(pad), IService(phone) ,在 common module 创建 CommonServiceImpl 实现 ICommonService,在 pad、phone module 分别创建 ServiceImpl 对应实现 IService 并继承与 CommonServiceImpl。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ED2tdgYB-1648368714897)(https://upload-images.jianshu.io/upload_images/24334488-d2b0b16d5b090e09.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
Service 注册
注册的方式有一般是通过代码用去注册,或通过注解进行注册。可以在 Application 注册也可以在业务 Module 下自己注册,如果使用注解则可以自动注册,具体要看项目怎样实现。例:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vGbcEule-1648368714898)(https://upload-images.jianshu.io/upload_images/24334488-e229b4f057bdf5b6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
解释下 MediatorServiceFacator,它只是一个服务工厂也是一个接口类,作用是负责管理各业务方的 Service 主要功能是注册与获取 Service。上面的代码就是往里注册了一个会员的 Service。
可以看出这个函数只有两个参数,一个是接口class一个是实现类class,第一个参数cls:它会作为 key 来使用,第二个参数implClass:它会作为 value 来使用。
Service 使用
通过 MediatorServiceFacator 懒加载获取service对象,如果业务方没有注册则获取一个空的对象。
注册有 service 没有使用时是不会创建的,如果使用过则会缓存下来,下次调用则直接返回。(第一次是通过反射创建)例:
- 在 mediator 模块下会员 CommonService 中 定义了一个模糊查询会员的方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PqnwGYHO-1648368714898)(https://upload-images.jianshu.io/upload_images/24334488-d7c7b4e4210695f1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
- 在会员模块下 common 中实现了该功能。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nBg9RESE-1648368714899)(https://upload-images.jianshu.io/upload_images/24334488-846e6a5569727c07.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
- 在会员模块下 pad 中继承了这个实现。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ce69Z3SO-1648368714899)(https://upload-images.jianshu.io/upload_images/24334488-d8da76d2b3d52891.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
- 在其他模块 pad 下使用这个功能。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ApAT4pUr-1648368714899)(https://upload-images.jianshu.io/upload_images/24334488-0a39743dcf6f9a0a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
可以看到获取 Service 只要传对应接口就即可,对于使用方是不用关心实现方,在开发过程中只要先定义好接口,合作的同学就可以进入正常开发了。细心的同学可以看出,返回的数据类型也是一个接口类,为什么不直接返回一个普通 java 类呢?主要原因是通过接口方法达到双方 api 约定,例如 getName() :String 方法是通过方法名返回值达到约定效果,这样不依赖具体实现。
从上面的例子可以看出主要分为三个部分:1、定义接口。2、提供方实现接口。3、使用方都通过服务工厂获取服务使用。对于使用者来是很简单的,不需要关心实现,通过接口可以直接获取到实现,并且获取到结果可以直接使用,不需要做序列化处理。
有了路由通信我们为什么还使用面向接口编程?
路由模式虽然很好的解决了耦合的问题,但他的方法调用都是静态的,对于传参与返回值只能是基本类型,如果是对象需要做序列化与反序列化处理,对性能有一定影响。类似在调后台接口一样,同时降低了代码的可读性, 对于 app 而言所有 Module 都是在同一个应用下,没有必要做这些序列化操作。
对于复杂业务不好处理,例如一个业务需要多次通信,路由模式则不好处理,而通过接口通信则可以容易解决。例如:一个读卡的操作,业务方需要对它有开启、关闭、暂停等多个状态的操作。通过接口则可以直接返回一个读卡的 service 控制器, 这样可以直接进行相应的控制操作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gIS6lAgX-1648368714900)(https://upload-images.jianshu.io/upload_images/24334488-47e70a649c53d334.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
从上面代码中可以看出,上层回调结果的同时并回调了一个控制接口,这样就提供使用方一个反向操作的能力。
《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
优秀开源项目:
ali1024.coding.net/public/P7/Android/git
github.com/android
尾声
以薪资待遇为基础,以发展为最终目标,要在高薪资的地方,谋求最好的发展!
下面是有几位Android行业大佬对应上方技术点整理的一些进阶资料。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ERop1Tw7-1648368714900)(https://upload-images.jianshu.io/upload_images/24216715-30c3e174f5b9737b.png?imageMogr2/auto-orient/strip|imageView2/2/w/928/format/webp)]
- github.com/android
尾声
以薪资待遇为基础,以发展为最终目标,要在高薪资的地方,谋求最好的发展!
下面是有几位Android行业大佬对应上方技术点整理的一些进阶资料。
[外链图片转存中…(img-ERop1Tw7-1648368714900)]