Dagger2使用探索

Dagger是针对Java和Android的编译时依赖注入框架。

依赖注入(Dependency Injection)和控制反转(Inversion of Control)是同一个概念。具体含义是:当某个角色(可能是一个Java实例,调用者)需要另一个角色(另一个Java实例,被调用者)的协助时,在 传统的程序设计过程中,通常由调用者来创建被调用者的实例。

  • 原始社会里,几乎没有社会分工。需要斧子的人(调用者)只能自己去磨一把斧子(被调用者)。对应的情形为:Java程序里的调用者自己创建被调用者。
  • 进入工业社会,工厂出现。斧子不再由普通人完成,而在工厂里被生产出来,此时需要斧子的人(调用者)找到工厂,购买斧子,无须关心斧子的制造过程。对应Java程序的简单工厂的设计模式。
  • 进入“按需分配”社会,需要斧子的人不需要找到工厂,坐在家里发出一个简单指令:需要斧子。斧子就自然出现在他面前。对应Dagger的依赖注入。

最初由Square创建Dagger1(现已弃用),而Dagger2Dagger1的分支,现在由谷歌公司接手开发。


为什么要使用Dagger2

  • 许多Android应用程序通常需要其他依赖关系的实例化对象。例如,可以使用诸如Retrofit的网络库来构建Twitter API客户端。要使用此库,您可能还需要添加解析库,例如Gson。此外,实现身份验证或缓存的类可能需要访问SharedPreferences或其他常见存储,需要首先实例化它们并创建固有的依赖关系链。

  • Dagger 2分析这些依赖关系,并生成代码以帮助将它们连接在一起。虽然还有其他的Java依赖注入框架,但是许多在依赖XML的过程中受到限制,需要在运行时验证依赖关系问题,或者在启动过程中产生性能损失。

  • Dagger 2完全依赖于使用Java 注释处理器和编译时检查来分析和验证依赖关系。它被认为是迄今为止建立的最有效的依赖注入框架之一。

  • Dagger2使用依赖注入(Dependency Injection简称DI),而使用它有以下好处:

    • 依赖的注入和配置独立于组件之外。
    • 因为对象是在一个独立、不耦合的地方初始化,所以当注入抽象方法的时候,我们只需要修改对象的实现方法,而不用大改代码库。
    • 依赖可以注入到一个组件中:我们可以注入这些依赖的模拟实现,这样使得测试更加简单。
  • Dagger2与Dagger1的区别:

    • 再也没有使用反射:图的验证、配置和预先设置都在编译的时候执行。
    • 容易调试和可跟踪:完全具体地调用提供和创建的堆栈
    • 更好的性能:谷歌声称他们提高了13%的处理性能
    • 代码混淆:使用派遣方法,就如同自己写的代码一样

当然有优势也有缺点:相比Dagger1它缺乏灵活性,例如(Dagger2没用反射所以没有动态机制)

注解含义了解

  • @Inject: 通常在需要依赖的地方使用这个注解。换句话说,你用它告诉Dagger这个类或者字段需要依赖注入。这样,Dagger就会构造一个这个类的实例并满足他们的依赖。

  • @Module: Modules类里面的方法专门提供依赖,所以我们定义一个类,用@Module注解,这样Dagger在构造类的实例的时候,就知道从哪里去找到需要的依赖。modules的一个重要特征是它们设计为分区并组合在一起(比如说,在我们的app中可以有多个组成在一起的modules)。

  • @Provide: 在modules中,我们定义的方法是用这个注解,以此来告诉Dagger我们想要构造对象并提供这些依赖。

  • @Component: Components从根本上来说就是一个注入器,也可以说是@Inject和@Module的桥梁,它的主要作用就是连接这两个部分。 Components可以提供所有定义了的类型的实例,比如:我们必须用@Component注解一个接口然后列出所有的@Modules组成该组件,如 果缺失了任何一块都会在编译的时候报错。所有的组件都可以通过它的modules知道依赖的范围。

  • @Scope: Scopes可是非常的有用,Dagger2可以通过自定义注解限定注解作用域。这是一个非常强大的特点,没必要让每个对象都去了解如何管理他们的实例。比方说我们用自定义的@ActivityScoped注解一个类,所以这个对象存活时间就和 activity的一样。简单来说就是我们可以定义所有范围的粒度(@FragmentScoped, @UserScoped, 等等)。

  • @Qualifier: 当类的类型不足以鉴别一个依赖的时候,我们就可以使用这个注解标示。例如:在Android中,我们会需要不同类型的context,所以我们就可以定义 qualifier注解“@ForApplication”和“@ForActivity”,这样当注入一个context的时候,我们就可以告诉 Dagger我们想要哪种类型的context。

使用Dagger2的一个简单示例

