Dagger2的使用示例

之前就听说过 Dagger2,也曾想见识见识,但都说学习成本很高,加上没太多闲时,后来也就不了了之了。最近得闲就来学习学习,网上看了一些博客后,跟着练习着写了 demo ,理解了 Dagger2 的用法与设计思想,以及这种分层的结构。所以整理了自己的理解与思路写下这篇博客。

相信认真看完这一篇文章,你也能够清晰的了解 Dagger2 的用法和一些设计思想。

项目地址:https://github.com/weioule/Dagger2Field

添加依赖:

  1. 项目build.gradle 中的 dependencies 节点添加 apt插件
//添加apt插件
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
  1. module里应用apt插件
//应用apt插件
apply plugin: 'com.neenbedankt.android-apt'
  1. module里的 dependencies 节点添加 dagger2 的依赖和 java注解
//引入dagger2
compile 'com.google.dagger:dagger:2.4'
apt 'com.google.dagger:dagger-compiler:2.4'
//java注解
provided 'org.glassfish:javax.annotation:10.0-b28'

Dagger2用法:

  1. 首先,我们来新建一个 AppModule ,一定要记得加 @Module 注解,不然系统就会因为找不到我们的 module 而报错。

接着我们声明两个函数以提供 Context 与 ToastUtil。也一定要记得加上 @Provides 注解,因为 @Provides 修饰的函数的返回值表示为 Module 对应的 Component 中能提供的产品(供外界注入)。

getToastUtil(Context context) 中的 context,不能直接使用 成员变量 mContext,而是要在本类中提供一个 Context getContext()的 @Provides 方法,这样在发现需要 context 的时候会调用 getContext() 来获取,这也是为了解耦。

因为这是最顶级的父 Module,我们想让它所提供的对象为单例,所以我们要在函数上加上 @Singleton 注解,加上 @Singleton表示为该函数的返回值为单例模式。

@Module
public class AppModule {

private Context context;

public AppModule(Context context) {
    this.context = context;
}

@Singleton
@Provides
public Context getContext() {
    return context;
}

@Singleton
@Provides
public ToastUtil getToastUtil(Context context) {
    return new ToastUtil(context);
}
}
  1. 其次我们就新建一个 AppComponent,它是一个接口类型的 interface 。

然后给它添加注解 @Component(modules = AppModule.class) 。@Component 就是跟上面的 @module 一样,声明它为我们的 Component 。括号里面的 module 就指向我们刚刚创建的 AppModule 。

而这里呢要的是它的 .class 形式。这个 module 呢可以指向多个 module,它可以以一个数组的形式传进去。写法:

@Component(modules = {AppModule.class,xxModule.class , xxModule.class})。

因为是最顶级的 Component,我们需要给下面的 Component 提供 AppModule 中的对象:Context 和 ToastUtil,所以我们就声明两个接口函数 getContecxt() 和 getToastUtil() 。

这两个接口函数的作用就是:当下面的依赖需求方注入的类型为 Context 时,需要的时候就会调用 Component 中返回值为Context 的函数 getContecxt(),将 AppModule 中返回值类型为 Context 的函数的返回值赋值给到依赖需求方注入的变量。

如果没有声明这两个接口函数,那么下级(可以理解为继承)它的 Component 就不能通过注入的形式拿到 AppModule 中的Context 和 ToastUtil 。那么注入它下级的 Component 的 activity 或 fragment 也就拿不到了。

上面说了,要让 AppModule 中添加 @Singleton 注解的函数的返回值为单例,那么还需要在对应的 Component 上添加@Singleton 注解,不然呢你拿到的将不会是单例而是两个不同的对象。

@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
    Context getContecxt();

    ToastUtil getToastUtil();
}
  1. 然后我们新建一个App类,继承Application。在onCreate函数里与我们的AppComponent关联:
DaggerAppComponent.builder().appModule(new AppModule(this)).build()。

