技术标签: dagger 笔记 依赖注入 android 详解 Android
0. 前言
1. 依赖与注入
2. @Inject
3. @Module & @Provides
4. @Component
5. @Qualifier
6. Provider & Lazy
7. @Scope
8. 注入到Set和Map容器
9. Bind系列注解
10. dagger中依赖关系与继承关系
11. dagger.android
我们回顾一下目前为止的代码,我们通过ApplicationModule
数据仓库,提供应用名数据,而Activity
和Computer
为了拿到这个数据,是通过定义@Subcomponent
,使其与ApplicationComponent
称为类似内部类组合的关系,从而可以使用到ApplicationModule
中的内容。在Android系统中,Activity
、Fragment
都是由系统创建的,我们无法通过dagger管理其构造函数而仅能将注入语句插入到其生命周期中,并且每多出一个Activity
,我们都要在ApplicationComponent
中针对性的添加一个返回Subcomponent.Builder
的方法,最严重的是,Activity
在使用时需要知道使用的Component
的具体类型,这违反了被注入类不应该知道注入细节这个原则
之前用的显示Computer
信息那个项目太混乱了,我们这里重新创建一个应用模块,目前的需求如下:
Activity
(OneActivity
与TwoActivity
),我们需要指定其标题、背景色和一段文本内容Activity
有其各自的标题,但背景色需要统一,文本内容对应着一个全局单例的data
类就这两点需求,我们使用dagger来注入这些内容,其中统一的数据由ApplicationModule
提供,而各自的数据由各自的Module
提供:
我们先编写data
类和那些@Module
:
// data类相当简单,其内部由一个String和一个Int数据组成
data class VirtualData(private val name: String, private val num: Int)
// ActivityModule仅提供各自Activity自己的数据
@Module
class OneActivityModule {
@Qualifier annotation class Title // 对于基本类型、String和一些共享性较强的数据类型,通常习惯定义别名,以免造成冲突以及增加语义性
@[Provides Title] fun provideTitle() = "One Activity"
}
@Module
class TwoActivityModule {
@Qualifier annotation class Title
@[Provides Title] fun provideTitle() = "Two Activity"
}
// ApplicationModule中提供统一数据
@Module
class ApplicationModule {
@Qualifier annotation class ActivityColor
@[Provides ColorInt] fun provideActivityColor(@[ActivityColor ColorInt] color: Int) = color // @ColorInt是androidx中的注解,与dagger无关,这里仅为增强语义
@Qualifier annotation class ActivityData
@[Provides Singleton ActivityData] fun provideActivityData() = VirtualData(Date().toString(), Random.nextInt()) // Application生命周期即应用声明周期,就直接使用了@Singletion注解;为了证明其单例,data类中使用的为随机数据
}
通过@Module
我们准备好了数据仓库,接下来我们通过@Inject
在Activity
中将需要注入的数据打上标记:
class OneActivity : AppCompatActivity() {
@filed:[Inject OneActivityModule.Title] lateinit var title: String
// kotlin对于基本数据类型的成员变量,其默认都是使用private+get/set方法,这里就必须使用set和setparam指定注解目标了(其实也可以使用@JvmField注解,不过会丢失get/set方法)
@set:Inject
@setparam:ApplicationModule.ActivityColor
var color: Int = 0 // kotlin中的基本数据类型是不能用lateinit关键字的,使用字面量指定初始值时需要注意数据合法性
@filed:[Inject ApplicationModule.ActivityData] lateinit var data: VirtualData
/* ... */
}
// TwoActivity类似,这里就不贴出来了
编译后,通过@Inject
我们得到了注入器(MembersInjector
),接下来就是编写各种@Component
桥接类,将数据与注入器结合起来了:
@Subcomponent(modules = [OneActivityModule::class]) // Activity所需的数据并不能通过此Component建立完整依赖关系,所以使用@Subcomponent等待父Component辅助使其依赖完整
interface OneActivityComponent {
fun inject(target: OneActivity)
@Subcomponent.Builder // 使用@Subcomponent必须要将自定义Component构造器
interface Builder {
fun build(): OneActivityComponent
}
}
// TwoActivityComponent类似,这里就不贴出来了
// 别忘了将两个子Component加入到父Component的module依赖的subcomponents中,以便父Component能够访问到子Component的Builder构造器,以及让子Component能够共享父Component的Module依赖
@Module(subcomponents = [OneActivityComponent::class, TwoActivityComponent::class])
class ApplicationModule {
/* ... */ }
// ApplicationComponent主要提供ActivityComponent.Builder构造器,另外还需要将Module中的依赖补充完整
@Singleton // 别忘了ApplicationModule中的数据需要@Singleton作用域
@Component(modules = [ApplicationModule::class])
interface ApplicationComponent {
fun newOneActivityComponentBuilder(): OneActivityComponent.Builder
fun newTwoActivityComponentBuilder(): TwoActivityComponent.Builder
@Component.Builder
interface Builder {
@BindsInstance // 使用@BindsInstance使得ApplicationModule中的@ActivityColor依赖完整
fun activityColor(@[ApplicationModule.ActivityColor ColorInt] color: Int): Builder
fun build(): ApplicationComponent
}
}
编译通过后我们就可以尽情的使用dagger帮我们生成的Component
类了:
// Application中准备好ApplicationComponent,以便Activity可以访问到
class Case11Application : Application() {
private lateinit var _component: ApplicationComponent
val component get() = _component
override fun onCreate() {
super.onCreate()
_component = DaggerApplicationComponent
.builder()
.activityColor(Color.CYAN)
.build()
}
}
// Activity中通过访问Application拿到自己的Component.Builder
class OneActivity : AppCompatActivity() {
/* ... */
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_one)
(application as Case11Application)
.component
.newOneActivityComponentBuilder()
.build()
.inject(this)
setTitle(title)
container.setBackgroundColor(color)
content.text = data.toString()
}
}
运行一下,代码逻辑完全没有问题,该是单例的就是单例,两个页面的标题也不同。虽然目的是达到了,但我们假设下如果随着需求的增多,有几十上百个这样的Activity
时会怎样?
Activity
对应的Subomponent.Builder
和ApplicationComponent
中Activity
都需要重复那么一长段代码,随着项目的迭代和新人的接手,这段代码将变得容易出错且难以理解Activity
都需要知道自己要调用什么样的方法才能得到自己的Component
(比如上面的newOneActivityComponentBuilder()
方法),但被注入对象不应该知道任何被注入的细节(最少知道原则)为了解决这种冗余和重复代码,通常解决途径都是抽象和统一管理,于是乎dagger.android
扩展库就由此产生了
官方将dagger.android
的使用过程分成5步
implementation 'com.google.dagger:dagger-android:2.22.1'
implementation 'com.google.dagger:dagger-android-support:2.22.1' // 适用于使用了support包的应用
kapt 'com.google.dagger:dagger-android-processor:2.22.1' // 别忘了kotlin中使用的是kapt
AndroidInjectionModule
是dagger.android
框架提供的Module
,官方要求我们将其添加到ApplicationComponent
中:
@Singleton
@Component(modules = [ApplicationModule::class, AndroidInjectionModule::class])
interface ApplicationComponent {
/* ... */ }
也就是我们之前写的ActivityComponent
,不过dagger.android
要求其继承AndroidInjector
接口,并且其内部需要有一个继承了AndroidInjector.Factory
的@Subcomponent.Factory
接口:
@Subcomponent(modules = [OneActivityModule::class])
interface OneActivityComponent: AndroidInjector<OneActivity> {
@Subcomponent.Factory
interface Factory: AndroidInjector.Factory<OneActivity>
}
// TwoActivityComponent修改类似,不再贴出代码
这里对比之前有几点注意:
AndroidInjector
和AndroidInjector.Factory
都需要指定泛型,类型为Component
对应的被注入对象@Subcomponent
接口中写inject()
方法了Factory
与Builder
不能混用,因此这里删去@Subcomponent.Builder
Factory
中也不需要(且不能够)定义其他方法了这一步不仅要在ApplicationModule
的@Module.subcomponents
中添加上一步的@Subcomponent
,还需要添加一个较为复杂的抽象方法;之前文章说过**@Module
中不能同时有抽象方法和非静态方法**,所以这里我专门为这种@Subcomponent
新建一个抽象@Module
,并引入到ApplicationComponent
中:
// 将Subcomponents转移到新的抽象Module上
@Module
class ApplicationModule {
/* ... */ }
@Module(subcomponents = [OneActivityComponent::class, TwoActivityComponent::class])
abstract class ActivityFactoryModule {
@[Binds IntoMap ClassKey(OneActivity::class)] // @Binds是一种用于决定抽象类型的依赖具体实现类型的方案,可以看下之前的文章有介绍到
abstract fun bindOneActivityInjectorFactory(
factory: OneActivityComponent.Factory
): AndroidInjector.Factory<*>
@[Binds IntoMap ClassKey(TwoActivity::class)]
abstract fun bindTwoActivityInjectorFactory(
factory: TwoActivityComponent.Factory
): AndroidInjector.Factory<*>
}
// 在ApplicationComponent中引入上面新的抽象Module
@Singleton
@Component(modules = [ApplicationModule::class, AndroidInjectionModule::class, ActivityFactoryModule::class])
interface ApplicationComponent {
// 这两个方法不再需要
// fun newOneActivityComponentBuilder(): OneActivityComponent.Builder
// fun newTwoActivityComponentBuilder(): TwoActivityComponent.Builder
fun inject(target: Case11Application) // 我们需要对Application注入一些依赖
/* ... */
}
class Case11Application : Application(), HasActivityInjector {
@field:Inject
lateinit var activityInjectorDispatcher: DispatchingAndroidInjector<Activity> // 新增dagger注入对象
// 不再需要这个属性
// private lateinit var _component: ApplicationComponent
// val component get() = _component
override fun onCreate() {
super.onCreate()
DaggerApplicationComponent
.builder()
.activityColor(Color.CYAN)
.build()
.inject(this) // 需要注入
}
override fun activityInjector(): AndroidInjector<Activity> = activityInjectorDispatcher // HasActivityInjector接口要求实现的方法
}
// Activity.onCreate()方法
override fun onCreate(savedInstanceState: Bundle?) {
/* ... */
// (application as Case11Application)
// .component
// .newTwoActivityComponentBuilder()
// .build()
// .inject(this)
AndroidInjection.inject(this)
/* ... */
}
没错非常精简且经典的一个方法
我们先不慌分析其实现源码,从修改结果上,我们看下解决了什么问题,有带来了什么问题:
@Subcomponent.Builder
写入ApplicationComponent
中了,取而代之的,我们需要以一种特定的方式将@Subcomponent.Factory
写入ApplicationComponent
依赖的@Module
中(似乎更复杂了?)Activity
中不必再重复那样的模板代码,无论在哪个Activity
中都是相同的一句AndroidInjection.inject(this)
@Subcomponent
和@Component
的改动都是向着简化的方面进行整体上来说代码确实简化了不少,最重要的是,Activity
不再需要知道注入细节了!接下来就看看其源码实现怎样的
我相信大部分人看到上面用到的dagger.android
中的类名、接口名、@IntoMap
等就能猜到个大概了,大致就是通过Map
容器收集各Component.Factory
,通过AndroidInjection
访问到这个Map
容器,之后进行Factory
的派发与注入,从而实现Activity
与Component
桥接类的构建器解耦;同时通过接口和抽象类规范化使用流程,顺便起到简化编码的作用
下面我们从第一步开始慢慢分析
最开始我们将AndroidInjectionModule
写到ApplicationComponent
的modules
依赖中,其源码如下:
@Module
public abstract class AndroidInjectionModule {
@Multibinds abstract Map<Class<?>, AndroidInjector.Factory<?>> classKeyedInjectorFactories();
@Multibinds abstract Map<String, AndroidInjector.Factory<?>> stringKeyedInjectorFactories();
private AndroidInjectionModule() {
}
}
这是一个抽象@Module
,其用@Multibinds
提供了两种类型的Map
数据,之前文章说过@Multibinds
是为了解决在编码时不能确定容器内是否有元素被注入的问题,即为了依赖完整性可以提供大小为0的容器(不熟悉的可以翻下前面文章看下)
Q1:这两种类型的
Map
容器在哪里被使用到呢?
第二步中,我们修改了@Subcomponent
注解的接口,使其继承自AndroidInjector
,并且其内部的@Subcomponent.Factory
注解的接口也需要继承自AndroidInjector.Factory
,看下这两个的源码:
public interface AndroidInjector<T> {
void inject(T instance);
interface Factory<T> {
AndroidInjector<T> create(@BindsInstance T instance);
}
@Deprecated // 在Factory出现前都是用的Builder,不过现在已经被弃用了
abstract class Builder<T> implements AndroidInjector.Factory<T> {
/* ... */
}
}
难怪我们不用再在@Subcomponent
中手写inject()
方法,并且@Subcomponent.Factory
中必须有的唯一一个方法,也被这个接口规范化了
Q2:虽然这样规范化大幅度简化了开发者的编码,但当我们需要自定义Builder对象时怎么办呢?(比如需要使用上
@BindsInstance
)
在第三步中我们在新建的抽象@Module
中用到了这三个注解,这三个注解也在之前文章都有介绍过。在dagger.android
中将三者结合使用,不得不说学到了新姿势。我们重新看下我们编写的代码:
@[Binds IntoMap ClassKey(OneActivity::class)]
abstract fun bindOneActivityInjectorFactory(
factory: OneActivityComponent.Factory
): AndroidInjector.Factory<*>
@Binds
用于注入抽象类时指明具体的实现类;@IntoMap
和@ClassKey
用于指明Map
容器注入,并且是Map<Class<*>, AndroidInjector.Factory<*>>
这样的容器。这个容器是不是很熟悉?没错,就是AndroidInjectionModule
中定义的Map
容器,但其中还定义了一个Map<String, AndroidInjector.Factory<*>>
容器,由此可得出上面的@ClassKey
可以换成@StringKey
Q3:如果将
@ClassKey
换成@StringKey
,应该怎样写?(自定义一个String
吗?)
在第三步中我们还去掉了ApplicationComponent
中fun newOneActivityComponentBuilder(): OneActivityComponent.Builder
这样的代码,并不是说不需要@Subcomponent.Builder
了,而是dagger.android
将其注入到了上面的Map
容器中去了
第四步中我们让Application
实现了HasActivityInjector
接口,其定义如下:
public interface HasActivityInjector {
AndroidInjector<Activity> activityInjector();
}
接口虽然简单,但问题变得更多了
Q4:
HasActivityInjector
有什么作用?为什么其要定义一个返回AndroidInjector<Activity>
的方法?
同样在第四步中,我们还在Application
添加了如下代码:
@field:Inject
lateinit var activityInjectorDispatcher: DispatchingAndroidInjector<Activity>
在变量上添加了@Inject
代表这个变量是要需要被注入的,但我们在ApplicationComponent
所依赖的modules
中并没有看到提供这种数据的方法,怎么回事?赶快点开源码看看:
public final class DispatchingAndroidInjector<T> implements AndroidInjector<T> {
/* ... */
private final Map<String, Provider<AndroidInjector.Factory<?>>> injectorFactories; // 两个map合并后的map
@Inject
DispatchingAndroidInjector(
Map<Class<?>, Provider<AndroidInjector.Factory<?>>> injectorFactoriesWithClassKeys,
Map<String, Provider<AndroidInjector.Factory<?>>> injectorFactoriesWithStringKeys) {
this.injectorFactories = merge(injectorFactoriesWithClassKeys, injectorFactoriesWithStringKeys);
}
private static <C, V> Map<String, Provider<AndroidInjector.Factory<?>>> merge(
Map<Class<? extends C>, V> classKeyedMap, Map<String, V> stringKeyedMap) {
/* 合并两个map,非重要部分 */ }
@CanIgnoreReturnValue
public boolean maybeInject(T instance) {
Provider<AndroidInjector.Factory<?>> factoryProvider =
injectorFactories.get(instance.getClass().getName()); // 寻找真正的AndroidInjector
if (factoryProvider == null) {
return false;
}
@SuppressWarnings("unchecked")
AndroidInjector.Factory<T> factory = (AndroidInjector.Factory<T>) factoryProvider.get();
try {
AndroidInjector<T> injector =
checkNotNull(factory.create(instance), "%s.create(I) should not return null.", factory.getClass());
injector.inject(instance);
return true;
} catch (ClassCastException e) {
/* 抛出异常,非重要部分 */ }
}
@Override
public void inject(T instance) {
boolean wasInjected = maybeInject(instance);
if (!wasInjected) {
/* 注入失败抛异常,非重要部分 */ }
}
/* 异常与报错等,非重要部分 */
}
首先我们注意DispatchingAndroidInjector
的构造函数上有@Inject
,说明dagger可以通过此构造函数提供对象,所以不用写到@Module
内;@Inject
注解的构造函数中的参数也是被看做为依赖,刚好与AndroidInjectionModule
中@Multibinds
定义的两个Map
容器类型相同(注意这里用了dagger的Provider
封装了AndroidInjector.Factory<?>
,dagger对于Provider
和Lazy
都有自动转换依赖的能力),这里也就回答了Q1问题
其次我们发现DispatchingAndroidInjector
实现了AndroidInjector
接口,没错,就和我们修改@Subcomponent
实现的接口是同一个,其inject()
方法的实现是通过入参的全类名(即包含包名)去搜寻Map
从而找到真正的AndroidInjector
注入器,正如其名字一样,是一个对注入器的分发类。这里我们可以解答Q3问题:第三步中通过@StringKey
规定注入Key值的话,其需要写上对应的全类名(这里注意下,使用@StringKey
需要注意混淆问题)
最后,我们在Activity
中通过AndroidInjection.inject(this)
就能完成注入,这显然是一个静态方法,也能想到会调用到上面我们分析的DispatchingAndroidInjector
,来看下源码是怎样调用的吧:
public final class AndroidInjection {
private static final String TAG = "dagger.android";
public static void inject(Activity activity) {
checkNotNull(activity, "activity");
Application application = activity.getApplication();
if (!(application instanceof HasActivityInjector)) {
throw new RuntimeException( /* ... */ );
}
AndroidInjector<Activity> activityInjector =
((HasActivityInjector) application).activityInjector();
/* ... */
activityInjector.inject(activity);
}
public static void inject(Fragment fragment) {
checkNotNull(fragment, "fragment");
HasFragmentInjector hasFragmentInjector = findHasFragmentInjector(fragment);
/* ... */
AndroidInjector<Fragment> fragmentInjector = hasFragmentInjector.fragmentInjector();
/* ... */
fragmentInjector.inject(fragment);
}
private static HasFragmentInjector findHasFragmentInjector(Fragment fragment) {
// 寻找fragment的注入器
Fragment parentFragment = fragment;
while ((parentFragment = parentFragment.getParentFragment()) != null) {
// 先从parentFragment中搜索
if (parentFragment instanceof HasFragmentInjector) {
return (HasFragmentInjector) parentFragment;
}
}
Activity activity = fragment.getActivity();
if (activity instanceof HasFragmentInjector) {
// 再从宿主Activity中搜索
return (HasFragmentInjector) activity;
}
if (activity.getApplication() instanceof HasFragmentInjector) {
// 最后通过Application搜索
return (HasFragmentInjector) activity.getApplication();
}
/* ... */
}
public static void inject(Service service) {
/* ... */
Application application = service.getApplication();
if (!(application instanceof HasServiceInjector)) {
/* ... */ }
AndroidInjector<Service> serviceInjector = ((HasServiceInjector) application).serviceInjector();
/* ... */
serviceInjector.inject(service);
}
public static void inject(BroadcastReceiver broadcastReceiver, Context context) {
/* ... */
Application application = (Application) context.getApplicationContext();
if (!(application instanceof HasBroadcastReceiverInjector)) {
/* ... */ }
AndroidInjector<BroadcastReceiver> broadcastReceiverInjector =
((HasBroadcastReceiverInjector) application).broadcastReceiverInjector();
/* ... */
broadcastReceiverInjector.inject(broadcastReceiver);
}
public static void inject(ContentProvider contentProvider) {
/* ... */
Application application = (Application) contentProvider.getContext().getApplicationContext();
if (!(application instanceof HasContentProviderInjector)) {
/* ... */ }
AndroidInjector<ContentProvider> contentProviderInjector =
((HasContentProviderInjector) application).contentProviderInjector();
/* ... */
contentProviderInjector.inject(contentProvider);
}
private AndroidInjection() {
}
}
final
类、私有构造函数、全是静态方法,充分说明AndroidInjection
就是一个工具类,其对inject()
有5个重载方法,分别针对于Activity
、Fragment
、Service
、BroadcastReceiver
、ContentProvider
进行注入,方法内的流程也都类似:首先判断Application
是否有实现HasXXXInjector
接口,然后通过接口指定的xxxInjector
方法拿到第四步中提到的DispatchingAndroidInjector
进行注入(这里回答了Q4问题)。这里说下几个特殊处理:
Fragment
的注入,因为Fragment
的灵活性,导致Fragment
(嵌套)、Activity
或Application
都能作为其“容器”,自然这些都能管理Fragment
的注入器;因此先通过parentFragment
、再通过Activity
、最后通过Application
去寻找HasFragmentInjector
。由此,如果一个Fragment
没有“容器”,那么对其进行AndroidInjection.inject()
注入是注定会失败的BroadcastReceiver
是没有对应的Context
的(但其onReceive()
回调中会传入Context
,这个Context
会因广播是静态或动态、本地或非本地而不同,这里不深入探讨),因此对BroadcastReceiver
进行AndroidInjection.inject()
是要手动传入Context
对象的我们知道@Subcomponent.Factory
中有且仅能有唯一一个方法代表@Subcomponent
的工厂方法,但是dagger.android
中AndroidInjector.Factory
甚至已经将这个方法规范化了,因为dagger.android
需要统一管理这些Factory
,那么我们怎么做到像之前文章介绍@Component.Builder
一样自定义这个Builder/工厂类呢?就目前来看,答案是不能,我们从两个角度来思考为什么不能:
@Component.Builder
构建器:
@Component.modules
、@Component.dependencies
指定set系列方法,首先@Subcomponent
是没有dependencies
属性的,其次如果不使用@Component.Builder
dagger也会创建默认的Builder
,所以我认为这不是主要原因dagger.android
之前一样,将ActivityComponent
的构建器都交由ApplicationComponent
管理@BindsInstance
使依赖树完整,有些依赖我们想在运行时决定,于是需要给构建器留出定义接口,以便能在运行时动态传入这些参数dagger.android
:
dagger.android
框架一方面就是为了解决上述第二点带来的问题,我们不想每一个Activity
中都有着意义不明、仅有细微差别的代码dagger.android
前的代码吗?被注入对象不应该知道任何被注入的细节,这是依赖注入的核心原则。回想下我们使用@BindsInstance
时,对于被注入对象,不仅需要知道自己对应的Component
桥接类类型,还得调用@BindsInstance
注解的方法以绑定实例,这样甚至就和使用dagger框架前的代码一样,实在是知道得太多了那么如果真的遇到这种需求时怎么办呢?(比如@Module
所提供的依赖就是不完整的)其实解决方案有很多,要么直接写死在@Module
中或者将判断逻辑迁移到@Module
中,要么就将这种数据交由“容器”保管(就像上面例子中的@ActivityColor
一样),或者对于可能出现的数据使用dagger.Lazy
注入以免无用内存的产生,再不然就不要使用dagger框架了,正所谓好钢要用在刀刃上,不适合依赖注入的数据为何还要强行让其配合依赖注入框架呢?
源码分析完后是不是豁然开朗了?其实和我们最初想法即猜测是吻合的,Activity
的子Subomponent
的构建器还是由Application
管理着的,不过dagger.android
将这些构建器以全类名作为Key值其注入到Map
容器中,之后Activity
调用注入时,dagger.android
就从Map
容器中通过类名查找到对应的构建器,构建出Component
调用其注入方法完成注入,这样Activity
就可以实现完全不知道注入细节的依赖注入了
其实就是通过AndroidInjection
实现解耦,至于解耦的目的,也是本文一直强调的被注入对象不应该知道任何被注入的细节
接下来就是dagger.android
为了极致简化开发者编写代码而推出的各种辅助注解和辅助类了
在第二步和第三步中,我们将Activity
的@Subcomponent
进行了一番改造,并在ApplicationComponent
依赖的@Component.modules
中添加了@Binds @IntoMap @ClassKey ...
这种代码,其实对于大多数Activity
来说都是这样的模板代码。因此dagger.android
针对性的提供了@ContributesAndroidInjector
注解,这个注解会辅助生成一个@Subcomponent
以及一个使用此@Subcomponent
的抽象@Module
,我们改动一下ActivityFactoryModule
:
@Module(subcomponents = [/*OneActivityComponent::class, */TwoActivityComponent::class]) // 不再需要手动编写@Subcomponent
abstract class ActivityFactoryModule {
@ContributesAndroidInjector(modules = [OneActivityModule::class]) // @ContributesAndroidInjector一定是在@Module中注解一个无参方法,其modules属性和我们手写的@Subcomponent.modules等价
abstract fun contributeOneActivityInjector(): OneActivity // 这个方法的返回值表示被@Subcomponent桥接的被注入对象
// @[Binds IntoMap ClassKey(OneActivity::class)] // 不再需要手动编写这个函数了
// abstract fun bindOneActivityInjectorFactory(
// factory: OneActivityComponent.Factory
// ): AndroidInjector.Factory<*>
}
编译一下,你会发现由@ContributesAndroidInjector
生成了一个新的辅助类:
@Module(subcomponents = ActivityFactoryModule_ContributeOneActivityInjector.OneActivitySubcomponent.class) // 这个@Module可以被认为是默认添加到了ActivityFactoryModule的@Module.includes中
public abstract class ActivityFactoryModule_ContributeOneActivityInjector {
private ActivityFactoryModule_ContributeOneActivityInjector() {
}
@Binds
@IntoMap
@ClassKey(OneActivity.class)
abstract AndroidInjector.Factory<?> bindAndroidInjectorFactory(
OneActivitySubcomponent.Factory builder);
@Subcomponent(modules = OneActivityModule.class)
public interface OneActivitySubcomponent extends AndroidInjector<OneActivity> {
@Subcomponent.Factory
interface Factory extends AndroidInjector.Factory<OneActivity> {
}
}
}
很直接,就和我们手写的一模一样。当然,@ContributesAndroidInjector
注解还能使用在服务、广播等上,由其注解的方法返回值决定生成的@Subcomponent
的桥接对象
你有没有想过,我们甚至连AndroidInjection.inject(this)
这一句也不用手写?没错,只需编写一个BaseActivity
在其onCreate()
加入这句,其他Activity
继承自BaseActivity
就好了。dagger.android
也想到了这一点,于是提供了DaggerActivity
/DaggerAppCompoatActivity
,修改我们的Activity
如下:
class OneActivity : DaggerAppCompatActivity() {
/* ... */
override fun onCreate(savedInstanceState: Bundle?) {
/* ... */
// AndroidInjection.inject(this) // 这句已经不需要了
}
}
来看下其源码(这里以DaggerAppCompatActivity
举例,DaggerActivity
类似):
public abstract class DaggerAppCompatActivity extends AppCompatActivity
implements HasFragmentInjector, HasSupportFragmentInjector {
@Inject DispatchingAndroidInjector<Fragment> supportFragmentInjector;
@Inject DispatchingAndroidInjector<android.app.Fragment> frameworkFragmentInjector;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
AndroidInjection.inject(this); // dagger推荐依赖注入在生命周期中应该越早越好
super.onCreate(savedInstanceState);
}
@Override
public AndroidInjector<Fragment> supportFragmentInjector() {
return supportFragmentInjector;
}
@Override
public AndroidInjector<android.app.Fragment> fragmentInjector() {
return frameworkFragmentInjector;
}
}
没错,DaggerAppCompatActivity
不仅为你写好了AndroidInjection.inject(this)
,还贴心的为你将HasFragmentInjector
/HasSupportFragmentInjector
接口实现好了,极大方便了Fragment
的注入。类似的,dagger.android
还提供了DaggerBroadcastReceiver
、DaggerContentProvider
、DaggerDialogFragment
、DaggerFragment
、DaggerIntentService
和DaggerService
以在不同场景使用,任君挑选
和DaggerActivity
目的一样,dagger.android
自然也提供了DaggerApplication
,修改我们的Application
:
@Singleton
@Component(modules = [ApplicationModule::class, AndroidInjectionModule::class, ActivityFactoryModule::class])
interface ApplicationComponent : AndroidInjector<Case11Application> {
// 需要继承AndroidInjector接口
// fun inject(target: Case11Application) // 于是省去了inject()方法
/* ... */
}
class Case11Application : DaggerApplication() {
// 继承自DaggerApplication,于是我们只用实现applicationInjector将ApplicationComponent构建好后返回即可
override fun applicationInjector(): AndroidInjector<out DaggerApplication> =
DaggerApplicationComponent
.builder()
.activityColor(Color.CYAN)
.build()
}
来来来,看下你猜的DaggerApplication
源码对不对:
@Beta
public abstract class DaggerApplication extends Application
implements HasActivityInjector, HasFragmentInjector, HasServiceInjector, HasBroadcastReceiverInjector, HasContentProviderInjector {
// 实现了一系列的接口,以支持各种场景下的注入
// 自然这些派发器也是@Inject进来的
@Inject DispatchingAndroidInjector<Activity> activityInjector;
@Inject DispatchingAndroidInjector<BroadcastReceiver> broadcastReceiverInjector;
@Inject DispatchingAndroidInjector<Fragment> fragmentInjector;
@Inject DispatchingAndroidInjector<Service> serviceInjector;
@Inject DispatchingAndroidInjector<ContentProvider> contentProviderInjector;
@Override
public void onCreate() {
super.onCreate();
injectIfNecessary();
@ForOverride
protected abstract AndroidInjector<? extends DaggerApplication> applicationInjector(); // 我们需要重写的那个方法
private void injectIfNecessary() {
/* 去掉其他关于同步的部分,关键是下面这两句,在Application.onCreate()时进行注入 */
AndroidInjector<DaggerApplication> applicationInjector = (AndroidInjector<DaggerApplication>) applicationInjector();
applicationInjector.inject(this);
}
/* 剩下都是一些实现接口的方法,比如activityInjector()等 */
}
至此,总算把dagger所有内容都完全详细的介绍了一遍,从最开始手写依赖注入,到引入dagger实现依赖注入,再到最后使用dagger.android
完成在Android中的依赖注入,我也从最开始看着dagger就头疼放弃了500遍后,终于下定决心自己深入学习一趟,边学边编写此系列,陆陆续续将近半个月,总算是画了个句号,也是对dagger框架和依赖注入有着更深的认识了
本系列肯定有各种不尽人意的地方,特别严重的就是前面大部分文章都错误理解了@Module
的定位,不过个人认为作为学习笔记已经很够看了,这方面还请海涵
其实最开始看到dagger已经是很久之前了,写本系列时也忘记刻意记下参考文章orz,我只能翻查浏览器记录希望能补全吧:
Dagger 官方文档 - 最不推荐看的文档了= =恕我直言,这个官方指引对新手来说就是一坨[哔——]
Dagger2 最清晰的使用教程 - 看得最久的一篇文章,不过我看的感觉和文章开头差不多,也是脑浆炸裂
Dagger 2 完全解析(一),Dagger 2 的基本使用与原理 - 这个系列还不错,能够理解什么是依赖关系,什么是继承关系,最重要的是最后一篇是用kotlin写的,好评如潮
广播onReceive()方法的context类型探究 - 关于广播接收器中的Context
究竟是什么
Dagger2 依赖的接力游戏(六):Bind、BindInstance、Multibinds注解 - 主要是学习了Bind系列注解,讲得蛮好的
Dagger 2 multibindings with Kotlin - dagger在kotlin中出现泛型问题?先试试@JvmSuppressWildcards
吧
DI框架Dagger2系统性学习-不容错过的干货 - 官方文档的翻译,没怎么看
Dagger2 进阶使用 - 很多dagger的非基础使用,使用kotlin简直满分
深入Dagger:自定义AutoValue - 介绍了Google的AutoValue框架,以及如何与dagger混用
文章浏览阅读343次。五种原始的变量类型1.Undefined--未定义类型 例:var v;2.String -- ' '或" "3.Boolean4.Number5.Null--空类型 例: var v=null;Number中:NaN -- not a number非数本身是一个数字,但是它和任何数字都不相等,代表非数,它和自己都不相等判断是不是NaN不能用=_curry函数未定义
文章浏览阅读1.2w次,点赞2次,收藏17次。兑换码编码设计当前各个业务系统,只要涉及到产品销售,就离不开大大小小的运营活动需求,其中最普遍的就是兑换码需求,无论是线下活动或者是线上活动,都能起到良好的宣传效果。兑换码:由一系列字符组成,每一个兑换码对应系统中的一组信息,可以是优惠信息(优惠券),也可以是相关奖品信息。在实际的运营活动中,要求兑换码是唯一的,每一个兑换码对应一个优惠信息,而且需求量往往比较大(实际上的需求只有预期_优惠券编码规则
文章浏览阅读45次。C语言程序设计实训教程教学课件作者周林ch04结构化程序设计课件.ppt* * 4.1 选择结构程序设计 4.2 循环结构程序设计 4.3 辅助控制语句 第四章 结构化程序设计 4.1 选择结构程序设计 在现实生活中,需要进行判断和选择的情况是很多的: 如果你在家,我去拜访你 如果考试不及格,要补考 如果遇到红灯,要停车等待 第四章 结构化程序设计 在现实生活中,需要进行判断和选择的情况..._在现实生活中遇到过条件判断的问
文章浏览阅读999次。幻数使用说明 在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事情。 因为设备都是特定的,这里也没法说。关键在于怎样组织命令码,因为在ioctl中命令码是唯一联系用户程序命令和驱动程序支持的途径 。 命令码的组织是有一些讲究的,因为我们一定要做到命令和设备是一一对应的,利_ioctl-number.txt幻数说明
文章浏览阅读399次。键盘按下“Shift+Ctrl+p” 输入: C++Configurations,选择JSON界面做如下改动:1.首先把 “/usr/include”,放在最前2.查看C++路径,终端输入gcc -v -E -x c++ - /usr/include/c++/5 /usr/include/x86_64-linux-gnu/c++/5 /usr/include/c++/5/backward /usr/lib/gcc/x86_64-linux-gnu/5/include /usr/local/_orb-slam3 include 报错
文章浏览阅读129次。本系列的最后一篇,因未有精力写更多的入门教程,上篇已经抛出书单,有兴趣的朋友可阅读好书来成长,此系列主讲有理由爱Sqlserver的论证性文章,希望读者们看完后,可自行做出判断,Sqlserver是否真的合适自己,目的已达成。渴望自动化及使用场景笔者所最能接触到的群体为Excel、PowerBI用户群体,在Excel中,我们知道可以使用VBA、VSTO来给Excel带来自动化操作..._sqlsever 数据分析
文章浏览阅读294次,点赞6次,收藏4次。教育智脑)建立学校的全连接中台,对学校运营过程中的数据进行处理和标准化管理,挖掘数据的价值。能:一、原先孤立的系统聚合到一个统一的平台,实现单点登录,统一身份认证,方便管理;三、数据共享,盘活了教育大数据资源,通过对外提供数。的方式构建教育的通用服务能力平台,支撑教育核心服务能力的沉淀和共享。物联网将学校的各要素(人、机、料、法、环、测)全面互联,数据实时。智慧校园解决方案,赋能教学、管理和服务升级,智慧教育体系,该数据平台具有以下几大功。教育大数据平台底座:教育智脑。教育大数据平台,以中国联通。_高校智慧大脑
文章浏览阅读9.5k次,点赞2次,收藏27次。分治法,动态规划法,贪心算法这三者之间有类似之处,比如都需要将问题划分为一个个子问题,然后通过解决这些子问题来解决最终问题。但其实这三者之间的区别还是蛮大的。贪心是则可看成是链式结构回溯和分支界限为穷举式的搜索,其思想的差异是深度优先和广度优先一:分治算法一、基本概念在计算机科学中,分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两_算法概念实例
文章浏览阅读5.6k次。考研篇emmmmm,这是我随笔篇章的第二更,原本计划是在中秋放假期间写好的,但是放假的时候被安排写一下单例模式,做了俩机试题目,还刷了下PAT的东西,emmmmm,最主要的还是因为我浪的很开心,没空出时间来写写东西。 距离我考研结束已经快两年了,距离今年的考研还有90天左右。 趁着这个机会回忆一下青春,这一篇会写的比较有趣,好玩,纯粹是为了记录一下当年考研中发生的有趣的事。 首先介绍..._考研调剂抑郁
文章浏览阅读438次。SpringMVC文章目录SpringMVC1、SpringMVC简介1.1 什么是MVC1.2 什么是SpringMVC1.3 SpringMVC的特点2、HelloWorld2.1 开发环境2.2 创建maven工程a>添加web模块b>打包方式:warc>引入依赖2.3 配置web.xml2.4 创建请求控制器2.5 创建SpringMVC的配置文件2.6 测试Helloworld2.7 总结3、@RequestMapping注解3.1 @RequestMapping注解的功能3._class org.springframework.web.filter.characterencodingfilter is not a jakart
文章浏览阅读4.9k次。gdb 远程调试的一个问题:Don't know how to run. Try "help target".它在抱怨不知道怎么跑,目标是什么. 你需要为它指定target remote 或target extended-remote例如:target extended-remote 192.168.1.136:1234指明target 是某IP的某端口完整示例如下:targ..._don't know how to run. try "help target".
文章浏览阅读85次。习题 11、算法描述主要是用两种基本方法:第一是自然语言描述,第二是使用专用工具进行算法描述2、c 语言程序的结构如下:1、c 语言程序由函数组成,每个程序必须具有一个 main 函数作为程序的主控函数。2、“/*“与“*/“之间的内容构成 c 语言程序的注释部分。3、用预处理命令#include 可以包含有关文件的信息。4、大小写字母在 c 语言中是有区别的。5、除 main 函数和标准库函数以..._c语言语法0x1e