导入Dagger2依赖

  • 当前版本:目前2017/4/24号最新版本是2.10
  • 开源地址: https://github.com/google/dagger
  • 文档API:https://google.github.io/dagger/api/2.10/
  • app Gradle 引入依赖
    1
    2
    3
    4
    5
    dependencies {
    compile 'com.google.dagger:dagger:2.10'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.10'
    provided 'javax.annotation:jsr250-api:1.0'
    }

    创建AppModule (可以提供改造实例的类)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    @Module
    public class AppModule {

    Application mApplication;
    String mUrl;

    public AppModule(Application application,String url) {
    this.mApplication = application;
    this.mUrl = url;
    }

    @Provides
    @Singleton
    Application providesApplication() {
    return mApplication;
    }

    @Provides
    @Named("HomeUrl")
    String providesHomeUrl() {
    return mUrl+"/home";
    }

    @Provides
    @Named("ListUrl")
    String providesHomeListUrl(@Named("HomeUrl") String home) {
    return home+"/list";
    }

    }
    注意
  • 需要使用注解“@Module”标记、用于Dagger搜索可用的方法可能的实例提供者。
  • 暴露可用返回类型的方法应该用“@Provides”来注释标记,用于将此实例化与其他类型的其他模块关联在一起。
  • @Singleton 表示该实例在应用程序中只创建一次。
  • providesApplication providesHomeUrl 等方法名不是固定写法,可以取任意方法名。
  • @Named(“yourName”) 如果我们需要两个相同返回类型的不同对象,我们可以使用它注释标记。
    注意:@Named是一个限定符是由Dagger2先定义的,但你也可以创建自己的限定词注释
    1
    2
    3
    4
    5
    @Qualifier
    @Documented
    @Retention(RUNTIME)
    public @interface DefaultHomeUrl {
    }

创建AppComponent

1
2
3
4
5
6
7
@Singleton
@Component(modules={AppModule.class})
public interface AppComponent {
void inject(MainActivity activity);
// void inject(MyFragment fragment);
// void inject(MyService service)
}

注意

  • @component在Dagger2称为组件。它在我们的 activities,services或fragments 中分配。
    我们需要使用@Component声明来注释这个类。请注意,可以使用单独的inject()方法在此类中声明可以添加的activities,services或fragments
    请注意,基类不足以作为注射目标。Dagger 2依赖于强类型类,因此您必须明确指定应定义哪些类。
  • Dagger 2的一个重要方面是库生成使用@Component接口注释的类的代码。
    您可以使用前缀为Dagger的类(即DaggerAppComponent.java),该类将负责实例化依赖图的实例,
    并使用它来对使用@Inject注释的字段执行注入工作。

实例化AppComponent组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MyApp extends Application {

private AppComponent mAppComponent;

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

// Dagger%COMPONENT_NAME%
mAppComponent = DaggerAppComponent.builder()
//该组件的一部分的module列表也需要在这里创建
.appModule(new AppModule(this,"https://api.github.com")) // 该方法也对应于您的模块的名称
.build();

// 如果Dagger2 component不为其任何模块的任何构造函数参数,我们可以使用.create()作为快捷方式:
// mAppComponent = com.codepath.dagger.components.DaggerAppComponent.create();
}

public AppComponent getAppComponent() {
return mAppComponent;
}
}

注意:

  • 如果无法引用 Dagger2 component,请确保重新生成项目(在Android Studio中,选择“Build > Rebuild Project”)。
    因为我们是重写默认的应用程序类,并修改应用程序名称MyApp。这样您的应用程序将使用这个应用程序类来处理初始实例化。
    1
    2
    3
    <application
    android:allowBackup="true"
    android:name=".MyApp">

使用@Inject初始化需要的实例

1
2
3
4
5
6
7
8
9
public class MyActivity extends Activity {
@Inject Application mApplication;
@Inject @Named("HomeUrl") String mHomeUrl;
@Inject @Named("ListUrl") String mListUrl;

public void onCreate(Bundle savedInstance) {
((MyApp) getApplication()).getAppComponent().inject(this);
}
}

注:以上就是一个简单的Dagger2使用实例

高级用法

@Scope自定义范围来封装Component组件

1
2
3
4
5
@Scope
@Documented
@Retention(value=RetentionPolicy.RUNTIME)
public @interface MyActivityScope{
}

注意:

  • 您可以创建仅持续 activity 或 fragment 生命周期持续时间的作用域。您可以创建仅映射到user认证会话的作用域(即:用户登陆到退出这个过程)。

Dependent Components组件 vs 子组件Subcomponents

  • 利用scopes,我们可以创建依赖组件Dependent Components或子组件Subcomponents。
  • 上面的简单示例表明我们使用了@Singleton持续整个应用程序生命周期的符号。我们还依赖于一个主要的component。
  • 如果我们希望有多个组件不需要一直保留在内存中(即绑定到activity或fragment的生命周期的组件,甚至与用户登录时绑定的组件),我们可以创建依赖组件Dependent Components或子组件Subcomponents。在这两种情况下,每种情况都提供了一种封装代码的方式。
  • 依赖组件要求父组件明确列出可以向下游注入的依赖关系,而子组件则不包含。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// parent component
