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());
    }
}

效果:


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<String, String> 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上。结果如图:


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 注解,再运行一下看看。


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自动生成的代码,来看一下整个注入流程是如何走通的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容