Android - Dagger2使用详解

老婆保佑,代码无BUG

前言

2018年,大家开心哈,本文是在2018年第一天写完的,历时一年才完成,哈哈2018,大家加油


目录

  • 一:Dagger2是什么?
  • 二:为什么要有Dagger2
  • 三:Dagger2如何使用
      1. 基本的概念
      1. 如何使用Dagger2
      1. 高级用法
      • (1)构造方法需要其他参数时候
      • (2) 模块之间的依赖关系
      • (3) @Named注解使用
      • (4) @Singleton注解
      • (5)自定义Scoped
      • (6)Subcomponent
      • (7)lazy 和 Provider
  • 四: MVP + Dagger2

一:Dagger2是什么?

是一个依赖注入框架,butterknife也是一个依赖注入框架。不过butterknife,最多叫奶油刀,Dagger2被叫做利器啊,他的主要作用,就是对象的管理,其目的是为了降低程序耦合。

二:为什么要有Dagger2

u=2303921310,3094597953&fm=27&gp=0.jpg

下面我就手写了

public class A {
    public void eat() {
        System.out.print("吃饭了");
    }
}

使用的时候我们就要

A a = new A();
a.eat();

如果现在改了,早A的构造方法中必须传入B对象

public class A {
    private B b;
    public A(B b) {
        this.b = b;
    }
    public void eat() {
        System.out.print("吃饭了");
    }
}

那么使用的时候

A a = new A(new B());
a.eat();

可能就有人说了,不就加一个对象么,这里只是我举的一个很简单的例子,看的感觉很简单,但是在实际开发中,如果现在改了一个这个构造方法。是不是意味着,整个项目中的都的改,一不小心, 就是BUG 啊

额 呵呵

三:Dagger2如何使用

1. 基本的概念

上来给你说,怎么玩,肯定懵逼,这里我简单说一下几个概念,想有个认知,在往下看,会好很多,Dagger 是通过@Inject使用具体的某个对象,这个对象呢,是由@Provides注解提供,但是呢,这个@Provides只能在固定的模块中,也就是@Module注解,我们查找的时候,不是直接去找模块,而是去找@Component

屏幕快照 2017-12-29 下午1.51.18.png

我们反向推到,当们使用

@Inject
A a

想要获取a对象的示例的时候,Dagger2 会先去找,当前Activity或者Fragment所连接的桥梁,例如上图中,连接的只有一个桥梁,实际上可以有多个,这个桥梁,会去寻找他所依赖的模块,如图中,依赖了模块A,和模块B,然后在模块中,会去寻找@Providers注解,去寻找A的实例化对象。

2. 如何使用Dagger2

(1) 引入依赖库

Dagger2官网

    compile 'com.google.dagger:dagger:2.11'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.11'

(2) 创建Moudule

//第一步 添加@Module 注解
@Module
public class MainModule {
}

(3)创建具体的示例

//第一步 添加@Module 注解
@Module
public class MainModule {
    //第二步 使用Provider 注解 实例化对象
    @Provides
    A providerA() {
        return new A();
    }
}

(4)创建一个Component

//第一步 添加@Component
//第二步 添加module
@Component(modules = {MainModule.class})
public interface MainComponent {
    //第三步  写一个方法 绑定Activity /Fragment
    void inject(MainActivity activity);
}

(5)Rebuild Project

这一步是必须的

然后AS 会自动帮我们生成一个


屏幕快照 2017-12-30 下午12.53.05.png

开头都是以Dagger开始的

(6)将Component与Activity/Fragment绑定关系

package com.allens.daggerdemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.allens.daggerdemo.Bean.A;
import com.allens.daggerdemo.component.DaggerMainConponent;
import javax.inject.Inject;

public class MainActivity extends AppCompatActivity {
    /***
     * 第二步  使用Inject 注解,获取到A 对象的实例
     */
    @Inject
    A a;

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

        /***
         * 第一步 添加依赖关系
         */
        //第一种方式
        DaggerMainConponent.create().inject(this);

        //第二种方式
        DaggerMainConponent.builder().build().inject(this);

        /***
         * 第三步  调用A 对象的方法
         */
        a.eat();
    }
}

