Dagger2
概要及前提
Dagger2 是一个依赖注入的框架,通过编译时生成 Java 代码,来进行依赖注入实现。
使用前先需要了解依赖倒置原则及依赖注入这两个概念。依赖倒置原则是设计模式六大原则之一,程序要依赖于抽象接口,高层模块不应该依赖低层模块,就比如汽车依赖于引擎,而引擎不应该依赖于汽车。依赖注入其实是一个很简单且常用的东西,就是将一个对象放进另一个对象里,我们平常用 setter 或构造方法传参将一个对象注入到这个对象,这就是依赖注入。具体看以下文章即可。
依赖倒置原则:blog.csdn.net/zhengzhb/ar… 依赖注入:droidyue.com/blog/2015/0…
若要查看 Dagger2 相关的文档、API,直接访问官网即可:dagger.dev/
相关注解
@Inject:作为依赖提供方使用:在构造函数上,通过标记构造函数让 Dagger2 使用(Dagger2 通过 @Inject 可以在需要这个类实例的时候来找到这个构造函数并把相关实例 new 出来)从而提供依赖;作为依赖需求方:标记在需要依赖的变量 Dagger2 会帮助初始化,被我们使用。
@Moudle:依赖提供方,负责提供依赖中所需要的对象
@Component:依赖注入组件,负责将依赖注入到依赖需求方。
@Provides:会根据返回值类型在有此注解的方法中寻找应调用的方法
简单使用
未使用 Dagger2 ,一般是通过以下方式注入依赖:
public class Car {
public Car() {
//TODO
}
@Override
public String toString() {
return "汽车";
}
}
public class MainActivity extends AppCompatActivity {
private Car mCar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mCar = new Car();
Log.i(MainActivity.class.getName(), mCar.toString());
}
}
使用 Dagger2:
在 Car 类中用 @Inject 修饰需要被调用的构造函数:
public class Car {
@Inject
public Car() {
//
}
@Override
public String toString() {
return "汽车";
}
}
添加一个 Module 类,用于提供依赖中所需要的对象,需要用到 @Module 和 @Provider 注解:
@Module
public class CarModule {
@Provides public Car provideCar() {
return new Car();
}
}
添加一个 Component,表示给哪些类注入哪些对象,比如 MainActivity 类需要用到 Car 对象,那么就可以新建一个接口,在接口上添加注解 @Component,把 CarModule 赋上去,在接口下新增一个注入方法,把需要使用该对象的类作为参数传入进来,当然还可以将其他对象提供出去:
@Component(modules = CarModule.class)
public interface MainComponent {
void inject(MainActivity mainActivity);
}
然后修改 MainActivity,将直接 new 出来的 Car 对象去掉,采用 Dagger2 的方式进行依赖注入。首先在 Car 对象的声明上加上 @inject 注解,注意此处不能声明为 private,因为后面生成代码时要用到:
@Inject
Car mCar;
然后进行 build 生成辅助代码,此时 apt 会生成一个 DaggerMainComponent 类,此时在 MainActivity 通过 Dagger2 将相关的依赖注入:
public class MainActivity extends AppCompatActivity {
@Inject
Car mCar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MainComponent component = DaggerMainComponent.create();
component.inject(this);
Log.i(MainActivity.class.getName(), mCar.toString());
}
}
还有另一种 Component 创建模式,通过 Builder 来创建,在大型项目中这种形式可能会用得多一些,因为这样会更灵活:
MainComponent component = DaggerMainComponent.builder().carModule(new CarModule()).build();
其中,Builder 里的方法来自于 @Component 里的 Module 值,用来指定 Module。
到此,一个简单的使用 Demo 就完成了。其原理就是在 build 的时候生成一系列代码,对依赖进行注入,生成的代码用到了许多设计模式,后续可以深入学习。
通过 @Named 或 @Qulifier 区分同类型依赖
@Named 用于区分获取依赖的方法。从上文可知 Dagger2 寻找依赖是根据返回值进行查找的,当遇到同返回值的不同依赖即可通过 @Named 注解进行获取:
@Module
public class CarModule {
@Named("normal")
@Provides
public Car provideCar() {
return new Car();
}
@Named("blue")
@Provides
public Car provideBlueCar() {
Car car = new Car();
car.setColor(Color.BLUE);
return car;
}
}
在 MainActivity 中在 @Inject 的地方用 @Named 加以区分:
@Inject
@Named("blue")
Car mCar;
即:使用 @Named 标记 Module 中生成类实例的方法,使用 @Named 标记目标类中相应类实例
@Qualifier 可以实现与 @Named 一样的作用,其实 @Named 就是 @Qualifier 使用的一个例子。使用 @Qualifier 需要自己定义注解,用 @Qualifier 注解定义一个 @UserThirdQualifier 注解:
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface UserThirdQualifier {
String value() default "";
}
@Module
public class CarModule {
@UserThirdQualifier("normal")
@Provides
public Car provideCar() {
return new Car();
}
@UserThirdQualifier("blue")
@Provides
public Car provideBlueCar() {
Car car = new Car();
car.setColor(Color.BLUE);
return car;
}
}
在 MainActivity 中用 @UserThirdQualifier 注解区分即可,与 @Named 一模一样:
@Inject
@UserThirdQualifier("blue")
Car mCar;
单例(@Singleton, @Scope)
在 Dagger2 中,用 @Scope 注解的类(也可以注解方法,对应点类)可以在当前 Component 内产生的实例是单例的。并不是只有 @Singleton 注解的类才是单例,而是所有 @Scope 注解的类,看 @Singleton 的代码即可知:
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}
**注意,此处的单例是指这个 Component 的生命周期下。**我们可以自定义作用域的粒度(比如 @PerFragment 指定在 Fragment 的生命周期内等等)。
@Subcomponent
Component 可以被依赖(在 @Component 注解上面传 dependencies 参数即可),@Subcomponent 也是用于管理 Component 间的依赖的。如果需要父组件的全部 Provider,这时我们可以用 @Subcomponent 方式而不是用 dependencies 参数方式,相比于 dependencies 参数方式,@Subcomponent 方式不需要父组件显式显露对象,就可以拿到父组件全部对象,只需要在父 Component 接口中声明拿这个 Subcomponent 的方法即可:
@Component(dependencies = AppComponent.class, modules = ActModule.class)
public interface ActivityComponent {
void inject(DependenceTestActivity DependenceTestActivity);
void inject(SubComponentTestActivity subComponentTestActivity);
//包含SubComponent,这样的话该SubComponent也可以拿到ActivityComponent中能提供的依赖。
ActSubComponent getActSubComponent();
}
@Subcomponent
public interface ActSubComponent {
void inject(SubFragment subFragment);
}
在获取它的地方通过如下方式获取即可:
mActivityComponent = DaggerActivityComponent
.builder()
.appComponent(((App) getApplication()).getAppComponent())
.actModule(new ActModule())
.build();
//获取subcomponent
getActivityComponent().getActSubComponent().inject(this);
**具体关于 @Scope、@Subcomponent 的使用可见:juejin.cn/post/684490… ,上面 Subcomponent 例子来自于此处 **
懒加载注入与强制加载注入(Lazy, Provide)
懒加载 - Lazy:
首先先改写上面的例子
public class MainActivity extends AppCompatActivity {
@Inject
Lazy<Car> mCar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MainComponent component = DaggerMainComponent.create();
component.inject(this);
Car car = mCar.get();
Log.i(MainActivity.class.getName(), car.toString());
}
}
懒加载很好理解,就是 Component 初始化了一个对象,并将这个对象放在一个地方,啥时候要用的时候才去 get,如果是第一次去调用它的 get 方法,它会进行实例化,而后面的调用就会直接获取到第一次 get 进行实例化的那个实例了。这样做可以提高加载速度,比如在 AOSP 中的 SystemUI 项目,其依赖管理就是利用 Dagger2 的 Lazy 注入进行加载和获取的(代码位于 frameworks/base/packages/SystemUI/src/com/android/systemui/Dependency.java)。
强制加载 - Provider:
有时候不仅仅是注入单个实例,而是需要多个实例,那么就可以用 Provider 注入了:
public class MainActivity extends AppCompatActivity {
@Inject
Provider<Car> mCar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MainComponent component = DaggerMainComponent.create();
component.inject(this);
for (int i = 0; i < 10; i++) {
Car car = mCar.get();
Log.i(MainActivity.class.getName(), car.toString());
}
}
}
使用 Provider 注入的对象,每次 get 都会新实例化一个实例出来,而不像 Lazy 那样都是单例。
@Bind 注入接口实现
如果要注入一个接口的实现,一般我们会使用如下方式:
@Provides
public XXInterface providesXX(XXImp imp) {
return imp;
}
但如果用 @Bind,那么就能省下不少代码:
@Binds
public abstract XXInterface bindXX(XXImp imp);
需要注意的是,这个 Module 也需要为 abstract。
总结
Dagger2 是一个对于大型项目来说非常好的依赖注入组件,其通过编译时去生成辅助代码进行依赖注入,能够减少代码量,降低耦合,同时解耦对单元测试是非常友好的,能够提高应用的质量。但是如果是中小型项目我个人认为使用 Dagger2 是没有必要的,这点见仁见智。最初我看到简单使用的时候觉得这个就是多余的,但直到通过了解其他相关的内容以及结合 AOSP 中 SystemUI 的实践,我也慢慢体会到了 Dagger2 的一些魅力。
个人能力有限,若有不合理之处,希望大家能够指出。联系我:me@york1996.com
参考链接汇总: