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'

2. module里应用apt插件

//应用apt插件
apply plugin: 'com.neenbedankt.android-apt'

3. 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);
    }


}

 

2.  其次我们就新建一个 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();
}

 

3. 然后我们新建一个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;
    }
}

 

4.  接着我们来写第二级的 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;
    }
}

 

5.  接着是 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();

}

 

6. 然后就是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;
    }
}

 

7.  最后就是具体的实现层了,我们来新建一个 MainModule

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

 

@Module
public class MainModule {
    @Provides
    public UserRepostory getUserRepostory() {
        return new UserRepostory();
    }
}

 

8.  接着就是 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();

}

 

9.  然后我们来看看 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;
    }
}

 

10.  接着我们先来看看 MainFragmentComponent

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

然后就是 @Component 变成了 @Subcomponent

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

 

@MainActivityScope
@Subcomponent
public interface MainFragmentComponent {
    void inject(MainFragment mainFragment);
}

 

11.  这里我们先来看看 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);
        }
    }

}

 

12.  最后就是 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);
}

 

2.  再看看 ParameterComponent 。跟上面的 Subcomponent 一样。有一个与 Activity 关联的方法。

 

@Subcomponent(modules = ParameterModule.class)
public interface ParameterComponent {
    void inject(ParameterActivity parameterActivity);
}

 

3.  我们再来看一下数据模型 Bean 。它是一个带 @Inject 注解的类,不需要通过 module 就可以完成注入。但它是有参构造,实例化是需要传参数的。

 

public class Bean {

    @Inject
    public Bean(MvpView mvpView) {
    }

    @Override
    public String toString() {
        return "我是需要MvpView实例的数据bean";
    }
}

 

 

4.  再来看看我们创建的 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);
    }
}

 

5.  那么我们来看看 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的小伙伴。

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值