转载请标明出处https://www.jianshu.com/p/9075ef1bc49a
前言:最近沉迷于学习和回顾Android知识,在dagger2的回顾之中我发现和其他高阶框架有相似的思想。所以开了这篇文章,想要对dagger2的实现原理和基本写法做一个简单的概括。分析也是基于dagger2的2.28版本进行分析。
1.dagger2的前世今生
一开始swankjesse的dagger1先面世。而后由Google接收并开发出了dagger2版本。两者都旨在业务逻辑各部分之间的耦合度降低,提高程序比如说实例化对象的可重用性,释放内存中不再需要的对象,从而提升应用性能。
2.dagger1和dagger2的区别
dagger1的实现是基于反射的。而dagger2是通过APT来实现的,去进行依赖注入。
3.dagger2的基本使用
在开始之前先说明一下dagger2的3个概念,一个是依赖对象,依赖关系集合,依赖诉求。所以注解也大致分为3种:
依赖对象:Provides(标记返回对象的方法)、 Binds(标记抽象方法或接口)
依赖关系集合:module
依赖诉求:Inject
Component(依赖和诉求之间桥梁)。
还有一个作用域注解,下文会提到。
implementation 'com.google.dagger:dagger:2.28'
annotationProcessor 'com.google.dagger:dagger-compiler:2.28'
项目依赖引入。假设想要实例化一个User对象,先用Inject标记。之后编写Module类,用Module标记,里面集合了各种依赖,比如依赖对象的返回方法。最后编写Component将依赖和诉求联系起来。
public class HomeActivity extends Activity {
@Inject
User user;
@Module
public class UserModule {
@Singleton
@Provides
public User getUser() {
return new User("dagger2", "dagger2");
}
}
@Singleton
@Component(modules = UserModule.class)
public interface MainComponent {
void inject(HomeActivity activity);
}
使用可以说是非常简单,这里还用到Singleton,是javax.inject 软件包随附的唯一一个作用域注释。可以用来标记应用中重复使用的对象。这样一来可以业务上一个对象重复使用。
对于dagger2而言,作用域是很重要的概念,官方文档也是花费了大量篇幅在处理作用域上面。因为作用域直接影响了实例化对象是重复使用还是重新创建。因为有的对象创建成本高,且是经常重复使用的,这种最好是能指定作用域到整个应用。而有的对象,用户是希望他是一个始终不同的。这里就关系到对于对象的作用域设定。翻看文档,官方始终是推荐自定义作用域的。上面提到了Singleton软件包里提供的唯一一个作用域注释,通过我们自定义,也能达到我们想要的效果。
并且官方提到,作用域的实现不应该跟目的挂钩,而应该跟生命周期挂钩。因为该注释可以被同级组件多次使用。
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {}
4.dagger2的进阶使用
Dagger2还涉及到关于子组件作用域以及组件化下的依赖操作。
在子组件的实际环境中,Singleton已经不足以开发使用。因为不同的业务逻辑下,对于对象实例化的要求不一样,有时希望对象重复可使用,有时希望对象不断生成新的实例化。
在dagger2中,可以使用subcomponents 注释,由MainComponent负责注入,做到子组件获取父组件作用域,因而可以使用父组件的所有对象。
且Component 是在 Activity 的 onCreate() 方法中创建的,将随着 Activity 的销毁而被隐式销毁。
@Subcomponent
public interface MainComponent {
@Subcomponent.Factory
interface Factory {
MainComponent create();
}
void inject(HomeActivity HomeActivity);
}
@Module(subcomponents = MainComponent.class)
public class SubcomponentsModule {
}
@Singleton
@Component(modules = { SubcomponentsModule.class} )
public interface ApplicationComponent {
MainComponent.Factory MainComponent();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
loginComponent = ((MyApplication) getApplicationContext())
.appComponent.MainComponent().create();
loginComponent.inject(this);
}
这时候我们业务上需要指定对象的作用域,就可以通过创建自定义注释作用域来决定对象的作用域。
//这里也可以是一个ViewModel
@Module
public class UserModule {
//@ApplicationScope,作用域则定于整个APP生命周期
@ActivityScope
@Provides
public User getUser() {
return new User("dagger2", "dagger2");
}
}
@ActivityScope
@Component(modules = UserModule.class)
public interface MainComponent {
void inject(HomeActivity activity);
}
组件化下,父级组件并不知道子级的存在,所以无法像子组件那样去进行SubcomponentsModule 操作。
所以应该使用到dependencies和Factory。
@Component(dependencies = ApplicationComponent.class)
public interface MainComponent {
@Component.Factory
interface Factory {
MainComponent create(ApplicationComponent component);
}
void inject(HomeActivity homeActivity);
}
5.dagger2的原理
那么,dagger2是怎么实现的呢,抱着好奇的心态我点开了APT生成的代码。直接贴上源码,从create方法开始看,发现生成类的create方法走了一个内部builder类的实例化,走了Component类的initialize方法,这里维护了一个Factory工厂类,持有了userModule对象。provider是一个对于Factory的判空方法。当我们去inject的时候,实际上会去这个工厂类进行get,在这个get的过程也进行了一次判空操作。最终走了Injector类的injectUser方法,实现了一个对于activity的对象赋值的操作。
6.dagger2的优劣
dagger的确是可以省去很多实例化代码并且避免代码编写带来的逻辑内存问题。十分解耦。
劣势大概就是代码对新手不友好,真的就是见仁见智了。可以看项目情况使用。
7.题外话
其实对于dagger2的源码阅读难度并不大,dagger这个框架运用上APT预编译,APT在高阶框架上不时就能看到。
IOC :DataBinding,Dagger,Dagger2,Eventbus等。
反射:Eventbus2,Dagger等
APT:Eventbus3,Dagger2,DataBinding等
总结起来会发现,反射在资源消耗性能方面最近是越来越不受人待见,连官方都更倾向于APT直接生成模板代码。java web上的概念也被不断的运用到Android上,Android真的越来越壮大了。(APT感觉是大神必经之路,想要熟练使用,任重而道远。)