Dagger2使用学习笔记

注:本文是小生自学时做(fanyi)的笔记,可能含有少儿不宜的误导性内容,学习Dagger请移步原博。
原博地址:http://frogermcs.github.io/

Dagger2使用:
需求:主页上有一个TextView,在TextView中显示人名和年龄
使用Dagger2做依赖注入,需要创建两样东西:
1.Component接口: 想象成是注射器的针管,单词的含义貌似跟作用完全没关系啊,待我查查看。Component类使用 @Component(modules = {xxx.class}) 注解。
UserComponent:

@Component(modules = {UserModule.class})
public interface UserComponent {
    void inject(MainActivity mainActivity);
}

2.Module: 想象是注射器的针筒。注意,Module不是提供的依赖(注射液),而是提供依赖的组件(针筒)。Module类使用 @Module 注解,提供依赖的方法用 @Provides 标识,方法名以 provide 开头。

@Module
public class UserModule {

    UserModule() {}

    @Provides
    UserModel provideUsers() {
        UserModel user = new UserModel();
        user.setName("lala");
        user.setAge(18);
        return user;
    }
}

以上就是我们为了使用Dagger需要额外添加的东西,针筒加针管,还缺少:1.注射器的活塞。2.需要注射的液体。3.接受注射的病人。

1.注射器的活塞
也就是用来真正实施注入的东西。Dagger会根据注解生成一个实现了MembersInjector接口的类。MembersInjector直译过来就是成员注射器,我们把它看成是注射器的活塞。此接口只有一个方法,就是 void injectMembers(T instance).调用生成类的 injectMembers 方法即相当于推动活塞的动作,真正实施注射。对于这个东西我们不需要添加额外的代码,它由Dagger在预编译时期自动生成。

2.需要注射的液体
这就是我们需要注入的依赖了。它可以是任何可以实例化的东西。
UserModel:

//UserModel没有添加任何额外的东西
public class UserModel {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

3.接受注射的病人
也就是接收依赖的需求者。我们用 @inject 注解来标识。
MainActivity:

//需要注入的依赖使用 @Inject 标记
public class MainActivity extends AppCompatActivity {
    private TextView textView;

    @Inject
    UserModel user;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.text_view);

        //进行真正的注入操作
        DaggerUserComponent.builder()
                .userModule(new UserModule())
                .build()
                .inject(this);

        textView.setText("Name:" + user.getName() + "::Age:" + user.getAge());
    }
}

效果:


Dagger2使用学习笔记_第1张图片
successInject.png

咦,这么看起来整个实现还是比较麻烦的。直接在MainActivity new一个user出来不就完了,为什么要用Dagger绕这么大一圈。
先看看什么是依赖。
在MainActivity需要一个UserModel的实例,此时,我们称MainActivity对UserModel有依赖,如果我们要改UserModel,比如在构造函数里就传进去姓名和年龄,而不通过set方法。此时,我们就要更改MainActivity中的代码,这里的MainActivity和UserModel是高耦合的。
而使用Dagger后,即使UserModel有了改变,我们只要改一下Module里的provide方法即可,而不用动MainActivity的代码,此时,MainActivity和UserModel是低耦合的。真真是在平坦的路面上曲折前行啊...

上面是使用Dagger做一个最基本的依赖注入。下面来详解一下各个部分。

  1. @Inject 注解

构造函数注入

public class LoginActivityPresenter {
    
    private LoginActivity loginActivity;
    private UserDataStore userDataStore;
    private UserManager userManager;
    
    @Inject
    public LoginActivityPresenter(LoginActivity loginActivity,
                                  UserDataStore userDataStore,
                                  UserManager userManager) {
        this.loginActivity = loginActivity;
        this.userDataStore = userDataStore;
        this.userManager = userManager;
    }
}

直接在构造函数上加 @Inject 注解,所有的参数都从Module里获取。同时,这个类也可以被用于注入。如下:

public class LoginActivity extends BaseActivity {

    @Inject
    LoginActivityPresenter presenter;
    
    //...
}

限制是一个类中只能有一个构造函数有 @Inject 注解。

成员注入

public class SplashActivity extends AppCompatActivity {
    
    @Inject
    LoginActivityPresenter presenter;
    @Inject
    AnalyticsManager analyticsManager;
    
    @Override
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        getAppComponent().inject(this);
    }
}

也就是上面我们使用的注入方式,这种方式比较好理解,不过需要手动调用注入才能完成注入过程。注入前该成员一直都是null。
限制是被注入成员不能是private的,因为Dagger生成的代码是这么注入的:
splashActivity.analyticsManager = analyticsManagerProvider.get();
这个后面分析生成代码的时候会谈到。

方法注入

public class LoginActivityPresenter {
    
    private LoginActivity loginActivity;
    
    @Inject 
    public LoginActivityPresenter(LoginActivity loginActivity) {
        this.loginActivity = loginActivity;
    }

    @Inject
    public void enableWatches(Watches watches) {
        watches.register(this);    //Watches instance required fully constructed LoginActivityPresenter
    }
}

