哔哩哔哩在Hilt组件化的使用 | 技术探索

DI(Dependency Injection),即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

最近业务同学需要接入谷歌推的Hilt框架。因为哔哩哔哩的业务上很容易出现业务层面的交叉,而因为项目完成了大量的组件化拆分。由于不希望业务之间产生相互引用,所有在技术评估完成之后我们决定由我们部门来对Hilt进行接入。

方案介绍

接入Hilt

摘自官方文档 使用 Hilt 实现依赖项注入

首先先声明下dagger.hilt.android.plugin相关的plugin。

buildscript {
    ...
    dependencies {
        ...
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.35.1'
    }
}
复制代码

其次由于hilt一大部分相关的都是基于kapt的代码生成逻辑,所以我们要在使用到hilt的模块的build.gradle中都定义如下相关的。

...
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'

android {
    ...
}

dependencies {
    implementation "com.google.dagger:hilt-android:2.35.1"
    kapt "com.google.dagger:hilt-android-compiler:2.35.1"
}
复制代码

我们需要给我们的Application增加一个注解。

@HiltAndroidApp
class ExampleApplication : Application() {  }
复制代码

根据官方接入文档哦,我们组只要把Application只要打上@HiltAndroidApp的注解,就可以完成这部分接入能力了。

Hilt在组件化

但是但是官方有个声明是这样的。

Hilt 代码生成操作需要访问使用 Hilt 的所有 Gradle 模块。编译 Application 类的 Gradle 模块需要在其传递依赖项中包含所有 Hilt 模块和通过构造函数注入的类。

从这部分说明上来看,这个注解最好是能放在com.android.application模块中, 这样就能保证依赖到所有的子模块中去了。

但是实际我们在使用过程中,由于com.android.application模块还是有一些代码量的,而由于kapt代码生成机制,需要先将kotlin代码转化成java代码,之后才能生成ast语法树。

根据ci上的实验结果,在com.android.application模块下kapt耗时在30s左右,而整体编译时间大概为3分钟左右。这种耗时我个人觉得还是属于不能接受的。

所以我们调整了下这部分代码。这次建立了一个壳module,这边只有HiltApplication的注解相关。然后将这个module依赖了所有业务仓库,按照编译逻辑来说,基于gradle taskdepend逻辑,他会在application模块编译之前,所有业务模块编译之后,这样能保证hilt生成的代码逻辑正常。

同时由于是一个空工程,我们把空工程定义为bundle-kapt,所以整体来说对于编译速度影响会变到最小。让各位大佬看下我们后续的优化结果。

image.png

上述是我最后截图出来的结果,我们将一个耗时大概30s的任务优化到3s,其实效果上来说已经非常明显,达到我们想要的预期了已经。

当然如果后续hilt支持了ksp之后,这部分速度应该可以更快,毕竟我么可以直接抛弃java语法树了吗。

出现了点小问题

这两天业务方实际在使用过程中,突然反馈说貌似我们接入的Hilt貌似不行啊,进入到页面直接崩溃了。

有一说一,一脸懵逼。先看看异常吧。初一开始我以为是kapt没有生成好或者别的什么原因导致的。

com.xxxxxx.xxxxxx}: java.lang.ClassCastException: xxxxx.DaggerHiltApplication_HiltComponents_SingletonC$ActivityRetainedCImpl$ActivityCImpl cannot be cast to xxxxx_GeneratedInjector
复制代码

只能一步步调试咯,我打开项目开启编译项目,之后进入蛮长的等待过程中。10分钟过去了终于好了。

image.png

由于Hilt使用了kapt,所以很自然的打开了build/generated/source/kapt文件路径,之后我看了下DaggerHiltApplication_HiltComponents_SingletonC这个类的生成。

26b412f7-ded9-474e-bddd-fdd525c0985d.png

ActivityRetainedCImpl从这里我大概猜测出了一小部分Hilt原理,通过收集不同子Module的抽象接口,然后把这部分能力聚合在HiltApplication中,举个例子Hilt_BangumiDetailActivityV3这个就是一个子业务内的DI注入的一个类的实现。

突然这个时候我想到了一件事哦,也就是说我们的bundle-kapt模块,其实它的实际编译产物会根据接入业务的不同而产生实际的变更。也就是说虽然这个模块的代码没有发生变更,但是由于子业务增加了注解和代码变更,导致了这个模块的kapt还是需要重新执行,这样才能保证输出的产物是变化的。

然而我们的项目之前由于工程结构太庞大了,可能有30-40个Module,所有我们将一部分没有变更的代码产物化,也就是基于commit变成了一个个aar或者jar发布到了远端。之后根据commit来重新定向到产物。

bundle-kapt这个模块也很不幸,被当做了一个静态模块,变成了一个远端的产物,之后即时业务添加了再多的注入相关的,因为bundle-kapt没有参与编译,所以注入的能力就出错了。


作者:究极逮虾户
链接:https://juejin.cn/post/6978407567047262222
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值