Dagger2在Android平台上的新魔法
0. 前言
上一篇文章 Dagger2在Android平台上的新姿势,主要介绍了Dagger2在Android平台上的更加简洁,更加符合依赖注入思想的新用法。按照里面介绍的步骤,我们可以一步步的实现,并没有什么难度。但是,关于这一切的魔法究竟是怎么发生的,上一篇文章(官方文档)中只给出了大概的描述。这一篇文章将通过一个实际的例子来解释这一切背后的魔法。
我已经放弃使用dagger.android了,具体原因可以查看当定义Dagger2 Scope时,你在定义什么?
1. 一个问题
你可能会说,在Android上,dagger2我用得6的飞起,你现在告诉我说又有新姿势了。。。现在骗子这么多,我凭啥相信你!你倒是说说为什么要换新姿势,就因为我的发际线越来越高了吗?!
大兄弟,莫急,你原来那个姿势的问题说大不大,说小也不小。你原来是不是经常这么写:
public class YourActivity extends Activity {
@Inject
Frombulator frombulator;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerActivityComponent.builder()
.appComponent(YourApp.getAppComponent())
.activityModule(new ActivityModule(this))
.build()
.inject(this);
// ...其它代码
}
}
这里最主要的问题在于,被注入类(YourActivity
)需要知道注入器(DaggerActivityComponent
)是什么,并且由于四大组件和Fragment的创建不受我们的控制,因此我们只能在其生命周期内完成相应的依赖注入。这破坏了依赖注入的崇高理想:一个类不应该对它是如何被注入的有任何的了解。
对于Activity
,一般需要在onCreate
中构造DaggerActivityComponent
调用它的inject
方法。虽然你可以通过封装,将这些步骤封装到YourActivity
的父类中,避免每个Activity
都写这些仪式感的代码。但是,我们崇高的理想并没有实现。
新姿势就是为了实现我们更懒的崇高理想。为此dagger2给我们指了一条明路:把Android四大组件和Fragment对应的注入器的构造器(Subcomponent.Builder)放在Map中,当系统构建四大组件和Fragment时,从Map中查找出对应的Builder,完成注入。最后,我们可以像下面这样完成注入。
public class YourActivity extends Activity {
@Inject
Frombulator frombulator;//OK!就是这么简单
}
2. 背后的原理
2.1 创建SubComponent并加入到Map中
假设我们有一个MainActivity
,里面有UserFragment
和SearchFragment
。定义两个Module
:MainActivityModule
和FragmentBuildersModule
:
@Module
public abstract class MainActivityModule {
@ContributesAndroidInjector(modules = FragmentBuildersModule.class)
abstract MainActivity contributeMainActivity();
}
@Module
public abstract class FragmentBuildersModule {
@ContributesAndroidInjector
abstract UserFragment contributeUserFragment();
@ContributesAndroidInjector
abstract SearchFragment contributeSearchFragment();
}
这就是dagger2在Android平台上新的用法,详见Dagger2在Android平台上的新姿势。注解@ContributesAndroidInjector
会生成如下的代码:
@Module(subcomponents = MainActivityModule_ContributeMainActivity.MainActivitySubcomponent.class)
public abstract class MainActivityModule_ContributeMainActivity {
private MainActivityModule_ContributeMainActivity() {}
@Binds
@IntoMap
@ActivityKey(MainActivity.class)
abstract AndroidInjector.Factory<? extends Activity> bindAndroidInjectorFactory(
MainActivitySubcomponent.Builder builder);
@Subcomponent(modules = FragmentBuildersModule.class)
public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MainActivity> {}
}
}
这是魔法得以发生的核心,里面包含了许多概念,让我们一一来看。
先来看几个dagger2中的功能。@Binds
可以用于注解Module
中abstract
方法,一般是用来把一个接口的实现绑定到接口上。@IntoMap
和@ActivityKey
注解,向Map中加入数据。@Module(subcomponents = ...)
其中的参数subcomponents可以指定一些subcomponent,这些subcomponent必须是该Module被使用的component的子component,这样这些Subcomponent.Builder就可以像普通对象一样被注入。
再来看看AndroidInjector
:
public interface AndroidInjector<T> {
void inject(T instance);
/**
* Factory模式创建具体类型T的 AndroidInjector
*
* @param <T> 四大组件或Fragment
*/
interface Factory<T> {
AndroidInjector<T> create(T instance);
}
/**
* Builder模式实现Factory
*/
abstract class Builder<T> implements AndroidInjector.Factory<T> {
@Override
public final AndroidInjector<T> create(T instance) {
seedInstance(instance);
return build();
}
/**
* 把四大组件或Fragment的实例instance绑定到AndroidInjector上
*/
@BindsInstance
public abstract void seedInstance(T instance);
public abstract AndroidInjector<T> build();
}
}
看清楚,AndroidInjector
接口中只有一个方法inject
(是不是让你想到了Component)。AndroidInjector的命名非常的自解释,意思是Android的注入器,即只能在Android四大组件和Fragment上使用的注入器,所谓的注入器就是我们平时定义的Component。AndroidInjector
接口中还定义了一个Factory
接口,和一个实现了该接口的Builder
抽象类。所以,AndroidInjector
接口的意义就十分明显了,它是一个帮助我们定义Component的接口。再看看之前MainActivitySubcomponent
的定义:
@Subcomponent(modules = FragmentBuildersModule.class)
public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MainActivity> {}
}
MainActivitySubcomponent
的定义完全借助AndroidInjector
接口和AndroidInjector.Builder
抽象类。其中,AndroidInjector.Builder
通过seedInstance(T instance)
方法,把MainActivity
实例绑定到了MainActivitySubcomponent
中,这样就可以在MainActivitySubcomponent
关联的Module
或其子Component中使用MainActivity
实例了。
让我们来回顾一下到目前为止的流程:
同理对于Fragment有:
FragmentSubcomponent流程显然FragmentSubcomponent是ActivitySubcomponent的子Component;而ActivitySubcomponent是AppComponent的子Component。如下是AppComponent的定义:
@Singleton
@Component(modules = {
AndroidInjectionModule.class,//用于提供四大组件和Fragment的Map
AppModule.class,//定义一些全局的对象
MainActivityModule.class//这样MainActivitySubcomponent就成了AppComponent的子Component
})
public interface AppComponent {
@Component.Builder
interface Builder {
//跟之前同样的方式,把Application实例绑定到AppComponent,比把Application传递给AppModule再提供出来要更方便也更快捷
@BindsInstance Builder application(Application application);
AppComponent build();
}
void inject(MyApp myApp);
}
2.2 从Map中把相应的SubComponent取出来
现在 *ActivitySubcomponent.Builder 和 *FragmentSubcomponent.Builder,都被存储在各自的Map中了,最后一步就是从Map中把它们取出来,然后完成注入。这需下两个步骤。
2.2.1 父Component创建分发者
首先,在父Component创建的地方实现HasActivityInjector
(HasServiceInjector
,HasBroadcastReceiverInjector
,HasContentProviderInjector
,HasFragmentInjector
,HasSupportFragmentInjector
),具体实现哪个接口,取决于子Component有哪些。
/**
* 对于Application,其子Component包括 *ActivitySubcomponent,所以实现 HasActivityInjector接口
*/
public class MyApp extends Application implements HasActivityInjector {
@Inject
DispatchingAndroidInjector<Activity> dispatchingActivityInjector;
@Override
public void onCreate() {
super.onCreate();
DaggerAppComponent.builder()
.application(this)
.build()
.inject(this);
}
@Override
public AndroidInjector<Activity> activityInjector() {
return dispatchingActivityInjector;
}
}
/**
* 对于Activity,其子Component包括 *FragmentSubcomponent,所以实现 HasSupportFragmentInjector接口
*/
public class MainActivity extends AppCompatActivity implements HasSupportFragmentInjector {
@Inject
DispatchingAndroidInjector<Fragment> dispatchingAndroidInjector;
@Override
public DispatchingAndroidInjector<Fragment> supportFragmentInjector() {
return dispatchingAndroidInjector;
}
}
实现接口的主要目的是创建分发者DispatchingAndroidInjector
,也就是解决从哪个Map中查找的问题。下面是DispatchingAndroidInjector
的定义:
/**
* @param <T> the core Android type to be injected
*/
public final class DispatchingAndroidInjector<T> implements AndroidInjector<T> {
private final Map<Class<? extends T>, Provider<AndroidInjector.Factory<? extends T>>>
injectorFactories;
@Inject
DispatchingAndroidInjector(
Map<Class<? extends T>, Provider<AndroidInjector.Factory<? extends T>>> injectorFactories) {
this.injectorFactories = injectorFactories;
}
/**
* 对于instance尝试 members-injection
*/
public boolean maybeInject(T instance) {
//通过instance.getClass()去查找对应的AndroidInjector.Factory,其实就是我们的AndroidInjector.Builder
Provider<AndroidInjector.Factory<? extends T>> factoryProvider =
injectorFactories.get(instance.getClass());
if (factoryProvider == null) {
return false;
}
@SuppressWarnings("unchecked")
AndroidInjector.Factory<T> factory = (AndroidInjector.Factory<T>) factoryProvider.get();
try {
AndroidInjector<T> injector = factory.create(instance);
//注意!这里完成了注入。
injector.inject(instance);
return true;
} catch (ClassCastException e) {
throw new InvalidInjectorBindingException();
}
}
@Override
public void inject(T instance) {
boolean wasInjected = maybeInject(instance);
if (!wasInjected) {
throw new IllegalArgumentException(errorMessageSuggestions(instance));
}
}
}
当我们实现了Has*Injector
接口时,其实就是确定了DispatchingAndroidInjector
需要查找的Map,根据instance.getClass()
查找到对于的AndroidInjector.Factory
即可完成成员注入。
2.2.2 真正的成员注入
万事俱备了,只差最后的成员注入了。有了DispatchingAndroidInjector
对象,只需要指定被注入的对象是什么,然后调用DispatchingAndroidInjector
上的inject(T instance)
方法即可完成成员注入。
public class MainActivity extends AppCompatActivity implements HasSupportFragmentInjector {
@Inject
SomeDependency someDep;
public void onCreate(Bundle savedInstanceState) {
//这一步往往封装到父类中
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
}
}
public class UserFragment extends Fragment {
@Inject
SomeDependency someDep;
@Override
public void onAttach(Activity activity) {
//这一步往往封装到父类中
AndroidSupportInjection.inject(this);
super.onAttach(activity);
// ...
}
}
最后把一切连接在一起的就是这个AndroidInjection.inject(this)
,对于android.support.v4.app.Fragment
而言是AndroidSupportInjection.inject(this)
。其实这里面的逻辑极其简单:
/** Injects core Android types. */
public final class AndroidInjection {
public static void inject(Activity activity) {
checkNotNull(activity, "activity");
Application application = activity.getApplication();
if (!(application instanceof HasActivityInjector)) {
throw new RuntimeException();
}
//这里的activityInjector显然就是DispatchingAndroidInjector<Activity>
AndroidInjector<Activity> activityInjector =
((HasActivityInjector) application).activityInjector();
checkNotNull(
activityInjector,
"%s.activityInjector() returned null",
application.getClass().getCanonicalName());
//在Activity的Map中查找,并完成成员注入
activityInjector.inject(activity);
}
public static void inject(Fragment fragment) {
//...
}
public static void inject(Service service) {
//...
}
public static void inject(BroadcastReceiver broadcastReceiver, Context context) {
//...
}
public static void inject(ContentProvider contentProvider) {
//...
}
private AndroidInjection() {}
}
对于Service
,BroadcastReceiver
,ContentProvider
,逻辑跟inject(Activity activity)
方法是一样的:从Application
中获得DispatchingAndroidInjector<Activity/Service/BroadcastReceiver/ContentProvider>
,然后调用其inject
方法完成成员注入。
对于android.support.v4.app.Fragment
,逻辑稍微复杂一些:
/** Injects core Android types from support libraries. */
public final class AndroidSupportInjection {
/**
* 按照如下逻辑找到合适的 AndroidInjector<Fragment>:
* 1. 查找所有的父fragment,看它是否实现了HasSupportFragmentInjector接口;如果没有则
* 2. 看getActivity()的Activity是否实现了HasSupportFragmentInjector接口;如果没有则
* 3. 看Application是否实现了HasSupportFragmentInjector接口
*/
public static void inject(Fragment fragment) {
checkNotNull(fragment, "fragment");
HasSupportFragmentInjector hasSupportFragmentInjector = findHasFragmentInjector(fragment);
AndroidInjector<Fragment> fragmentInjector =
hasSupportFragmentInjector.supportFragmentInjector();
checkNotNull(
fragmentInjector,
"%s.supportFragmentInjector() returned null",
hasSupportFragmentInjector.getClass().getCanonicalName());
//完成成员注入
fragmentInjector.inject(fragment);
}
private static HasSupportFragmentInjector findHasFragmentInjector(Fragment fragment) {
Fragment parentFragment = fragment;
while ((parentFragment = parentFragment.getParentFragment()) != null) {
if (parentFragment instanceof HasSupportFragmentInjector) {
return (HasSupportFragmentInjector) parentFragment;
}
}
Activity activity = fragment.getActivity();
if (activity instanceof HasSupportFragmentInjector) {
return (HasSupportFragmentInjector) activity;
}
if (activity.getApplication() instanceof HasSupportFragmentInjector) {
return (HasSupportFragmentInjector) activity.getApplication();
}
throw new IllegalArgumentException(
String.format("No injector was found for %s", fragment.getClass().getCanonicalName()));
}
private AndroidSupportInjection() {}
}
之所以Fragment
的注入逻辑要复杂些,是因为*FragmentSubcomponent可以是其父Fragment的子Component,也可以是Activity的子Component,还可以是Application的子Component。而Activity(Service,BroadcastReceiver,ContentProvider)的Subcomponent只可能是Application的子Component。
3. 总结
下面以Activity为例,回顾一下整体的流程。
总流程以上就是为什么我们没有显式地构造ActivitySubcomponent,却能完成成员依赖注入的魔法。这样每个Activity都不需要知道其注入器是什么,就可以完成依赖注入,实现了我们最初的崇高理想。
AndroidInjection.inject(activity)
传入的activity最后被绑定到了ActivitySubcomponent上,这样在ActivitySubcomponent关联的Module和其子Component中就可以使用该activity实例了。
4. 尾巴
dagger2在Android平台上新的用法,在其底层技术实现上,只是使用了dagger2原来就有的subcomponent和multibindings概念,并没有任何新增的东西。我在想,为什么我们总是技术的使用者,为什么面对很多问题我们总是熟视无睹,为什么总是要等到新的技术产生时,才会发现原来使用的技术有这样那样的问题?我瞎猜了几个原因:
- 技术能力不够,有的问题看不出来是问题。
- 只是使用,缺乏思考。
- 技术本来就是分层的工作,我们大多在应用层。
- 没拿那份钱,不考虑那些事。