被注解的方法的所有参数都由Module提供。当被注入方需要当前类的实例(this)时可以用这种方式把自己传给被注入方。注入方法会在构造函数调用完毕后立马被调用。

@Module 注解
用来标注那些提供依赖的类,Dagger通过这个注解来找到提供依赖的类。

@Module
public class GithubApiModule {
    
    @Provides
    @Singleton
    OkHttpClient provideOkHttpClient() {
        OkHttpClient okHttpClient = new OkHttpClient();
        okHttpClient.setConnectTimeout(60 * 1000, TimeUnit.MILLISECONDS);
        okHttpClient.setReadTimeout(60 * 1000, TimeUnit.MILLISECONDS);
        return okHttpClient;
    }

    @Provides
    @Singleton
    RestAdapter provideRestAdapter(Application application, OkHttpClient okHttpClient) {
        RestAdapter.Builder builder = new RestAdapter.Builder();
        builder.setClient(new OkClient(okHttpClient))
               .setEndpoint(application.getString(R.string.endpoint));
        return builder.build();
    }
}

@Provides 注解
标注返回依赖的方法。

@Module
public class GithubApiModule {
    
    //...
    
    @Provides   //This annotation means that method below provides dependency
    @Singleton
    RestAdapter provideRestAdapter(Application application, OkHttpClient okHttpClient) {
        RestAdapter.Builder builder = new RestAdapter.Builder();
        builder.setClient(new OkClient(okHttpClient))
               .setEndpoint(application.getString(R.string.endpoint));
        return builder.build();
    }
}

@Component 注解
标注Module与Inject之间的桥梁接口。在这个接口中定义从哪个module获取依赖,也用于定义那些Module可以用于注入,以及可以注入对象到哪里。
这个例子表示:当前Component使用两个Modules,可以注入依赖到GithubClientApplication,可以使三个依赖公开可见:

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

    void inject(GithubClientApplication githubClientApplication);

    Application getApplication();

    AnalyticsManager getAnalyticsManager();

    UserManager getUserManager();
}

Component也可以依赖别的Component,也可以有自己的生命周期(详见下面的Scope)

@ActivityScope
@Component(      
    modules = SplashActivityModule.class,
    dependencies = AppComponent.class
)
public interface SplashActivityComponent {
    SplashActivity inject(SplashActivity splashActivity);

    SplashActivityPresenter presenter();
}

@Scope 注解

@Scope
public @interface ActivityScope {
}

用于定义自定义作用域注解。有点类似于单例,不同的是,单例的作用域是整个application,而自定义作用域可以自定义(特么废话么)。下面还会详解Scope,这里按下不表。先说好,Scope是拿来干这个的:保持对象的单一实例。

然后附赠一些不咋用,不太重要的东西:

@MapKey 注解
用于定义依赖的集合。
定义:

@MapKey(unwrapValue = true)
@interface TestKey {
    String value();
}

提供依赖:

@Provides(type = Type.MAP)
@TestKey("foo")
String provideFooKey() {
    return "foo value";
}

@Provides(type = Type.MAP)
@TestKey("bar")
String provideBarKey() {
    return "bar value";
}

使用:

@Inject
Map map;

map.toString() // => „{foo=foo value, bar=bar value}”

@Qualifier 注解
给高阶程序猿(强迫症)准备的,用于为继承自同一个接口的依赖打 TAG 来区分他们。比如有两个Adapter继承自同一个Adapter接口,你还想让代码看上去整齐有型,就这么干。
命名依赖:

@Provides
@Singleton
@GithubRestAdapter  //Qualifier
RestAdapter provideRestAdapter() {
    return new RestAdapter.Builder()
        .setEndpoint("https://api.github.com")
        .build();
}

@Provides
@Singleton
@FacebookRestAdapter  //Qualifier
RestAdapter provideRestAdapter() {
    return new RestAdapter.Builder()
        .setEndpoint("https://api.facebook.com")
        .build();
}

注入依赖:

@Inject
@GithubRestAdapter
RestAdapter githubRestAdapter;

@Inject
@FacebookRestAdapter
RestAdapter facebookRestAdapter;

@Singleton 注解(Java自带)
Dagger不单可以注入实例,还可以注入单例。当年老大要咱们做单元测试,结果因为项目里各种单例用得太乱导致大量代码处于不可测的状态。一开始注意到Dagger也是因为有了Dagger,就不需要写那种很难Mock对象的单例了。
我们更改一下一开始的示例的界面,加一个TextView,MainActivity如下:

public class MainActivity extends AppCompatActivity {
    private TextView textView1;
    private TextView textView2;

    @Inject
    UserModel user1;

    @Inject
    UserModel user2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView1 = (TextView) findViewById(R.id.text_view_1);
        textView2 = (TextView) findViewById(R.id.text_view_2);

        DaggerUserComponent.builder()
                .userModule(new UserModule())
                .build()
                .inject(this);
        user2.setName("Stark");
        user2.setAge(10);
        textView1.setText("Name:" + user1.getName() + "::Age:" + user1.getAge());
        textView2.setText("Name:" + user2.getName() + "::Age:" + user2.getAge());
    }
}