肯定有小伙伴说了,为了拿到一个对象,这么大个弯,太麻烦了。别急慢慢看,路要一步一步走嘛

3. 高级用法

(1)构造方法需要其他参数时候

怎么说呢,就和最来时的意思一样,

A a = new A(new B());
a.eat();

这种情况,如何使用Dagger2呢
肯定有小伙伴这么想

   @Provides
    A providerA() {
        return new A(new B());
    }

直接 new 一个B ,这样的使用方法,是不对的!!!!!!,不对的!!!!!!!,不对的!!!!!!!!!

正确的打开方式

这时候,我们什么都不用该,只需要在moudule中添加一个依赖就可以了

@Module
public class MainModule {

    /***
     * 构造方法需要其他参数时候
     *
     * @return
     */
    @Provides
    B providerB() {
        return new B();
    }

    @Provides
    A providerA(B b) {
        return new A(b);
    }
}

(2) 模块之间的依赖关系

屏幕快照 2017-12-30 下午1.28.11.png

模块与模块之间的联系,

@Module (includes = {BModule.class})// includes 引入)
public class AModule {
    @Provides
    A providerA() {
        return new A();
    }
}

这样的话,Dagger会现在A moudule 中寻找对象,如果没找到,会去找module B 中是否有被Inject注解的对象,如果还是没有,那么GG,抛出异常

一个Component 应用多个 module

@Component(modules = {AModule.class,BModule.class})
public interface MainComponent {
    void inject(MainActivity activity);
}

dependencies 依赖其他Component

@Component(modules = {MainModule.class}, dependencies = AppConponent.class)
public interface MainConponent {
    void inject(MainActivity activity);
}

注意 这里有坑。一下会讲解


(3) @Named注解使用

相当于有个表示,虽然大家都是同一个对象,但是实例化对象不同就不如

A a1 = new A();
A a2 = new A();

// a1  a2 能一样嘛

Module中 使用@Named注解

@Module
public class MainModule {

    private MainActivity activity;

    public MainModule(MainActivity activity) {
        this.activity = activity;
    }


    @Named("dev")
    @Provides
    MainApi provideMainApiDev(MainChildApi mainChildApi, String url) {
        return new MainApi(mainChildApi, activity,"dev");
    }
    
    @Named("release")
    @Provides
    MainApi provideMainApiRelease(MainChildApi mainChildApi, String url) {
        return new MainApi(mainChildApi, activity,"release");
    }


}

在Activity/Fragment中使用

public class MainActivity extends AppCompatActivity {

    @Named("dev")
    @Inject
    MainApi apiDev;

    @Named("release")
    @Inject
    MainApi apiRelease;

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

        DaggerMainComponent.builder()
                .mainModule(new MainModule(this))
                .mainChildModule(new MainChildModule())
                .build()
                .inject(this);
        apiDev.eat();
        apiRelease.eat();
        Log.i("TAG","apiDev--->" + apiDev);
        Log.i("TAG","apiRelease--->" + apiRelease);
    }

}

打印Log

07-14 01:46:01.170 2006-2006/? I/TAG: apiDev--->com.allen.rxjava.MainApi@477928f
07-14 01:46:01.170 2006-2006/? I/TAG: apiRelease--->com.allen.rxjava.MainApi@f2b291c

(4) @Singleton注解

单利模式,是不是超级方便,你想然哪个对象单利化,直接在他的Provider上添加@Singleton 就行了

例如

    @Singleton
    @Provides
    A providerA(B b) {
        return new A(b);
    }

注意: 第一个坑!!!
如果 moudule所依赖的Comonent 中有被单利的对象,那么Conponnent也必须是单利的

@Singleton
@Component(modules = {MainModule.class})
public interface MainConponent {
}

然后 在Activity中使用,直接打印a1 a2 的地址,

    @Inject
    A a2;
    @Inject
    A a1;

可以看到Log

12-30 01:32:58.420 3987-3987/com.allens.daggerdemo E/TAG: A1---->com.allens.daggerdemo.Bean.A@11fa1ba
12-30 01:32:58.420 3987-3987/com.allens.daggerdemo E/TAG: A1---->com.allens.daggerdemo.Bean.A@11fa1ba

