我的Dagger2学习历程:从一头雾水到恍然大悟

前言

关于Dagger2的教程在网上已经有很多很多了,对于使用和原理都讲得比较明白,但是对于第一次接触的人们来说(比如我),难免会看得一头雾水,所以这里我就记录一下我学习Dagger2的过程,分享最快速的学习方法给大家。

介绍

Dagger2是一个依赖注入的框架,什么是依赖注入?简单的来说就是类中依赖的对象只要声明就可以使用了,它的创建由框架来管理。如下代码所示,application直接就可以拿来用了。

    public class LoginActivity extends Activity{

        @Inject
        Application application;
    }

开始

刚开始接触Dagger2的时候大量阅读了网上的教程,主要是一些概念性的东西,篇幅长了一下就看晕了,所以这里推荐大家直接看代码。把代码运行起来,结合文章和代码一起看,相信你很快就能上手了。 gitHub地址

在主项目的build.gradle中添加

    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

在Module的build.gradle中添加

    compile 'com.google.dagger:dagger:2.6'
    apt 'com.google.dagger:dagger-compiler:2.6'

@Component

首先这个先定义一个全局的AppComponent,为什么需要全局的AppComponent呢?因为这里放得都是一些公共的对象,它们的生命周期是和Application一致的。通常情况下一个项目定义一个AppComponent,其他每个Activity或Fragment会对应一个Component。

    @Singleton
    @Component(modules = {AppModule.class, HttpModule.class, ApiServiceModule.class, DBModule.class})
    public interface AppComponent {

        Application application();

        Gson gson();
        //网络访问Service
        ServiceManager serviceManager();
        //数据库访问
        DBManager DBManager();
    }

AppComponent是一个接口,里面定义了提供依赖的方法声明,这个AppComponent提供了Application、Gson、ServiceManager、DBManager依赖(方法名没有限制),dagger框架会根据对象类型把它们注入到需要使用它们的地方。
那这些提供的对象是从哪里来的呢?总不能凭空产生吧,这就要看module了,AppComponent中引用的AppModule、HttpModule、ApiServiceModule、DBModule,现在我们进入AppModule中看看。

@Module & @Provides

    @Module
    public class AppModule {

        public AppModule(HuiApplication application) {
            this.mApplication = application;
        }

        private Application mApplication;

        @Singleton
        @Provides
        public Application provideApplication() {
            return mApplication;
        }

        @Singleton
        @Provides
        public Gson provideGson() {
            return new Gson();
        }
    }

可以看到Module就是提供这些依赖的地方,dagger会根据@Provides标记的方法返回依赖对象,这个AppModule中提供了Application和Gson对象的创建。可能大家都注意到@Singleton这个注解了吧,如果需要让依赖对象是单例的话标注一下就可以,后面还会提到。

如果你的provide方法里需要用到其他provide提供的对象,可以直接通过方法参数传进来,如下所示,provideRetrofit()方法中需要用到OkHttpClient和HttpUrl,直接传进来就可以了。

    @Module
    public class HttpModule {
        @Singleton
        @Provides
        Retrofit provideRetrofit(OkHttpClient client, HttpUrl baseUrl) {
            return new Retrofit.Builder()
                    .client(client)
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .baseUrl(baseUrl)
                    .build();
        }

        @Singleton
        @Provides
        OkHttpClient provideOkHttpClient() {

            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            builder.connectTimeout(DEFAULT_CONNECT_TIMEOUT, TimeUnit.SECONDS)
                    .readTimeout(DEFAULT_READ_TIMEOUT, TimeUnit.SECONDS)
                    .writeTimeout(DEFAULT_WRITE_TIMEOUT, TimeUnit.SECONDS);

            return builder.build();
        }

    }

AppComponent

现在需要创建AppComponent,因为这是全局的Component,自然是在Application中创建了:

    public class HuiApplication extends Application{

        private AppComponent mAppComponent;

        @Override
        public void onCreate() {
            super.onCreate();

            mAppComponent = DaggerAppComponent
                    .builder()
                    .appModule(new AppModule(this))
                    .apiServiceModule(new ApiServiceModule())
                    .dBModule(new DBModule())
                    .httpModule(new HttpModule())
                    .build();
        }


        public AppComponent getAppComponent() {
            return mAppComponent;
        }
    }

