【Android】个人Dagger2学习历程:从一头雾水到恍然大悟

前言

关于Dagger2的教程在网上已经有不少不少了,对于使用和原理都讲得比较明白,可是对于第一次接触的人们来讲(好比我),不免会看得一头雾水,因此这里我就记录一下我学习Dagger2的过程,分享最快速的学习方法给你们。javascript

介绍

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

public class LoginActivity extends Activity{

    @Inject
    Application application;
}复制代码

开始

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

在主项目的build.gradle中添加git

classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'复制代码

在Module的build.gradle中添加github

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中看看。api

@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中建立了:app

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。

代码地址 GitHubQQ:318531018

相关文章
相关标签/搜索