Dagger2 注解基础
结构
Dagger2 要实现一个完整的依赖注入,必不可少的元素有三种,Module,Component,Container。
Container 就是可以被注入的容器,Container 拥有需要被初始化的元素。需要被初始化的元素必须标上 @Inject,只有被标上 @Inject 的元素才会被自动初始化。被注解的构造方法会自动编译生成一个Factory工厂类提供该类对象。
Module 可以说就是依赖的原材料的制造工厂,所有需要被注入的元素的实现都是从 Module 生产并且封装起来,然后交给注入器 Component。
Component 相当于中间的运输员,将 Module 中产生的依赖对象自动注入到 Container 中。
一些常用的注解
@Inject
- 用 @Inject 注解标注目标类中依赖类的实例对象
- 用 @Inject 注解标注依赖类的构造函数
- 若其他类还依赖于另外的类,则重复进行上面2个步骤
@Component
- 所有的 Component 都必须以接口形式定义,使用 @Component 注解的接口一般直接命名为 XXXComponent
- Dagger2 框架将自动生成该 Component 的实现类,对应的类名是 DaggerXXXComponent
- 添加注入方法,一般使用 inject 做为方法名,方法参数为对应的 Container,调用该方法进行注入,有参数的方法返回值类型是void
- 注入方法也可以没有输入参数,但是必须有返回值,返回的实例会先从事先定义的 Module 中查找,如果找不到,则会使用该类中带 @Inject 的构造方法来生成,同时也会递归注入构造方法的参数以及带 @Inject 的成员变量
@Module
- Module 是一个简单的工厂类,添加注解 @Module 注明本类属于 Module
- Module 里面的方法都是创建相应类实例的方法
- 在 Module 中添加注解 @Provides 注明该方法是用来提供依赖对象的特殊方法
注:一个 Component 可以包含多个 Module ,这样 Component 获取依赖时候会自动从多个 Module 中查找获取,Module 间不能有重复方法。添加多个 Module 有两种方法,一种是在 Component 的注解 @Component(modules={×××.class,×××.class}) 添加多个 modules,另外一种是在可以被使用的 Module 标签中的添加其他 modules: @Module(includes={×××.class,×××.class})
@Provides
- 通过 @Provides 标注该 Module 可以向外界提供的类的实例对象的方法
- Module 中 @Provides 方法可以带输入参数,其参数由 Module 集合中的其他 @Provides 方法的返回值提供
- 如果2中找不到 @Provides 方法提供对应参数的对象,则其会自动查找参数类中带 @Inject 的无参构造生成对象
@Binds
- 使用 @Inject 初始化对象时如果需要初始化的是接口的实例,这时候就需要用到 @Binds
- 方法中必须有且只有一个参数,必须是接口的实现类
- 实现类必须提供 @Inject 的构造或 Module 中以 @Provides 形式提供
- @Binds 相当于是在接口或者抽象类中代替 @Provides
@Named
- @Named 是基于 String 的限定符
- 有两个相同的依赖(都继承某一个父类或者都实现某一个接口)可以提供,这个时候就需要用 @Named 来区分,使用方法为 @Named("AAA"),@Named("BBB")
- 拥有相同 @Named 的 @Inject 成员变量与 @Provides 方法才能被对应起来使用
@Qualifier
- @Qualifier 用来注解接口作为自定义区分标志
- 使用此注解的接口 xxx.java 可以当做注解标签进行使用,@xxx
@Singleton与@Scope
- 顾名思义使用 @Singleton 是用来标注单例对象的
- 在 Component 里的方法添加 @Singleton,同时在 Module 对应的 @Provides 方法上也添加 @Singleton,这样一来每次注入的都会是同一个对象
- @Scope 是注解的注解, Scope机制可以保证在 Scope 标记的 Component 作用域内 ,类会保持单例,@Singleton 是 @Scope 的一个默认实现
@Subcomponent
- @Subcomponent 用于拓展原有 Component,它与 @Component(dependencies=XXComponent.classs) 类似
- @Subcomponent 注解不会在编译后生成Dagger前缀的容器类,而是体现在父 Component 的私有内部类
- Subcomponent 既拥有父 Component 拥有的 Scope,也拥有自己的 Scope
- 在父 Component 中显式添加添加返回 SubComponent 的方法后,继承关系才成立,SubComponent 能自动在父 Component 中查找缺失的依赖
注入方式
Provider与Lazy
Provider和Lazy都是用于包装Container中需要被注入的类型,Lazy用于延迟加载,Provide用于强制重新加载。
Provider保证每次重新加载,但是并不意味着每次返回的对象都是不同的,只有Module的Provide方法每次都创建新实例时,Provider每次get()的对象才不相同。
Lazy != Singleton,如果有两个不同的Lazy对象,那么他们get()的对象也是不同的。
示例:
@Module
final class CounterModule {
int next = 100;
@Provides
Integer provideInteger() {
System.out.println("computing...");
return next++;
}
}
// Provider 注入
final class ProviderCounter {
@Inject Provider provider;
void print() {
System.out.println("printing...");
System.out.println(provider.get());
System.out.println(provider.get());
System.out.println(provider.get());
}
/**
* 打印结果:
* printing...
* computing...
* 100
* computing...
* 101
* computing...
* 102
*/
// Lazy 注入
final class LazyCounter {
@Inject Lazy Lazy;
void print() {
System.out.println("printing...");
System.out.println(Lazy.get());
System.out.println(Lazy.get());
System.out.println(Lazy.get());
}
/**
* 打印结果:
* printing...
* computing...
* 100
* 100
* 100
*/
Dagger2 导航
Android Studio 针对 Dagger2 的导航进行了支持,方便开发者快速回溯依赖关系。
- 点击向上的箭头可以查看该实例注入的提供方
- 点击向下的树形图会转到或展开该实例被用作依赖项的位置或列表
Dagger2 在 SystemUI 里的应用
SystemUI里面大量的模块都使用了此注入方式,比如StatusBar,QS,Keyguard等。
下面以 Android 11 源码的SystemUI来详细说明。
定义清单列表
SystemUI的application标签定义了一个appComponentFactory属性
...
...
这个属性是用来控制manifest清单文件里的组件的初始化,在manifest清单文件里的组件构建对象时会调用下面这些方法。
启动Application
随着SystemServer发出启动SystemUIService的请求,SystemUI的Application将首先被实例化。在实例化之前,指定的AppComponentFactory实现类将会收到回调。
- 调用super得到Application实例之后向其注册Context准备完毕的回调,该回调会执行SystemUIFactory和各组件的初始化:
public class SystemUIAppComponentFactory extends AppComponentFactory {
@Inject
public ContextComponentHelper mComponentHelper;
...
@Override
public Application instantiateApplicationCompat(
@NonNull ClassLoader cl, @NonNull String className)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
Application app = super.instantiateApplicationCompat(cl, className);
if (app instanceof ContextInitializer) {
// 注册Context成功取得的回调
((ContextInitializer) app).setContextAvailableCallback(
context -> {
SystemUIFactory.createFromConfig(context);
// 注入SystemUIAppComponentFactory对象
SystemUIFactory.getInstance().getRootComponent().inject(
SystemUIAppComponentFactory.this);
}
);
}
return app;
}
...
}
- 在SystemUIApplication onCreate时执行上述回调, 构建SystemUIFactory,并对它进行初始化:
public class SystemUIApplication extends Application implements
SystemUIAppComponentFactory.ContextInitializer {
...
@Override
public void setContextAvailableCallback(
SystemUIAppComponentFactory.ContextAvailableCallback callback) {
mContextAvailableCallback = callback;
}
@Override
public void onCreate() {
...
// 执行回调
mContextAvailableCallback.onContextAvailable(this);
// 获取 SystemUIRootComponent,很多组件都是通过它注入的
mRootComponent = SystemUIFactory.getInstance().getRootComponent();
// 通过 SystemUIRootComponent 定义的注入方法获得 ContextComponentHelper 对象
mComponentHelper = mRootComponent.getContextComponentHelper();
...
}
}
- 创建SystemUIFactory实例,并初始化SystemUI 的Dagger组件,并向Dependency实例注入依赖:
public class SystemUIFactory {
...
public static void createFromConfig(Context context) {
...
try {
Class cls = null;
cls = context.getClassLoader().loadClass(clsName);
// 创建SystemUIFactory实例
mFactory = (SystemUIFactory) cls.newInstance();
// 初始化SystemUIFactory
mFactory.init(context);
}
}
private void init(Context context) {
// 取得SystemUI的Dagger组件实例
mRootComponent = buildSystemUIRootComponent(context);
// 创建Dependency实例
Dependency dependency = new Dependency();
// 将Dependency绑定到子组件DependencyInjector中,这里会通过Component将各类依赖注入Dependency
mRootComponent.createDependency().createSystemUI(dependency);
// 初始化Dependency
dependency.start();
}
// 初始化Dagger组件
protected SystemUIRootComponent buildSystemUIRootComponent(Context context) {
return DaggerSystemUIRootComponent.builder()
.dependencyProvider(new DependencyProvider())
.contextHolder(new ContextHolder(context))
.build();
}
...
}
- Dependency里掌管着各式各样的依赖,被依赖的各实例通过Map管理。每个依赖都通过LazyDependencyCreator将各实例对应的懒加载回调缓存进去,然后保存在Map mProviders里。其后在各实例真正使用时调用createDependency() 进行创建:
public class Dependency {
// 使用class作为key将对应实例缓存
private final ArrayMap
启动SystemUIService
创建好Application后,会启动SystemUIService这个主Service,然后逐个启动其他组件。
public class SystemUIService extends Service {
...
@Override
public void onCreate() {
super.onCreate();
// Start all of SystemUI
((SystemUIApplication) getApplication()).startServicesIfNeeded();
...
}
...
}
启动各类服务组件
回到 SystemUIApplication
public class SystemUIApplication {
...
public void startServicesIfNeeded() {
// 各services定义在 R.array.config_systemUIServiceComponents 里面
String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponents(getResources());
startServicesIfNeeded(/* metricsPrefix= */ "StartServices", names);
}
private void startServicesIfNeeded(String metricsPrefix, String[] services) {
...
final int N = services.length;
for (int i = 0; i < N; i++) {
String clsName = services[i];
...
try {
// 通过class name从ContextComponentHelper中获取对应组件的实例
SystemUI obj = mComponentHelper.resolveSystemUI(clsName);
if (obj == null) {
Constructor constructor = Class.forName(clsName).getConstructor(Context.class);
obj = (SystemUI) constructor.newInstance(this);
}
mServices[i] = obj;
} catch (ClassNotFoundException
| NoSuchMethodException
| IllegalAccessException
| InstantiationException
| InvocationTargetException ex) {
throw new RuntimeException(ex);
}
if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
// 调用各组件实现的start()方法
mServices[i].start();
...
}
...
}
}
其中 mComponentHelper 在上一节中有提到,下面讲解一下它的注入过程。
在 SystemUIRootComponent 中定义了 ContextComponentHelper 单例对象的获取方法,
@Singleton
@Component(modules = {
...
SystemUIModule.class,
SystemUIDefaultModule.class})
public interface SystemUIRootComponent {
...
@Singleton
ContextComponentHelper getContextComponentHelper();
...
}
上文dagger2注解基础里有提到,无参有返回值的方法,实例从定义的Module中查找,这里在 SystemUIModule 里可以找到,实际注入的是 ContextComponentResolver 对象,它是 ContextComponentHelper 的实现类:
@Module(includes = {
...
},
subcomponents = {StatusBarComponent.class,
NotificationRowComponent.class,
ExpandableNotificationRowComponent.class})
public abstract class SystemUIModule {
...
@Binds
public abstract ContextComponentHelper bindComponentHelper(
ContextComponentResolver componentHelper);
...
}
由于该方法标了abstract并没有进行实现,所以实例对象会通过 ContextComponentResolver 里面带 @Inject 标签的构造方法来生成。
@Singleton
public class ContextComponentResolver implements ContextComponentHelper {
private final Map, Provider> mActivityCreators;
private final Map, Provider> mServiceCreators;
private final Map, Provider> mSystemUICreators;
private final Map, Provider> mRecentsCreators;
private final Map, Provider> mBroadcastReceiverCreators;
@Inject
ContextComponentResolver(Map, Provider> activityCreators,
Map, Provider> serviceCreators,
Map, Provider> systemUICreators,
Map, Provider> recentsCreators,
Map, Provider> broadcastReceiverCreators) {
mActivityCreators = activityCreators;
mServiceCreators = serviceCreators;
mSystemUICreators = systemUICreators;
mRecentsCreators = recentsCreators;
mBroadcastReceiverCreators = broadcastReceiverCreators;
}
...
/**
* Looks up the SystemUI class name to see if Dagger has an instance of it.
*/
@Override
public SystemUI resolveSystemUI(String className) {
return resolve(className, mSystemUICreators);
}
private T resolve(String className, Map, Provider> creators) {
try {
Class clazz = Class.forName(className);
Provider provider = creators.get(clazz);
return provider == null ? null : provider.get();
} catch (ClassNotFoundException e) {
return null;
}
}
}
这里的构造方法由于带了 @Inject,所以它的参数Map都会一起注入进去,那这些Map又是哪里来的呢,这里提一下dagger2的 MultiBindings。
首先在 Module 中多次定义一系列返回值类型相同的方法,这里以 SystemUIBinder 为例,如果需要的类型是Map,那么就使用 @IntoMap 和 对应key的 @MapKey 的实现 @ClassKey,@IntoMap 表示这些方法的返回值对象都要装入同一个Map集合中,@ClassKey 表示Map的key为class。
然后还必须加上 @Provides 标签,如果是接口或抽象类则用 @Binds。这样声明下来,dagger2 会自动为其注入 Map
/**
* SystemUI objects that are injectable should go here.
*/
@Module(includes = {RecentsModule.class, StatusBarModule.class, BubbleModule.class,
KeyguardModule.class})
public abstract class SystemUIBinder {
/** Inject into AuthController. */
@Binds
@IntoMap
@ClassKey(AuthController.class)
public abstract SystemUI bindAuthController(AuthController service);
/** Inject into Divider. */
@Binds
@IntoMap
@ClassKey(Divider.class)
public abstract SystemUI bindDivider(Divider sysui);
...
/** Inject into StatusBar. */
@Binds
@IntoMap
@ClassKey(StatusBar.class)
public abstract SystemUI bindsStatusBar(StatusBar sysui);
...
}
以 bindsStatusBar 为例,参数 StatusBar 的实例又是由 StatusBarPhoneModule 提供的
/**
* Dagger Module providing {@link StatusBar}.
*/
@Module(includes = {StatusBarPhoneDependenciesModule.class})
public interface StatusBarPhoneModule {
/**
* Provides our instance of StatusBar which is considered optional.
*/
@Provides
@Singleton
static StatusBar provideStatusBar(
Context context,
NotificationsController notificationsController,
LightBarController lightBarController,
AutoHideController autoHideController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
StatusBarIconController statusBarIconController,
...) {
return new StatusBar(
context,
notificationsController,
lightBarController,
autoHideController,
keyguardUpdateMonitor,
statusBarIconController,
...);
}
}
new StatusBar 对象所用到的参数,有些是通过内部自身带 @Inject 标签的构造方法注入,有些则是又再通过其他 Module 提供,这里就不再往下继续跟踪了。
到此,config_systemUIServiceComponents 里面定义的组件的实例对象注入过程就结束了。
总结
由于 SystemUI 模块内大部分结构都已经用上了 Dagger2 框架,本文旨在通过对 Dagger2 框架进行一些基础概念的总结,结合 SystemUI 部分的源码来了解其工作原理,这样在后续解读代码以及添加需求功能的时候可以用上。