bulid一下项目,dagger2会为每个component创建Dagger+Component名的类,该类中会提供创建和设置每一个module实例的方法,可以看到这里的module是我们自己new了传进去的,我们可以为各个module做一些初始化的处理。这里为AppComponent创建一个get方法,接下来就要到Activity了。上面说了通常情况下每个Activity会对应一个Component,那现在就为LoginActivity创建一个LoginComponent:

@dependencies

    @ActivityScope
    @Component(dependencies = AppComponent.class)
    public interface LoginComponent {

        void inject(LoginActivity activity);
    }

首先看到@dependencies,这里就把AppComponent中提供的一些对象依赖了过来,实现了全局共用。同时声明一个inject方法,参数是你要注入到的类(方法名词不限,这里用inject比较形象)。现在就看看LoginActivity是如何注入的:

@Inject

    public class LoginActivity extends Activity {

        @Inject
        ServiceManager serviceManager;
        @Inject
        DBManager DBManager;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            HuiApplication mApplication = (HuiApplication) getApplication();
            setupActivityComponent(mApplication.getAppComponent());
            initData();
        }


        @Override
        protected void setupActivityComponent(AppComponent appComponent) {
            DaggerLoginComponent
                    .builder()
                    .appComponent(appComponent)
                    .build()
                    .inject(this);
        }
    }

同样定义了LoginComponent后会自动生成DaggerLoginComponent,这里从Application中获取之前创建的AppComponent,最后调用inject,注入就完成了。此时使用@Inject来标记成员变量就可以使用了,有没有感觉很神奇?此时你可能会觉得疑惑,接下来就开始对上面使用的一些注解和方法进行讲解。

概念

dagger2是什么?

Dagger2是一款基于Java注解来实现的完全在编译阶段完成依赖注入的开源库,主要用于模块间解耦、提高代码的健壮性和可维护性。Dagger2在编译阶段通过apt利用Java注解自动生成Java代码,然后结合手写的代码来自动帮我们完成依赖注入的工作。

下图能很好地展示Dagger2这几个注解的作用:

注解介绍

@Component 用于标注接口,是依赖需求方和依赖提供方之间的桥梁。被Component标注的接口在编译时会生成该接口的实现类(Dagger+Component名字),我们通过调用这个实现类的方法完成注入;Component接口中主要定义一些提供依赖的声明

@Inject有三个作用 一是用来标记需要依赖的变量,以此告诉Dagger2为它提供依赖; 二是用来标记构造函数,Dagger2通过@Inject注解可以在需要这个类实例的时候来找到这个构造函数并把相关实例构造出来,以此来为被@Inject标记了的变量提供依赖,例如:

    public class Student {

        private String name;
        private int age;

        @Inject
        public Student() {

        }
    }

三是用来标记普通方法,该方法会在对象注入完成之后调用,可以根据这一特性因此做一些初始化的工作。

@Module @Module用于标注提供依赖的类。你可能会有点困惑,上面不是提到用@Inject标记构造函数就可以提供依赖了么,为什么还需要@Module?很多时候我们需要提供依赖的构造函数是第三方库的,我们没法给它加上@Inject注解,又比如说提供以来的构造函数是带参数的,如果我们之所简单的使用@Inject标记它,那么他的参数又怎么来呢?@Module正是帮我们解决这些问题的。

@Provides @Provides用于标注Module所标注的类中的方法,该方法在需要提供依赖时被调用,从而把预先提供好的对象当做依赖给标注了@Inject的变量赋值;

@Scope @Scope同样用于自定义注解,我能可以通过@Scope自定义的注解来限定注解作用域,实现局部的单例;比如我们前面使用到的@ActivityScope:

    @Scope
    @Retention(RUNTIME)
    public @interface ActivityScope {}

如果需要提供局部单例支持,则需要在Component中和@provides注解的方法上@ActivityScope,这里说的局部单例的意思是在该Component中是唯一的,如果Component是全局唯一的话就是全局单例了,比如AppComponent。

@Singleton @Singleton其实就是一个通过@Scope定义的注解,我们一般通过它来标记全局单例(AppComponent)。

