app壳 工程,是依赖所有组件的壳,该模块不应该包含任何代码,它只作为一个空壳存在,由于项目中使用了EventBusAPT技术,需要索引到各业务组件的对应的APT生成类,所以在 app壳 内有这一部分的代码。
脚本模块,该模块下存放的都是各个组件及封装的一些 Gradle 脚本文件。初衷是将所有的脚本统一管理,事实上我在组件内查找脚本的习惯还是没有改掉。
这是一个特殊的文件夹,负责项目的构建,里面存放着一些项目构建时用到的东西,比如项目配置,依赖。这里面还是存放 Gradle 插件的地方,一些自定义的 Gradle 的插件都需要放在此处。
项目的基础公共模块,存放着各种基类封装、对远程库的依赖、以及工具类、三方库封装, Android开源项目《ali1024.coding.net/public/P7/Android/git》 该组件是和项目业务无关的,和项目业务相关的公共部分需要放在 lib_common 中。
项目的业务公共模块,这里面存放着项目里各个业务组件的公共部分,还有一些项目特定需要的一些文件等,该组件是和项目业务有关系的。
网络模块,网络模块的配置、封装等,专门设立了一个组件来负责网路模块部分。
为了更好的代码隔离与解耦,在特定组件内使用的SDK及三方库,应该只在该组件内依赖,不应该让该组件的特定SDK及三方库的API暴露给其他不需要用的组件。有一个问题就出现了,SDK及三方库常常需要手动去初始化,而且一般都需要在项目一启动(即 Application 中)初始化,但是一个项目肯定只能有一个自定义的 Application,该项目中的自定义 Application 在 lib_base 模块中,并且也是在 lib_base 模块中的清单文件中声明的,那其他组件该如何初始化呢?带着这个问题我们一起来深入研究下。
常见的组件初始化解决方案:
在我的了解范围内,目前有两种最为常见的解决方案:
该方案是基于接口编程,自定义 Application 去实现一个自定义的接口(interface),这个接口中定一些和 Application 生命周期相对应的抽象方法及其他自定义的抽象方法,每个组件去编写一个实现类,该实现类就类似于一个假的自定义 Application,然后在真正的自定义 Application 中去通过反射去动态查找当前运行时环境中所有该接口的实现类,并且去进行实例化,然后将这些实现类收集到一个集合中,在 Application 的对应声明周期方法中去逐一调用对应方法,以实现各实现类能够和 Application 生命周期相同步,并且持有 Application 的引用及 context 上下文对象,这样我们就可以在组件内模拟 Application 的生命周期并初始化SDK和三方库。使用反射还需要做一些异常的处理。该方案是我见过的最常见的方案,在一些商业项目中也见到过。
该方案的后半部分也是和第一种方法一样,通过接口编程实现 Application 的生命周期同步,其实这一步是避免不了的,在我的方案中,后半部分也是这样实现的。不同的是前半部分,也就是如何找到接口的实现类,该方案使用的是 AndroidManifest 的 meta-data 标签,通过每个组件内的 AndroidManifest 内去声明一个 meta-data 标签,包含该组件实现类的信息,然后在 Application 中去找到这些配置信息,然后通过反射去创建这些实现类的实例,再将它们收集到一个集合中,剩下的操作基本相同了。该方案和第一种方案一样都需要处理很多的异常。这种方案我在一些开源项目中见到过,个人认为过于繁琐,还要处理很多的异常。
本项目中所使用的方案:
先来认识下 Java 的 SPI 机制:面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候不用在程序里动态指明,这就需要一种服务发现机制。JavaSPI 就是提供这样的一个机制:为某个接口寻找服务实现的机制。这有点类似 IOC 的思想,将装配的控制权移到了程序之外。这段话也是我复制的别人的,听起来很懵逼,大致意思就是我们可以通过 SPI 机制将实现类暴露出去。关于如何使用 SPI,这里不在陈述,总之是我们在各组件内通过 SPI 去将实现类暴露出去,在 Application 中我们通过 Java 提供的 SPI API 去获取这些暴露的服务,这样我们就拿到了这些类的实例,剩下的步骤就和上面的方案一样了,通过一个集合遍历实现类调用其相应的方法完成初始化的工作。由于使用 SPI 需要在每个模块创建对应的文件配置,这比较麻烦,所以我们使用 Google 的 AutoService 库来帮助我们自动创建这些配置文件,使用方式也非常的简单,就是在实现类添加一个 AutoService 注解。本框架中的核心类是这几个:lib_base-LoadModuleProxy、lib_base-ApplicationLifecycle。这种方案是我请教的一个米哈游的大佬,这位大佬告诉我在组件化中组件的初始化可以使用 ServiceLoader 来做,于是我就去研究了下,最后发现这种方案还不错,比前面提到的两种方案都要简单、安全。
在组件化方案中,资源命名冲突是一个比较严重的问题,由于在打包时会进行资源的合并,如果两个模块中有两个相同名字的文件,那么最后只会保留一份,如果不知道这个问题的小伙伴,在遇到这个问题时肯定是一脸懵逼的状态。问题既然已经出现,那我们就要去解决,解决办法就是每个组件都用固定的命名前缀,这样就不会出现两个相同的文件的现象了,我们可以在 build.gradle 配置文件中去配置前缀限定,如果不按该前缀进行命名,AS 就会进行警告提示,配置如下:
android {
resourcePrefix “前缀_”
}
其实组件的划分一直是一个比较难的部分,这里其实也给不到一些非常适合的建议,看是看具体项目而定。
关于基础组件通常要以独立可直接复用的角度出现,比如网络模块、二维码识别模块等。
关于业务组件,业务组件一般可以进行单独调试,也就是可以作为 app 运行,这样才能发挥组件化的一大用处,当项目越来越大,业务组件越来越多时,编译耗时将会是一个非常棘手的问题,但是如果每个业务模块都可以进行的单独调试,那就大大减少了编译时间,同时,开发人员也不需要关注其他组件。
关于公共模块,lib_base 放一些基础性代码,属于框架基础层,不应该和项目业务有牵扯,而和项目业务相关的公共部分则应该放在 lib_common 中,不要污染 lib_base。
组件化常见的一个问题就是依赖版本,每个组件都有可能自己的依赖库,那我们应该统一管理各种依赖库及其版本,使项目所有使用的依赖都是同一个版本,而不是不同版本。本项目中使用 buildSrc 中的几个kt文件进行依赖版本统一性的管理,及其项目的一些配置。
关于 Kotlin 协程,是真的香,具体教程可以看我的一篇文章:
Flow 类似于 RxJava,它也有一系列的操作符,资料:
PermissionX 是郭霖的一个权限申请框架 使用方式:
PermissionX.init(this)
.permissions(“需要申请的权限”)
.request { allGranted, grantedList, deniedList -> }
资料:
GitHub: [github.com/guolindev/P…](()
事件总线这里选择的还是 EventBus,也有很多比较新的事件总线框架,还是选择了这个直接上手的 在框架内我对 EventBus 进行了基类封装,自动注册和解除注册,在需要注册的类上添加 @EventBusRegister 注解即可,无需关心内存泄漏及没及时解除注册的情况,基类里已经做了处理
@EventBusRegister
class MainActivity : AppCompatActivity() {}
很多使用 EventBus 的开发者其实都没有发现 APT 的功能,这是 EventBus3.0 的重大更新,使用 EventBus APT 可以在编译期生成订阅类,这样就可以避免使用低效率的反射,很多人不知道这个更新,用着3.0的版本,实际上却是2.0的效率。 项目中已经在各模块中开启了 EventBus APT,EventBus 会在编译器对各模块生成订阅类,需要我们手动编写代码去注册这些订阅类:
// 在APP壳的AppApplication类中
EventBus
.builder()
.addIndex(“各模块生成的订阅类的实例 类名在base_module.gradle脚本中进行了设置 比如 module_home 生成的订阅类就是 module_homeIndex”)
.installDefaultEventBus()
屏幕适配使用的是 JessYan 大佬的 今日头条屏幕适配方案终极版
GitHub: [github.com/JessYanCodi…](()
使用方式:
// 在清单文件中声明
// 主单位使用dp 没设置副单位
// 默认是以竖屏的宽度为基准进行适配
// 如果是横屏项目要适配Pad(Pad适配尽量使用两套布局 因为手机和Pad屏幕宽比差距很大 无法完美适配)
// 以高度为基准进行适配 (还需要手动代码设置以高度为基准进行适配) 目前以高度适配比宽度为基准适配 效果要好
// 在Application 中设置
// 屏幕适配 AndroidAutoSize 以横屏高度为基准进行适配
我们见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了7、8年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。
其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。
不断奔跑,你就知道学习的意义所在!
注意:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
道学习的意义所在!
注意:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-G9PmbNGt-1650016809320)]