@Singleton
@Component(modules={AppModule.class, NetModule.class})
public interface NetComponent {
// 如果下游模块将执行注射 删除 injection 方法

//下游组件需要这些暴露
//方法名不重要,只有返回类型
Application providesApplication();

// ... 需要NetModule提供
Retrofit retrofit();
OkHttpClient okHttpClient();
SharedPreferences sharedPreferences();
}

如果您忘记添加此行,则可能会看到有关注射目标缺失的错误。类似于如何管理私有/公共变量,使用父组件允许更明确的控制和更好的封装,但是使用子组件使得依赖关系注入更容易管理,而牺牲了更少的封装。

注意:

  • 两个相关组件不能共享相同的范围。例如,两个组件不能同时作用于@Singleton注释。由于这里描述的原因,这种限制是强制的。依赖组件需要定义自己的范围。
  • 虽然Dagger 2还能够创建范围限定的实例,但责任在于您创建和删除与预期行为一致的引用。
Dependent Components 依赖组件

例如,如果我们希望使用为登录到应用程序的用户会话的整个生命周期创建的组件,我们可以定义我们自己的UserScope界面:

1
2
3
4
5
6
import java.lang.annotation.Retention;
import javax.inject.Scope;

@Scope
public @interface UserScope {
}

接下来,我们定义父组件:

1
2
3
4
5
6
7
@Singleton
@Component(modules={AppModule.class, NetModule.class})
public interface NetComponent {
// downstream components need these exposed with the return type
// method name does not really matter
Retrofit retrofit();
}

NetModule.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@Module
public class NetModule {

String mBaseUrl;

// Constructor needs one parameter to instantiate.
public NetModule(String baseUrl) {
this.mBaseUrl = baseUrl;
}

// Dagger will only look for methods annotated with @Provides
@Provides
@Singleton
// Application reference must come from AppModule.class
SharedPreferences providesSharedPreferences(Application application) {
return PreferenceManager.getDefaultSharedPreferences(application);
}

@Provides
@Singleton
Cache provideOkHttpCache(Application application) {
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(application.getCacheDir(), cacheSize);
return cache;
}

@Provides
@Singleton
Gson provideGson() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
return gsonBuilder.create();
}

@Provides
@Singleton
OkHttpClient provideOkHttpClient(Cache cache) {
OkHttpClient client = new OkHttpClient();
client.setCache(cache);
return client;
}

@Provides
@Singleton
Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(mBaseUrl)
.client(okHttpClient)
.build();
return retrofit;
}
}

然后,我们可以定义一个子组件:

1
2
3
4
5
@UserScope // 使用先前定义的范围,请注意,“@Singleton”将无法工作
@Component(dependencies = NetComponent.class, modules = GitHubModule.class)
public interface GitHubComponent {
void inject(MainActivity activity);
}

让我们假设这个GitHub模块简单地返回一个API接口到GitHub API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Module
public class GitHubModule {

public interface GitHubApiInterface {
@GET("/org/{orgName}/repos")
Call<ArrayList<Repository>> getRepository(@Path("orgName") String orgName);
}

@Provides
@UserScope // needs to be consistent with the component scope
public GitHubApiInterface providesGitHubInterface(Retrofit retrofit) {
return retrofit.create(GitHubApiInterface.class);
}
}

为了GitHubModule.java使其可以访问该Retrofit实例,我们需要在上游组件中明确定义它们。如果下游模块将执行注入,也应该从上游组件中移除:

1
2
3
4
5
6
7
8
9
10
@Singleton
@Component(modules={AppModule.class, NetModule.class})
public interface NetComponent {
// 如果下游模块将执行注射 删除 injection 方法

//下游组件需要这些暴露
Retrofit retrofit();
OkHttpClient okHttpClient();
SharedPreferences sharedPreferences();
}

最后一步是使用它GitHubComponent来执行实例化。这一次,我们首先需要构建并将NetComponent其传递给DaggerGitHubComponent构建器的构造函数:

1
2
3
4
5
6
7
8
9
NetComponent mNetComponent = DaggerNetComponent.builder()
.appModule(new AppModule(this))
.netModule(new NetModule("https://api.github.com"))
.build();

GitHubComponent gitHubComponent = DaggerGitHubComponent.builder()
.netComponent(mNetComponent)
.gitHubModule(new GitHubModule())
.build();

有关一个工作示例,请参阅此示例代码dagger2-example