我们提到@Inject和@Module都可以提供依赖,那如果我们即在构造函数上通过标记@Inject提供依赖,有通过@Module提供依赖Dagger2会如何选择呢?具体规则如下: 步骤1:首先查找@Module标注的类中是否存在提供依赖的方法。 步骤2:若存在提供依赖的方法,查看该方法是否存在参数。 a:若存在参数,则按从步骤1开始依次初始化每个参数;
b:若不存在,则直接初始化该类实例,完成一次依赖注入。
步骤3:若不存在提供依赖的方法,则查找@Inject标注的构造函数,看构造函数是否存在参数。 a:若存在参数,则从步骤1开始依次初始化每一个参数
b:若不存在,则直接初始化该类实例,完成一次依赖注入。

Dagger2遇上MVP

如果你的项目是采用MVP架构的,那么结合Dagger2将会是一件非常棒的体验,它让M-V-P进一步解藕,架构更清晰。 在上面的LoginActivity基础上实现MVP模式。

LoginContract

MVP接口契约类,定义view和model的接口

    public interface LoginContract {

        interface View extends BaseView {

            /**
             * 登录成功
             * @param result
             */
            void loginSuccess(String result);
        }

        interface Model extends IModel {

            /**
             * 登录
             * @param mobile
             * @param password
             */
            Observable<String> login(String mobile, String password);

        }
    }

LoginModule

定义Module,只要提供view和model的依赖,可以看到LoginModel是通过方法参数注入进来的,这样model和view就解耦了。

    @Module
    public class LoginModule {
        private LoginContract.View view;

        public LoginModule(LoginContract.View view) {
            this.view = view;
        }

        @ActivityScope
        @Provides
        LoginContract.View provideLoginView() {
            return this.view;
        }

        @ActivityScope
        @Provides
        LoginContract.Model provideLoginModel(LoginModel model) {
            return model;
        }
    }

LoginComponent

在之前的loginComponent中添加LoginModule

    @ActivityScope
    @Component(modules = LoginModule.class, dependencies = AppComponent.class)
    public interface LoginComponent {

        void inject(LoginActivity activity);
    }

LoginActivity

在LoginActivity中初始化LoginComponent,我们就从这里开始看看MVP的依赖是怎么行程的:

    public class LoginActivity extends BaseActivity<LoginPresenter> implements LoginContract.View {

        @Override
        protected void setupActivityComponent(AppComponent appComponent) {
            DaggerLoginComponent
                    .builder()
                    .appComponent(appComponent)
                    .loginModule(new LoginModule(this))
                    .build()
                    .inject(this);
        }
    }

我们的view就是当前的Activity,所以new LoginModule(this)这里就提供了view的依赖。 这里定义了BaseActivity并使用了泛型,设置成当前界面的presenter,这里可以知道注入的过程是在BaesActivity中完成的,现在看看BaseActivity:

    public abstract class BaseActivity<P extends BasePresenter> extends AppCompatActivity {

        ....省略代码

        @Inject
        protected P mPresenter;

        ....省略代码
    }

BaseActivity中其实做的操作很简单,通过@Inject注解将对应的Presenter注入进来。这样在LoginActivity中就可以使用该Presenter了,现在我们看看LoginPresenter的实现。

LoginPresenter

    @ActivityScope
    public class LoginPresenter extends BasePresenter<LoginContract.Model, LoginContract.View> {

        @Inject
        public LoginPresenter() {
        }

    }

Presenter中和Actvity的做法基本类似,对象的注入还是放在父类里面,通过泛型的方式确定类型。这里可以看到这里有一个@Inject标注的空构造方法,这个是必须的,为了就是在LoginActivity中可以依赖到该Presenter。

    public class BasePresenter<M extends IModel, V extends BaseView> implements IPresenter {
        @Inject
        protected M mModel;
        @Inject
        protected V mView;
    }

BasePresenter里面就是View和Model的注入。
model的实现和presenter的是原理是一样的,这里就不一一述说了,这样MVP的架构就简历起来了:
* 在View(Activity)中注入Presenter; * 在Presenter中注入View 和 Model * 在Model中注入其他一些数据处理的对象(数据库实例和网络请求实例)

Dagger2生成的代码解析