你直接敲 DaggerAppComponent 是不会给你提示的,因为这会儿程序根本不知道 DaggerAppComponent 是什么鬼,你得先编译一下,然后 Dagger2 会通过自动生成 DaggerAppComponent 来帮你进行注入。这时候你就可以带着系统提示敲出你要的 DaggerAppComponent 了。

因为下级的 Component 在进行关联的时候还需要关联父 Component,所以我们就把关联后的 AppComponent 对象抽取成员变量,再提供一个 get 方法给下级进行关联时调用。

这里需要说一下: .appModule(new AppModule(this)) 这里的作用主要是用来传参的,如果你不需要传参数,那么可以写也可以省略(传参的用处最后面讲)。

public class App extends Application {

private AppComponent mAppComponent;

@Override
public void onCreate() {
    super.onCreate();
    mAppComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();
}

public AppComponent getAppComponent() {
    return mAppComponent;
}
}
  1. 接着我们来写第二级的 Module 和 Component 。

同样的我们新建一个 AcvtivityModule,写法跟上面的 Module 一样,而这一层呢我们要给下一级和 AcvtivityComponent 提供的是 Activity。

需要说的是@PerActivity这个没见过的注解,这个注解呢并不是Dagger2规定的注解。其实 @PerActivity 的代码和 @Singleton 是一样的,只是需要我们自己重新定义一下而已。表示在 @PerActivity 这个生命周期中,只包含一个这样产品的标签。

但严格来说@Singleton 和 @PerActivity并不是我们设计模式中的单例模式,而是 Dagger2 中为了保持一个产品在一个 Scope中只有一个对象的标签。

为什么要自定义 @PerActivity 呢?因为如果使用了 dependencies(同继承类似,可以理解为继承),那么依赖的一方的 Scope 不能和 父级 相同。

也就是说,AcvtivityComponent 是继承 AppComponent 的,那么 AppComponent 已经使用 @Singleton 注解了,如果要是我们也要实现单例的话,则 AcvtivityComponent 和 AcvtivityModule 就不能使用同样的注解了,因此我们需要自定义一个跟它一样的注解。一定要注意:你的 Component 要与对应的 Module 使用相同的注解才能编译过去,不然会报错。

这一点确实有点麻烦。

@Module
public class AcvtivityModule {
private Activity activity;

public AcvtivityModule(Activity activity) {
    this.activity = activity;
}

@PerActivity
@Provides
public Activity getActivity() {
    return activity;
}
}
  1. 接着是 AcvtivityComponent,跟 AppComponent 一样,声明两个接口函数以给与其关联的 activity 或 fragment 或者下一级的Component 提供对象 Acvtivity 和 ToastUtil 。

如果这里不重写 ToastUtil getToastUtil() 函数,那么下一级的就拿不到 ToastUtil 了。这一点呢就有点不像继承了。

@PerActivity 注解就是上面说的与AcvtivityModule保持一致,而@Component里面多了dependencies这个属性。这个dependencies 呢就类似 extends 。

dependencies = {AppComponent.class},意为 AcvtivityComponent 中部分对象,需要 AppComponent.class 来提供依赖。也就是说父 Component 中的方法暴露给子 Component 部分。这里指:Acvtivity 和 ToastUtil 。

dependencies 与 module 一样都是可以指向多个类的,写法一样。

 @PerActivity
 @Component(modules = AcvtivityModule.class, dependencies = AppComponent.class)
 public interface ActivityComponent {

    Activity getActivity();

    ToastUtil getToastUtil();

}
  1. 然后就是BaseActivity,它与App里面的关联是一样的,只是因为有了父Component所以还要关联AppComponent:.appComponent(((App) getApplication()).getAppComponent()) 。

同样提供 Component 对象给下一级的关联。

