Dagger 2: Step To Step

假设你已经了解 依赖注入 这一概念,只是在如何使用 Dagger 时遇到了一些困扰,因为 Dagger 其实是一个上手难度颇高的库。我试图通过这篇文章解决如何上手这一问题。

目前 Dagger 有两个分支,一个由 Square 维护,一个为 Google 在前者的基础上开出的分支,即 Dagger 2 。关于二者的比较,点击此处

本文写作过程中参考了不少优秀的 Dagger 文章,列在文章末尾。

在此一并感谢他们的工作!

Dagger

在引入 Dagger 之前,我们需要了解一些基础概念。Dagger 主要分三块:

  • @Inject:需要注入依赖的地方,Dagger 会构造一个该类的实例并满足它所需要的依赖;
  • @Module:依赖的提供者,Module 类中的方法专门提供依赖,并用 @Provides 注解标记;
  • @Component:依赖的注入者,是 @Inject@Module 的桥梁,它从 @Module 中获取依赖并注入给 @Inject

对于以上关系,一句话解释就是:模块(Module)负责提供依赖,组件(Component)负责注入依赖。

Sourcecode

源码放在 Github: DaggerDemo

Gradle

app/build.gradle

    
    dependencies {
        ...
    
       // Add Dagger dependencies,2017.04.26
      compile 'com.google.dagger:dagger:2.11-rc1'
      annotationProcessor 'com.google.dagger:dagger-compiler:2.11-rc1'
    }
    ```

## Example
Demo 实现一个简单的 `ListView` 显示字符串列表

1. 创建 `UserAdapter` 类,并在构造函数前添加 **@Inject** 注解。这样,Dagger 就会在需要获取 `UserAdapter` 对象时,调用这个被标记的构造函数,从而生成一个 `UserAdapter` 对象。
    ```java
    public class UserAdapter extends BaseAdapter {
        private LayoutInflater inflater;
        private List<String> users;
    
        @Inject
        public UserAdapter(Context ctx, List<String> users) {
            this.inflater = LayoutInflater.from(ctx);
            this.users = users;
        }

        ...
    }
    ```
    
    需要注意的是:如果构造函数含有参数,Dagger 会在调用构造对象的时候先去获取这些参数(不然谁来传参?),所以你要保证它的参数也提供可被 Dagger 调用到的生成函数。Dagger 可调用的对象生成方式有两种:一种是用 **@Inject** 修饰的构造函数,上面就是这种方式。另外一种是用 **@Provides** 修饰的函数,下面会讲到。[参考:Dagger 源码解析](http://a.codekk.com/detail/Android/%E6%89%94%E7%89%A9%E7%BA%BF/Dagger%20%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90)

2. 构建 Module,提供 Context 和 List<String> 依赖,如此, Dagger 生成 UserAdapter 时所需要的依赖就从这里获取。
    ```java
    @Module
    public class UserModule {
        private static final int COUNT = 10;
    
        private final Context context;
    
        public UserModule(Context context) {
            this.context = context;
        }
    
        @Provides
        @ActivityScope
        Context provideActivityContext() {
            return context;
        }
    
        @Provides
        @ActivityScope
        List<String> provideUsers() {
            List<String> users = new ArrayList<>(COUNT);
    
            for (int i = 0; i < COUNT; i++) {
                users.add("item " + i);
            }
    
            return users;
        }
    }
    ```

    **@ActivityScope** 是一个自定义的范围注解,作用是允许对象被记录在正确的组件中,当然这些对象的生命周期应该遵循 Activity 的生命周期。
    ```java
    import java.lang.annotation.Retention;
    import static java.lang.annotation.RetentionPolicy.RUNTIME;
    
    import javax.inject.Scope;
    
    @Scope
    @Retention(RUNTIME)
    public @interface ActivityScope {
    
    }
    ```

3. 构建 Component,负责注入依赖
    ```java
    @ActivityScope
    @Component(modules = {UserModule.class})
    public interface UserComponent {
        void inject(MainActivity activity);
    }
    ```

    **注意**:这里必须是真正消耗依赖的类型 `MainActivity`,而不可以写成其父类,比如   `Activity` ,否则会导致注入失败。([参考:使用Dagger 2进行依赖注入](http://codethink.me/2015/08/06/dependency-injection-with-dagger-2/))

4. 完成依赖注入
    ```java
    public class MainActivity extends AppCompatActivity {

        ...
    
        @Inject
        UserAdapter adapter;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            
            ...
    
            // 完成注入
            DaggerUserComponent.builder()
                .userModule(new UserModule(this))
                .build()
                .inject(this);
    
            listView.setAdapter(adapter);
        }
    }
    ```

    **如果找不到 `DaggerUserComponent` 类,你需要先编译一下整个项目。**这是因为 Dagger 是在编译时生成必要的元素,编译时 Dagger 会处理我们的注解,为 @Components 生成实现并命名为 `Dagger$${YouComponentClassName}`,如 `UserComponent` -> `DaggerUserComponent` 。你可以在 `app/build/generated/source/apt` 下找到相关的类。

    实际上,调用 inject 方法最终调用的是以下这样一段代码,更多细节可以查看源码。
    ```java
    @Override
    public void injectMembers(MainActivity instance) {  
      if (instance == null) {
        throw new NullPointerException("Cannot inject members into a null reference");
      }
      supertypeInjector.injectMembers(instance);
      instance.adapter = adapterProvider.get();
    }
    ```

## Dagger too
1. @Inject 和 @Provide 两种依赖生成方式的区别:
    - @Inject 用于注入可实例化的类,@Provides 可用于注入所有类
    - @Inject 可用于修饰属性和构造函数,可用于任何非 Module 类,@Provides 只可用于用于修饰非构造函数,并且该函数必须在某个Module内部
    - @Inject 修饰的函数只能是构造函数,~~@Provides 修饰的函数必须以 provide 开头~~

2. Dagger 的其他注解:

    - **@Scope**: Dagger 可以通过自定义注解限定注解作用域,参考前面的 **@ActivityScope**。
    - **@Qualifier**:限定符,当类的类型不足以鉴别一个依赖的时候,我们就可以使用这个注解来区分。例如:在 Android 中,我们会需要不同类型的 Context,所以我们可以定义 @Qualifier 注解 `@ForApplication` 和 `@ForActivity`,这样当注入一个 Context 的时候,我们就可以告诉  Dagger 我们想要哪种类型的 Context。
    - **@Singleton**:单例模式,依赖的对象只会被初始化一次

3. Dagger 的实际应用:本例只是一个上手教程,辅助理解 Dagger 的原理及使用方式,具体的项目应用可以参考 Reference 中第 3 条的 [Avengers 的源码](https://github.com/saulmm/Avengers) 。

## Practice
Google 最近新开了一个 repo:[android-architecture](https://github.com/googlesamples/android-architecture/tree/todo-mvp-dagger_fixProguard),不仅有多种架构方案,还有完整的测试,是非常好的学习项目。

## Reference

1. [Dagger 2 Official Documentation](http://google.github.io/dagger/)
2. [Tasting Dagger 2 on Android](http://fernandocejas.com/2015/04/11/tasting-dagger-2-on-android/),中文:[详解 Dagger 2](http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0519/2892.html)
3. [When the Avengers meet Dagger2, RxJava and Retrofit in a clean way](http://saulmm.github.io/when-Thor-and-Hulk-meet-dagger2-rxjava-1),中文:[当复仇者联盟遇上 Dragger2、RxJava 和 Retrofit 的巧妙结合](http://www.devtf.cn/?p=565)
4. [使用 Dagger 2 进行依赖注入](http://codethink.me/2015/08/06/dependency-injection-with-dagger-2/)
5. [Dagger 源码解析](http://a.codekk.com/detail/Android/%E6%89%94%E7%89%A9%E7%BA%BF/Dagger%20%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90) (PS: 这是 Dagger 1,但是很有参考价值)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,561评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,218评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,162评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,470评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,550评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,806评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,951评论 3 407
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,712评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,166评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,510评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,643评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,306评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,930评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,745评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,983评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,351评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,509评论 2 348

推荐阅读更多精彩内容