不相信的小伙伴可以吧@Singleton去掉试试

现在我们完成了单利,然后做了一个事情,就是点击某个按钮,跳转到一个新的Activiry,两边都引用同样一个A 对象,打印A 的地址,

说一下,一个Conponent 可以被对个Activity/Fragment 引用,如

@Singleton
@Component(modules = {MainModule.class})
public interface MainConponent {
    void inject(MainActivity activity);
    void inject(TestAct activity);
}

上面与两个Activity, MainActivity 和 TestAct ,都引用相同的 对象,答应地址看看

12-30 00:48:17.477 2788-2788/com.allens.daggerdemo E/TAG: A1---->com.allens.daggerdemo.Bean.A@11fa1ba
12-30 00:48:17.517 2788-2788/com.allens.daggerdemo E/TAG: A2---->com.allens.daggerdemo.Bean.A@4f81861

竟然不同,说好的单利呢

注意: 第二个坑,单利对象只能在同一个Activity中有效。不同的Activity 持有的对象不同

那有人就要问了,没什么办法么,我就想全局只要一个实例化对象啊? 办法肯定是有的,


(5) 自定义Scoped

/**
 * @作者 :  allens
 * @创建日期 :2017/7/14 下午3:04
 * @方法作用:
 * 参考Singleton 的写法
 * Scope 标注是Scope
 * Documented 标记在文档
 * @Retention(RUNTIME) 运行时级别
 */
@Scope
@Documented
@Retention(RUNTIME)
public @interface ActivityScoped {
}

首先想一下,什么样的对象,能够做到全局单例,生命周期肯定和APP 绑定嘛,这里我做演示,一个AppAip 我们要对这个对象,全局单利,所以二话不说,先给Application 来个全家桶,

Module

@Module
public class AppModule {

    @Singleton
    @Provides
    AppApi providerAppApi() {
        return new AppApi();
    }
}

Component

@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
    AppApi getAppApi();
}

Application

public class MyApp extends Application {

    private AppConponent appComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        appComponent = DaggerAppConpoment.create();
    }

    public AppConponent getAppComponent() {
        return appConponent;
    }
}

最后是如何使用

首先,这个是个桥梁,依赖方式,上文已经说过了

@ActivityScoped
@Component(modules = {MainModule.class}, dependencies = AppConponent.class)
public interface MainComponent {
    void inject(MainActivity activity);
    void inject(TestAct activity);
}

细心的小伙伴可能已经发现了,只有在上面一个MainComponent添加了一个@ActivityScoped,这里说明一下,@Singleton是Application 的单利

注意,第三个坑,子类component 依赖父类的component ,子类component的Scoped 要小于父类的Scoped,Singleton的级别是Application

所以,我们这里的@Singleton 级别大于我们自定义的@ActivityScoped,同时,对应module 所依赖的component ,也要放上相应的Scope

好吧,上面的例子,打印Log.

12-30 02:16:30.899 4717-4717/? E/TAG: A1---->com.allens.daggerdemo.Bean.AppApi@70bfc2
12-30 02:16:31.009 4717-4717/? E/TAG: A2---->com.allens.daggerdemo.Bean.AppApi@70bfc2

一样啦

爬坑指南(极度重要)

  1. Provide 如果是单例模式 对应的Compnent 也要是单例模式
  2. inject(Activity act) 不能放父类
  3. 即使使用了单利模式,在不同的Activity 对象还是不一样的
  4. 依赖component, component之间的Scoped 不能相同
  5. 子类component 依赖父类的component ,子类component的Scoped 要小于父类的Scoped,Singleton的级别是Application
  6. 多个Moudle 之间不能提供相同的对象实例
  7. Moudle 中使用了自定义的Scoped 那么对应的Compnent 使用同样的Scoped

(6)Subcomponent

这个是系统提供的一个Component,当使用Subcomponent,那么默认会依赖Component

例如

@Subcomponent(modules = TestSubModule.class)
public interface TestSubComponent {
    void inject(MainActivity activity);
}
@Component(modules = {MainModule.class})
public interface MainConponent {
    TestSubComponent add(TestSubModule module);
}