public class BaseActivity extends AppCompatActivity {

private ActivityComponent mActivityComponent;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mActivityComponent = DaggerActivityComponent.builder()
            .appComponent(((App) getApplication()).getAppComponent())
            .acvtivityModule(new AcvtivityModule(this))
            .build();
}

public ActivityComponent getActivityComponent() {
    return mActivityComponent;
}
}
  1. 最后就是具体的实现层了,我们来新建一个 MainModule。

写法跟上面的 Module 一样,这次我们提供一个 MainFragment 需要的 UserRepostory 仓库,里面有一个获取 User 的方法。User 就是我们需要的数据模型。当然了不需要仓库的可以直接返回 User 即可。

@Module
public class MainModule {
@Provides
public UserRepostory getUserRepostory() {
    return new UserRepostory();
}
}
  1. 接着就是 MainComponent 。@MainActivityScope 就跟上面的 @PerActivity 一样,属于自定义注解。

这里的 inject 接口函数就是 与 MainActivity 关联的方法。将 MainActivity 的实例传进来就可以与它进行关联。

MainFragmentComponent 呢就类似于是 MainComponent 的一个分支,用于关联到 MainFragment 里。这里的 MainFragmentComponent 就相当于 MainComponent 的子组件,注意:它能拥有 MainComponent 里所有提供的对象。也就是 MainModule 里所有提供的对象。

@MainActivityScope
@Component(modules = MainModule.class, dependencies = ActivityComponent.class)
public interface MainComponent {
    void inject(MainActivity mainActivity);

    MainFragmentComponent getMainFragmentComponent();

}
  1. 然后我们来看看 MainActivity 。

跟 BaseActivity 一样写法。与 MainComponent 进行关联。

因为 MainFragment 需要与 MainFragmentComponent 进行关联。而MainFragmentComponent 是 MainComponent 里的一个对象。所以我们一样要把 MainComponent 抽取出来再提供给 MainFragment 。

public class MainActivity extends BaseActivity {

private MainComponent mMainComponent;

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

    getSupportFragmentManager().beginTransaction().add(R.id.main_fl, new MainFragment()).commit();

    mMainComponent = DaggerMainComponent.builder()
            .activityComponent(getActivityComponent())
            .mainModule(new MainModule())
            .build();
    mMainComponent.inject(this);
}

public MainComponent getMainComponent() {
    return mMainComponent;
}
}
  1. 接着我们先来看看 MainFragmentComponent 。

这里可以看到 @MainActivityScope 注解,它是可以跟 MainComponent 使用同样注解,因为它并没有使用 dependencies 属性。所以没有受到限制。就像我们上面所说的: 开一个分支然后将其本身关联给它的子组件。只是共享对象。

然后就是 @Component 变成了 @Subcomponent 。

关于 @Subcomponent 的用法,它的作用和 dependencys 相似,这里表示 FragmentComponent 是一个子组件,那么它的父组件是谁呢? 提供了获取 MainFragmentComponent 的组件,如 MainComponent中的 MainFragmentComponent mainFragmentComponent(),是这样做的关联。

@MainActivityScope
@Subcomponent
public interface MainFragmentComponent {
      void inject(MainFragment mainFragment);
}
  1. 这里我们先来看看 MainFragmentContact 。

这里使用 @Inject 注解 Presenter 构造函数。 当请求一个 Presenter 的实例时,Dagger 会获取所需的参数,并调用此构造函数创建 Presenter 实例。

public class MainFragmentContact {
public interface MainView {
    void setUserName(String name);

    void showToast(String msg);
}

public static class Presenter {
    private UserRepostory userRepostory;
    private MainView mainView;
    private String userName;

    @Inject
    public Presenter(UserRepostory userRepostory) {
        this.userRepostory = userRepostory;
        userName = this.userRepostory.getUser().getName();
    }

    public void setView(MainView mainView) {
        this.mainView = mainView;
    }

    public void showToastBtnClick() {
        String msg = "hello " + userName;
        mainView.showToast(msg);
    }

