Dagger2在Android平台上的新魔法

作者: 珞泽珈群 | 来源:发表于2017-11-09 16:20 被阅读268次

    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,里面有UserFragmentSearchFragment。定义两个ModuleMainActivityModuleFragmentBuildersModule

    @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可以用于注解Moduleabstract方法,一般是用来把一个接口的实现绑定到接口上。@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实例了。
    让我们来回顾一下到目前为止的流程:

    ActivitySubcomponent流程

    同理对于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创建的地方实现HasActivityInjectorHasServiceInjectorHasBroadcastReceiverInjectorHasContentProviderInjectorHasFragmentInjectorHasSupportFragmentInjector),具体实现哪个接口,取决于子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() {}
    }
    

    对于ServiceBroadcastReceiverContentProvider,逻辑跟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概念,并没有任何新增的东西。我在想,为什么我们总是技术的使用者,为什么面对很多问题我们总是熟视无睹,为什么总是要等到新的技术产生时,才会发现原来使用的技术有这样那样的问题?我瞎猜了几个原因:

    • 技术能力不够,有的问题看不出来是问题。
    • 只是使用,缺乏思考。
    • 技术本来就是分层的工作,我们大多在应用层。
    • 没拿那份钱,不考虑那些事。

    相关文章

      网友评论

        本文标题:Dagger2在Android平台上的新魔法

        本文链接:https://www.haomeiwen.com/subject/yjqmmxtx.html