TestSubComponent中 我void inject(MainActivity activity);,便是这个桥梁,我是要注入到MainActivity,但是dagger 并不会给我生成一个Dagger开头的DaggerTestSubComponent 这个类,如果我想使用TestSubModule.class里面提供的对象,依然还是使用DaggerMainConponent例如

   DaggerMainConponent
                .builder()
                .mainModule(new MainModule())
                .build()
                .add(new TestSubModule())
                .inject(this);

可以看到这里有一个add的方法,真是我在MainConponent添加的TestSubComponent add(TestSubModule module);


(7)lazy 和 Provider

public class Main3Activity extends AppCompatActivity {

    @PresentForContext
    @Inject
    Lazy<Present>     lazy;
    @PresentForName
    @Inject
    Provider<Present> provider;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main3);

        AppComponent appComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();
        ActivityComponent activityComponent = DaggerActivityComponent.builder()
                .appComponent(appComponent)
                .activityModule(new ActivityModule())
                .build();

        activityComponent.injectActivity(this);
        Present present = lazy.get();
        Present present1 = provider.get();
    }
}

其中Lazy(懒加载)的作用好比component初始化了一个present对象,然后放到一个池子里,需要的时候就get它,所以你每次get的时候拿到的对象都是同一个;并且当你第一次去get时,它才会去初始化这个实例.

procider(强制加载)的作用:
1:同上当你第一次去get时,它才会去初始化这个实例
2:后面当你去get这个实例时,是否为同一个,取决于他Module里实现的方式

参考

http://blog.csdn.net/qq_33994844/article/details/54972941


四: MVP + Dagger2

这是现在主流的设计架构

MVP,这个我会在后面的文章介绍,这里不做太多解释

当你了解MVP 的时候,你就知道,所有的业务逻辑全在Presenter
换句话, presenter 持有的对象,控制着你程序的全部逻辑,这在dagger 中,讲白了 我们只要将所有的presetner 对象控制就可以了

下面附上目录结构,当然仅仅作为参考。dagger 强大的用法还是需要各位自己去体会,下面的项目 是我刚刚学会dagger 时候 写的一个项目

屏幕快照 2018-01-02 上午9.39.18.png
屏幕快照 2018-01-02 上午9.40.00.png

可以看到 ,我是 将所有的activity 或者 fragment 全部添加在同一个Component中,当然现在的话 不推荐,比如Utils 你可以专门做一个Component,

首先放上我的Module,公司项目,很多东西没敢放上来,体谅,可以看到 我这里提供了一个SplashPresenter,也就是启动页的Presneter,业务逻辑

@Module
public class ApiModule {

    public ApiModule() {

    }

    @Provides
    @Singleton
    Handler provideHandler() {
        return new Handler();
    }

    @Provides
    @Singleton
    SQLiteDatabase provideSQLiteDatabase() {
        return new DataBaseHelper(MyApp.context, Config.SqlName, null, Config.SqlVersion).getWritableDatabase();
    }

    /**
     * @ User :  allens
     * @ 创建日期 :  2017/7/13 下午3:24
     * @模块作用 :
     * <p>
     * ====================================================================================================================================
     * ====================================================================================================================================
     */
    private SplashPresenter splashPresenter;

    public ApiModule(SplashAct splashAct) {
        splashPresenter = new SplashPresenter(splashAct, new SplashModel());
    }

    @Provides
    @Singleton
    SplashPresenter provideSplashPresenter() {
        return splashPresenter;
    }

    .....
}

当我使用的时候,只需要注入即可,如下代码

public class SplashAct extends BaseActivity implements SplashContract.View {


    @Inject
    SplashPresenter presenter;

    @Inject
    Handler handler;

    @Inject
    ApiService apiService;

    @Override
    protected void onCreate() {
        setContentView(R.layout.activity_splash);
    }

    @Override
    protected void initInject() {
        DaggerApiComponent.builder()
                .apiModule(new ApiModule(this))
                .build()
                .inject(this);
    }

    @Override
    protected void initListener() {
        presenter.getWordsInfo(true, apiService);
    }

    @Override
    public void gotoLogInAct() {
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                startActivity(new Intent(SplashAct.this, LogInAct.class));
                finish();
            }
        }, 1500);
    }
}

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

推荐阅读更多精彩内容