可以看到这里我注入了两个UserModel的实例,在注入后重新设置user2的name和Age,然后显示在两个TextView上。结果如图:


Dagger2使用学习笔记_第2张图片
differentName.png

可以看到,上面的user1还是我在Module中创建的UserModel实例,user2是另一个独立的对象,所以它们之间互不干扰。
好,下面我们改动一丢丢Component和Module的代码,如下:

@Component(modules = {UserModule.class})
@Singleton
public interface UserComponent {
    void inject(MainActivity mainActivity);
}
@Module
public class UserModule {

    UserModule() {}

    @Provides
    @Singleton
    UserModel provideUsers() {
        UserModel user = new UserModel();
        user.setName("lala");
        user.setAge(18);
        return user;
    }
}

为Component加了 @Singleton 的类注解,为Module创建相应实例的方法加了同样的 @Singleton 注解,再运行一下看看。


Dagger2使用学习笔记_第3张图片
singleton.png

恩,就是这么简单,我只更改了user2的数据,结果user1也更改了,因为它们指向同一个对象。就这样,成功使用Dagger注入了单例。且这个单例的代码是可测的,因为它的实例很容易Mock。

Scope 详解
Scope,直译过来是范围、圈子的意思。就像前面提到的,在Dagger中,Scope用来保证在指定作用域中,拿到的依赖对象是唯一的(指定范围内是单例)。比如,我有一个登录系统,用户登录后,一直到登出前,拿到的UserModel就应该是单例。此时,我可以自定义一个UserScope的作用域,即实现一个 @UserScope 注解,用此注解标识的Component在指定的范围内(从用户登录到用户登出)注入的一定是同一个实例。有没有很爽的感觉。。。
Dagger是没有默认自带的各种Scope的,什么 @ActivityScope 啊,@ApplicationScope 啊统统没有,只有Java自带的一个@Singleton,也就是上面讲到的那个,它与Apllication处于同一作用域,从App开启到结束只有一个实例。那么下面我们来看看如何自定义一个 Scope 注解。

Scope的实现在Dagger2里头就是对Component做正确的配置工作。有两种方式实现自定义Scope,使用 @Subcomponent 注解 或 使用 Components 依赖。这两种实现方式最终出来的结果是不同的,使用 @Subcomponent 注解的话,子域可以获取到父域的所有依赖;而如果使用 Components 依赖,则子域只能获取到父域通过Component接口暴露出来的依赖。

先来看看使用 @Subcomponent 注解的方式实现:

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

    UserComponent plus(UserModule userModule);

    SplashActivityComponent plus(SplashActivityModule splashActivityModule);

}

可以看到,在AppComponent接口中有两个plus方法,这两个方法的意思是,我们可以从APPComponent中创建两个子Component: UserComponent 和 SplashActivityComponent. 因为它们是AppComponent的子Component,所以都可以获取到AppModule和GithubApiModule产生的实例。
规则:返回类型是子Component类型,方法名任性着来,参数是子Component所需的就行。

可以看到,UserComponent需要另一个Module,该Module被当做plus方法的参数传入。这样,我们我们用新Module产生的额外对象拓展了AppComponent可注入的依赖表。UserComponent如下:

@UserScope
@Subcomponent(
        modules = {
                UserModule.class
        }
)
public interface UserComponent {
    RepositoriesListActivityComponent plus(RepositoriesListActivityModule repositoriesListActivityModule);

    RepositoryDetailsActivityComponent plus(RepositoryDetailsActivityModule repositoryDetailsActivityModule);
}

@UserScope 注解肯定是自定义的咯:

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface UserScope {
}

在UserComponent中,可以创建另外两个子Component: RepositoriesListActivityComponent 和 RepositoryDetailsActivityComponent.
重要的东西在这儿呢。所有从UserComponent拿到的从AppComponent继承下来的实例都是单例(范围是Application)。而那些UserModule自己产生的实例都是“本地单例”,其周期就是UserComponent存在的周期。
所以,每次我们调用UserComponent userComponent = appComponent.plus(new UserComponent(user));时,从userComponent拿到的对象都是不同的实例。
对于这个我们自定义的UserScope,它的生命周期当然也得咱们自己维护,要维护它的初始化和销毁。

public class GithubClientApplication extends Application {

    private AppComponent appComponent;
    private UserComponent userComponent;

    //...

    public UserComponent createUserComponent(User user) {
        userComponent = appComponent.plus(new UserModule(user));
        return userComponent;
    }

    public void releaseUserComponent() {
        userComponent = null;
    }

    //...
}

当我们从网络获取到User对象时,调用createUserComponent方法,UserScope从此时开始。当RepositoriesListActivity finish时,调用releaseUserComponent终结UserScope。

到此为止,对于Dagger的使用已经学习地差不多了。但是只知道怎么用怎么能满足咱们欲求不满的内心呢?所以下一篇要赏析一下Dagger自动生成的代码,来看一下整个注入流程是如何走通的。

你可能感兴趣的:(Dagger2使用学习笔记)