Subcomponents 子组件
  • 使用子组件是扩展组件对象图的另一种方法。与具有依赖关系的组件相似,子组件具有自己的生命周期,并且在对子组件的所有引用都消失时可以进行垃圾回收,并具有相同的范围限制。
  • 使用此方法的一个优点是您不需要定义所有下游组件。
  • 另一个主要的区别是,子组件只需要在父组件中声明。

以下是使用子组件进行活动的示例。我们用自定义范围和注释来注释类@Subcomponent:

1
2
3
4
5
@MyActivityScope
@Subcomponent(modules={ MyActivityModule.class })
public interface MyActivitySubComponent {
@Named("my_list") ArrayAdapter myListAdapter();
}

将使用的模块定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Module
public class MyActivityModule {
private final MyActivity activity;

// 必须用activity进行实例化
public MyActivityModule(MyActivity activity) { this.activity = activity; }

@Provides @MyActivityScope @Named("my_list")
public ArrayAdapter providesMyListAdapter() {
return new ArrayAdapter<String>(activity, android.R.layout.my_list);
}
...
}

最后,在父组件中,我们将使用组件的返回值和实例化所需的依赖关系定义一个工厂方法:

1
2
3
4
5
6
7
8
@Singleton
@Component(modules={ ... })
public interface MyApplicationComponent {
// injection 注射方法在这里添加

//工厂方法来实例化这里定义的子组件(在模块实例中传递)
MyActivitySubComponent newMyActivitySubcomponent(MyActivityModule activityModule);
}

在上述示例中,每次newMyActivitySubcomponent()调用子组件的新实例将被创建。要使用子模块注入一个活动:

1
2
3
4
5
6
7
8
9
10
11
public class MyActivity extends Activity {
@Inject ArrayAdapter arrayAdapter;

public void onCreate(Bundle savedInstance) {
// assign singleton instances to fields
// We need to cast to `MyApp` in order to get the right method
((MyApp) getApplication()).getApplicationComponent())
.newMyActivitySubcomponent(new MyActivityModule(this))
.inject(this);
}
}
Subcomponent Builders 子部件 构建器 制造商
  • 从v2.7开始
  • 子组件构建器允许子组件的创建者通过删除在该父组件上声明的子组件工厂方法的需要从父组件解除耦合。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @MyActivityScope
    @Subcomponent(modules={ MyActivityModule.class })
    public interface MyActivitySubComponent {
    ...
    @Subcomponent.Builder
    interface Builder extends SubcomponentBuilder<MyActivitySubComponent> {
    Builder activityModule(MyActivityModule module);
    }
    }
    public interface SubcomponentBuilder<V> {
    V build();
    }
    子组件在子组件接口中被声明为内部接口,它必须包含一个build()返回类型与子组件匹配的方法。如上所述,使用此方法声明基础接口很方便SubcomponentBuilder。必须使用带有“@Subcomponent”参数的“binder”模块将此新构建器添加到父组件图中:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Module(subcomponents={ MyActivitySubComponent.class })
    public abstract class ApplicationBinders {
    // 将生成器包含在用于创建生成器的映射中。
    @Binds @IntoMap @SubcomponentKey(MyActivitySubComponent.Builder.class)
    public abstract SubcomponentBuilder myActivity(MyActivitySubComponent.Builder impl);
    }
    @Component(modules={..., ApplicationBinders.class})
    public interface ApplicationComponent {
    // 返回由其类映射的所有建设者的映射。
    Map<Class<?>, Provider<SubcomponentBuilder>> subcomponentBuilders();
    }
    // 只需要创建上面的映射
    @MapKey @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME)
    public @interface SubcomponentKey {
    Class<?> value();
    }
    一旦构建器在组件图中可用,该活动可以使用它来创建其子组件:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class MyActivity extends Activity {
    @Inject ArrayAdapter arrayAdapter;

    public void onCreate(Bundle savedInstance) {
    // 指定singleton实例字段
    // 我们需要投` MyApp `为了得到正确的方法
    MyActivitySubcomponent.Builder builder = (MyActivitySubcomponent.Builder)
    ((MyApp) getApplication()).getApplicationComponent())
    .subcomponentBuilders()
    .get(MyActivitySubcomponent.Builder.class)
    .get();
    builder.activityModule(new MyActivityModule(this)).build().inject(this);
    }
    }

    ProGuard

    匕首2应该不需要ProGuard即可开箱即用,但如果您开始查看library class dagger.producers.monitoring.internal.Monitors$1 extends or implements program class javax.inject.Provider,请确保您的Gradle配置使用annotationProcessor声明而不是provided

故障排除

  • 如果您正在升级Dagger 2版本(即从v2.0到v2.5),则某些生成的代码已更改。如果您正在将其用旧版本生成的匕首代码,您可能会看到MemberInjector和actual and former argument lists different in length错误。确保清理整个项目,并确认已升级所有版本以使用一致版本的Dagger 2。

参考博客