    public void setUserNameBtnClick() {
        this.mainView.setUserName(userName);
    }
}

}
  1. 最后就是 MainFragment 。

在 MainFragment 里呢,我们声明了 MainFragmentContact.Presenter 和 ToastUtil 两个类型的变量。并且都分别用了 @Inject 注解。

ToastUtil 呢就是上面 AppComponent 提供给 ActivityComponent 然后 ActivityComponent 再提供给 MainComponent ,然后再由MainFragment 与 MainComponent 的子组件(类似它的分支) MainFragmentComponent 进行关联。这样一层一层的提供了过来。

MainFragmentContact.Presenter 呢就是上面说的 Presenter 构造函数加了 @Inject 。所以在 MainFragment 使用到的时候Dagger2 就会自动生成代码 new 一个Presenter 对象并赋值给了过来。

原理是:当 MainFragment 里面的注解变量 mPresenter 需要用到的时候,因为 MainFragment 和 MainFragmentComponent 关联了,dagger2 就会把 MainFragmentComponent 拥有的 MainModule 中的 UserRepostory 实例传过去调用构造函数创建 mPresenter 的实例并注入到 MainFragment 里的注解变量 mPresenter 。

也就是说当Component找到以下被 @inject 的构造方法时,会发现凭借自己无法提供 UserRepostory 参数,然后就会前往它所关联的Module 看其能否提供参数,然后就是把Component和Module进行关联了

其他的就是MVP模式与 Presenter 进行交互了。

public class MainFragment extends Fragment implements MainFragmentContact.MainView, View.OnClickListener {

@Inject
MainFragmentContact.Presenter mPresenter;

@Inject
ToastUtil mToastUtil;
private TextView mUserName;

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (getActivity() instanceof MainActivity) {
        ((MainActivity) getActivity()).getMainComponent().getMainFragmentComponent().inject(this);
    }

    mPresenter.setView(this);
}

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fragment_main, container, false);
}

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    mUserName = (TextView) view.findViewById(R.id.user_name);
    view.findViewById(R.id.btn_toast).setOnClickListener(this);
    view.findViewById(R.id.btn_set_user_name).setOnClickListener(this);
}

@Override
public void setUserName(String name) {
    mUserName.setText(name);
}

@Override
public void showToast(String msg) {
    mToastUtil.showToast(msg);
}

@Override
public void onClick(View view) {
    switch (view.getId()) {
        case R.id.btn_set_user_name:
            mPresenter.setUserNameBtnClick();
            break;
        case R.id.btn_toast:
            mPresenter.showToastBtnClick();
            break;
        default:
            break;
    }
}
}

这里说下Dagg2的注解创建类实例的两个方法的优先级:

对于上面的两种获取对象方法,一种是通过 Module 里的函数创建返回,还有就是和上面的 Presenter 一样 通过在构造函数上添加 @Inject 注解返回。

Component 会首先从 Module 中查找有没有类实例,若找到就用 Module 创建类实例,并停止查找 @Inject 注解构造函数的类实例。否则才是从 Inject 注解构造函数中查找类实例。所以创建类实例级别 Module 要高于 Inject 注解构造函数。

最后说一下上面说的 .appModule(new AppModule(this)) 传参的用处。

我们在进行关联的时候,module 的实例主要使用来传参数的,不用传递的参数可以忽略不写。

  1. 好,我们来看看 CommonComponent 。

CommonComponent 呢是一个父组件,ParameterComponent 是它的子组件。它这里没有绑定 module ,我们看到接口函数 addSub 是需要传参的,而参数就是我们需要它为我们将 MvpView 实例传给 dagger2 的 ParameterModule 。

因为 ParameterComponent 是一个 Subcomponent 而不是 Component 所以它是不能直接和 Activity 或 Fragment 关联的,而是要通过父组件获取它的实例来关联。

因此也就不能通过 ParameterComponent 的 ( .parameterModule(new ParameterModule(this)) ) 的形式传递,所以 ParameterModule 就得从这里传进来。

@Component()
public interface CommonComponent {
    ParameterComponent addSub(ParameterModule parameterModule);
}
  1. 再看看 ParameterComponent 。跟上面的 Subcomponent 一样。有一个与 Activity 关联的方法。
@Subcomponent(modules = ParameterModule.class)
public interface ParameterComponent {
    void inject(ParameterActivity parameterActivity);
}
  1. 我们再来看一下数据模型 Bean 。它是一个带 @Inject 注解的类,不需要通过 module 就可以完成注入。但它是有参构造,实例化是需要传参数的。
public class Bean {

@Inject
public Bean(MvpView mvpView) {
}

@Override
public String toString() {
    return "我是需要MvpView实例的数据bean";
}
}
  1. 再来看看我们创建的 ParameterActivity 。

有一个注解变量 mBean 。如果你像之前一样关联后就想直接调用它,那么你拿到的将会是 null,因为它没有成功被实例化。那么为什么这次不能跟之前一样自动实例化再提供过来呢?原因是因为它在实例化的时候需要一个 MvpView 的对象,可你并没有给过它。

那么我们怎么给呢?你想想,既然我们拿数据都是在 module 里,那么我们传进去的数据也得传到 module 里了。这里我们就通过构造方法传进去 MvpView 实例。

这样 dagger2 就可以从 module 里获取 MvpView 实例去创建 Bean 的对象并注入返回了。

public class ParameterActivity extends AppCompatActivity implements MvpView {

@Inject
Bean mBean;

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

    DaggerCommonComponent.builder().build().addSub(new ParameterModule(this)).inject(this);

    TextView userName = (TextView) findViewById(R.id.user_name);
    userName.setText(mBean.toString());
    findViewById(R.id.btn_set_user_name).setVisibility(View.GONE);
    findViewById(R.id.btn_toast).setVisibility(View.GONE);
    findViewById(R.id.do_next).setVisibility(View.GONE);
}
}
  1. 那么我们来看看 ParameterModule 。

通过构造函数将 MvpView 实例传进来,然后在声明 getMvpView() 方法提供 MvpView 对象,这样当 dagger2 在创建Bean 实例需要 MvpView 对象的时候就可以从这里获取了。

@Module
public class ParameterModule {

private MvpView mMvpView;

public ParameterModule(MvpView mvpView) {
    mMvpView = mvpView;
}

@Provides
public MvpView getMvpView() {
    return mMvpView;
}
}

ok,就是这样,相信认真看完这篇文章。你也能够清晰的了解Dagger2的基本使用与一些设计思想了。认真起来其实也没那么难。希望能帮助想要学习Dagger2的小伙伴。

如有修正的地方,请多多赐教!

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

推荐阅读更多精彩内容

  • 安卓基础开发库,让开发简单点。Demo地址:https://github.com/LJYcoder/MvpDagg...
    JYcoder阅读 3,681评论 2 30
  • 部分内容参考自:[Android]使用Dagger 2依赖注入 - DI介绍(翻译)[Android]使用Dagg...
    AItsuki阅读 47,210评论 66 356
  • 本篇的内容不涉及Dagger2的源码,只是为了更好的使用. 想进一步了解的话可以阅读以下文章:dagger2让你爱...
    李庆雪阅读 988评论 0 3
  • 什么的依赖注入 在软件工程中,依赖注入是实现控制反转的方式之一。百度百科中对于控制反转的解释如下:控制反转(Inv...
    小甜李子阅读 1,668评论 5 3
  • 第一次关注到彭小六这个名字,是去年十点读书会举办的百天阅读打卡的总结大会上的15分钟发言; 后来上简书,几乎天天都...
    职场小能手Sara阅读 228评论 0 1