前言
最近在做技术通道代码评审时,发现腾讯挺多项目有使用比较“老”的dagger2框架,自己也是几年前在上家公司使用过,具体使用和原理都有点模糊了,完全不好评判评审代码优劣。 但是明确记得jectpack的hilt 是基于dagger2优化的。也私聊了部分开发同学,为什么不使用更好用的hilt,得到的答复基本都是历史代码为了统一代码风格。在review代码时候发现依赖注入看起来比较乱,并没有看出啥独到之处。为了更好的评审代码,抽空补补dagger2发现这个框架还是挺有意义的。
下面分3个部分介绍dagger2, 分别介绍他的基本使用和框架实现原理以及熟悉dagger2框架实现意义以及自己在学习该框架的一些思考总结
1.基本的使用
dagger2出现的背景:
比如项目中有一个全局到处都用到的对象,以我们项目举例股票对象:Stock 假如我要改动Stock对象的默认无参构造为每个地方都要给他传一个参数进去 而这个Stock对象在整个项目成百上千个地方都直接new Stock(),有这种改动时 需要全部替换为new Stock("参数")。当然我们可以全局搜索替换。不纠结这个 想表达的就是创建对象在各个地方都是new的,耦合比较严重,dagger2就是为了解决这种耦合,把new的过程通过APT在编译阶段生成代码,我们只需要通过注解标识,自动帮我们new出来,这样我们不用去改成百上千的地方,只需要改一个地方就行
上面的背景介绍完,你可能会想到这和网络请求封装中间层一样吗?到处都直接使用volley网络框架,后面要升级到okhttp每个地方都要替换,如果有个中间层,直接替换中间层的实现就好一样样的。
1.1 引入依赖:
implementation 'com.google.dagger:dagger:2.4'
annotationProcessor 'com.google.dagger:dagger-compiler:2.4'
dagger2的使用主要涉及4个对象:对象、module、 component、inject
1.2 对象定义:
对象就一个简单的股票对象就行:
public class StockObject {}
1.3 封装module:
使用module和Provides注解,提供对象时创建股票对象:
@Module
public class StockModule {
@Provides
public StockObject providerStockObject(){
return new StockObject();
}
}
1.4 定义component:
组件使用接口,通过component注解指定module:
@Component(modules = {StockModule.class, BrokersModule.class})
public interface TestComponent {
void injectMainActivity2(MainActivity2 activity2);
}
1.5 调用注入代码:
使用注入的注解来标识要注入的对象,然后编译后的component调用注入方法,这个时候对象已经被创建了:
@Inject
StockObject stock;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
// 方式一
DaggerTestComponent.create().injectMainActivity2(this);
Log.i("测试", stock.hashCode() + " MainActivity2");
}
2.实现原理
从上面几个步骤的使用可以看出主要使用了APT技术,通过不同的注解,在编译时生成一些辅类,从1.1的注解处理工具也可以看出实现原理
我们可以看下编译后生成的类如下:
2.1源码查看入口
DaggerTestComponent.create().injectMainActivity2(this);
我们代码里直接在activity调用生成的component的注入方法,有个共性提前说下,生成的代码大部分都提供了create方法,里面其实就创建了生成类对象。
直接上图,关键流程步骤都标注好了,
走到上面第9步,再看下provider的get方法,实现在生成的Factory里面,删除无用代码,主要调用 module.providerStockObject() 这个就是我们自己写的module中创建的真实的对象:
public final class StockModule_ProviderStockObjectFactory implements Factory<StockObject> {
private final StockModule module;
@Override
public StockObject get() {
return Preconditions.checkNotNull(
module.providerStockObject(), "Cannot return null from a non-@Nullable @Provides method");
}
}
上面就是整个注入的源码实现,还是比较清晰的
1.在初始化时,生成组件内部通过建造者模式创建了module对象,调用工厂类传入module生成provider,再把provider给到注入器
2.在使用页面调用生成组件注入当前activity
3.调用注入器的注入成员方法,获取当前activity的注入成员,使用provider的get赋值
4.在provider中,直接获取module的具体提供对象,也就是真实的对象
3.意义和思考(总结)
有意义的地方是:
1.可以拓展自己的技术面,不然看不懂别人的代码无法装逼
2.dagger的设计初衷是解耦合,但是实际使用比较比较难用。如果只是为了不改多个地方实际可以做一个封装,类似他源码实现用工厂模式、中间层 代理等方式来简化。
3.里面比较亮点的地方反而是他的注解处理实现,开发者只需要关注注解使用。和Arouter比较类似,主要目的是简化开发使用,通过APT生成一些帮助类,业务开发不需要关注具体实现,来实现解耦的目的。在架构和封装角度看 还是值得学习的。
总结:
dagger2框架主要是通过注解方式实现依赖注入,调用地方不用自己new 对象,通过APT在编译时候生成代码实现帮助new对象功能,实现解耦。里面涉及基本的对象、module、component、注入四个大的???。module里面封装真实的对象,然后给到组件,组件根据谁注入的帮调用注入的传递基本的对象。类比在家下单点外卖,外卖员把外卖送到家一样。快餐就是基本对象,module就是包装盒,provider就是袋子,外卖员就是component,用户点餐就是注入门牌号。