到这里,你是不是觉得为什么Dagger2会如此神奇?我们这里就对生成的代码DaggerAppComponent来进行解析,看看它是怎么实现依赖注入的。

    DaggerAppComponent
        .builder()
        .appModule(new AppModule(this))
        .apiServiceModule(new ApiServiceModule())
        .dBModule(new DBModule())
        .httpModule(new HttpModule())
        .build();

那就从build方法开始:

    public AppComponent build() {
          if (appModule == null) {
            throw new IllegalStateException(AppModule.class.getCanonicalName() + " must be set");
          }
          if (httpModule == null) {
            this.httpModule = new HttpModule();
          }
          if (apiServiceModule == null) {
            this.apiServiceModule = new ApiServiceModule();
          }
          if (dBModule == null) {
            this.dBModule = new DBModule();
          }
          return new DaggerAppComponent(this);
        }

build方法中会对我们传入的module进行NULL检查,可以看出来,如果我们的model的构造函数是无参的话,可以不用设置,dagger2会帮我们初始化,接着看new DaggerAppComponent():

    private DaggerAppComponent(Builder builder) {
        assert builder != null;
        initialize(builder);
      }


      @SuppressWarnings("unchecked")
      private void initialize(final Builder builder) {

        this.provideApplicationProvider =
            DoubleCheck.provider(AppModule_ProvideApplicationFactory.create(builder.appModule));

        this.provideOkHttpClientProvider =
            DoubleCheck.provider(HttpModule_ProvideOkHttpClientFactory.create(builder.httpModule));

        this.provideBaseUrlProvider =
            DoubleCheck.provider(
                ApiServiceModule_ProvideBaseUrlFactory.create(builder.apiServiceModule));

        this.provideRetrofitProvider =
            DoubleCheck.provider(
                HttpModule_ProvideRetrofitFactory.create(
                    builder.httpModule, provideOkHttpClientProvider, provideBaseUrlProvider));

        this.provideUserServiceProvider =
            DoubleCheck.provider(
                ApiServiceModule_ProvideUserServiceFactory.create(
                    builder.apiServiceModule, provideRetrofitProvider));

        this.serviceManagerProvider =
            DoubleCheck.provider(ServiceManager_Factory.create(provideUserServiceProvider));

        this.provideCommonSQLiteHelperProvider =
            DoubleCheck.provider(
                DBModule_ProvideCommonSQLiteHelperFactory.create(
                    builder.dBModule, provideApplicationProvider));

        this.provideUserInfoDaoProvider =
            DoubleCheck.provider(
                DBModule_ProvideUserInfoDaoFactory.create(
                    builder.dBModule, provideCommonSQLiteHelperProvider));

        this.dBManagerMembersInjector = DBManager_MembersInjector.create(provideUserInfoDaoProvider);

        this.dBManagerProvider =
            DoubleCheck.provider(
                DBManager_Factory.create(dBManagerMembersInjector, provideApplicationProvider));

        this.provideGsonProvider =
            DoubleCheck.provider(AppModule_ProvideGsonFactory.create(builder.appModule));
      }

在构造方法里面调用了initialize(builder),这里面对所有Provider进行初始,这个方法也是完成注入的方法,这里涉及到两个对象: * Provider & Factory 其实就是一个包装类,里面提供了get方法返回对应的包装对象,比如Provider,get方法就返回它持有的Application对象。这些Provider就是我们所有提供依赖的对象(包括在@module类中使用@Provides注解标注的对象,或者@Inject标记构造方法的对象) * XX_MembersInjector 顾名思义,这个是对象注入器,哪些使用了@Inject注解的类就会生成对应的MembersInjector,通过调用其injectMembers()方法实现对象注入。

DoubleCheck.provider()有什么作用呢,它是实现局部单例的,返回一个实现单例的Provider。

这里我们看下LoginActivity_MembersInjector 是怎么注入LoginPresenter的:

    public final class LoginActivity_MembersInjector implements MembersInjector<LoginActivity> {
      private final Provider<LoginPresenter> mPresenterProvider;

      public LoginActivity_MembersInjector(Provider<LoginPresenter> mPresenterProvider) {
        assert mPresenterProvider != null;
        this.mPresenterProvider = mPresenterProvider;
      }

      public static MembersInjector<LoginActivity> create(Provider<LoginPresenter> mPresenterProvider) {
        return new LoginActivity_MembersInjector(mPresenterProvider);
      }

      @Override
      public void injectMembers(LoginActivity instance) {
        if (instance == null) {
          throw new NullPointerException("Cannot inject members into a null reference");
        }
        cn.xdeveloper.dagger2.base.mvp.BaseActivity_MembersInjector.injectMPresenter(
            instance, mPresenterProvider);
      }
    }

在injectMembers方法中调用了父类的injectMembers方法,因为我们把注入的过程抽取到父类了,再看看父类的injectMembers方法:

    public static <P extends BasePresenter> void injectMPresenter(
          BaseActivity<P> instance, Provider<P> mPresenterProvider) {
        instance.mPresenter = mPresenterProvider.get();
      }

通过简单的对象赋值就完成了注入,那调用LoginActivity_MembersInjector的injectMembers方法的地方是哪里呢?

    @Override
    public void inject(LoginActivity activity) {
        loginActivityMembersInjector.injectMembers(activity);
    }

没错这个方法就是我们在LoginComponent中定义的inject接口的实现方法。 这里你可能会觉得奇怪,为什么我定义了inject方法名就会生成injectMembers的实现呢?我们再看看之前定义的LoginComponent的代码:

    @ActivityScope
    @Component(modules = LoginModule.class, dependencies = AppComponent.class)
    public interface LoginComponent {

        void inject(LoginActivity activity);
    }

这里的方法申明是有讲究的: * 如果参数有值的,代表这个方法是注入方法,注入的类就是该参数类,框架会为其创建injectMembers的实现,这个时候是不允许有返回值的,这里方法名是可以随意填写,叫inject比较形象; * 如果参数是没有值的时候,则代表该component提供了依赖,返回类型就是该依赖对象,比如之前的AppComponet中定义的:

    @Singleton
    @Component(modules = {AppModule.class, HttpModule.class, ApiServiceModule.class, DBModule.class})
    public interface AppComponent {

        Application application();

        ServiceManager serviceManager();

        DBManager DBManager();

        Gson gson();
    }

那么这些定义的方法有什么用呢?最简单的方法就是看代码中哪里引用了它们就知道了:

    this.applicationProvider =
            new Factory<Application>() {
              private final AppComponent appComponent = builder.appComponent;

              @Override
              public Application get() {
                return Preconditions.checkNotNull(
                    appComponent.application(),
                    "Cannot return null from a non-@Nullable component method");
              }
            };

这里是DaggerLoginActivityComponent的初始化方法里,因为我们的LoginActivityComponent是依赖AppComponnet的,那要怎么引用这些AppComponent中已经初始化好的对象呢?则是通过上面定义的这些接口方法来访问的。

后续问题

因为Dagger2是在编译阶段完成依赖注入,没有了反射带来的效率问题,但同时就会缺乏了灵活性。 我在重构项目的时候就遇到了这么一个问题,由于我的项目是多数据库的,一个用户对应一个数据库,这样在依赖SQLDataBaseHelper的时候就无从下手了,因为这个在编译期间是无法知道用户信息的,思前想后终于想到了一个办法: 我们可以在AppComponent中管理一个叫DBManager的对象,在DBManager里面含有各种Dao对象,但是这些Dao的创建是由我们自己去创建而不是靠dagger2注入的,这样的话我们就可以在其他需要使用数据库的地方@Inject DBManager就可以了,附上代码仅供参考:

    @Singleton
    public class DBManager {

        private Application application;

        private ContactDao contactDao;

        @Inject
        public DBManager(Application application) {
            this.application = application;
        }


        public ContactDao getContactDao(Long userId) {
            if (contactDao == null) {
                synchronized (DBManager.class) {
                    if (contactDao == null)
                        contactDao = new ContactDao(PrivateDBHelper.getInstance(application, userId));
                }
            }
            return contactDao;
        }
    }

结尾

以上就是全部我对dagger2的了解,最初我也是从一头雾水,现在终算恍然大悟,还是那句老话:代码是最好的老师。对于了解dagger2的注入原理的话多看生成的代码,逻辑还是挺清晰的。 最后,希望大家能早日拥抱dagger2。

代码地址 GitHub QQ:318531018