之前就听说过 Dagger2,也曾想见识见识,但都说学习成本很高,加上没太多闲时,后来也就不了了之了。最近得闲就来学习学习,网上看了一些博客后,跟着练习着写了 demo ,理解了 Dagger2 的用法与设计思想,以及这种分层的结构。所以整理了自己的理解与思路写下这篇博客。
相信认真看完这一篇文章,你也能够清晰的了解 Dagger2 的用法和一些设计思想。
项目地址:https://github.com/weioule/Dagger2Field
添加依赖:
- 项目build.gradle 中的 dependencies 节点添加 apt插件
//添加apt插件
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
- module里应用apt插件
//应用apt插件
apply plugin: 'com.neenbedankt.android-apt'
- 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用法:
- 首先,我们来新建一个 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);
}
}
- 其次我们就新建一个 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();
}
- 然后我们新建一个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;
}
}
- 接着我们来写第二级的 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;
}
}
- 接着是 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();
}
- 然后就是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;
}
}
- 最后就是具体的实现层了,我们来新建一个 MainModule。
写法跟上面的 Module 一样,这次我们提供一个 MainFragment 需要的 UserRepostory 仓库,里面有一个获取 User 的方法。User 就是我们需要的数据模型。当然了不需要仓库的可以直接返回 User 即可。
@Module
public class MainModule {
@Provides
public UserRepostory getUserRepostory() {
return new UserRepostory();
}
}
- 接着就是 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();
}
- 然后我们来看看 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;
}
}
- 接着我们先来看看 MainFragmentComponent 。
这里可以看到 @MainActivityScope 注解,它是可以跟 MainComponent 使用同样注解,因为它并没有使用 dependencies 属性。所以没有受到限制。就像我们上面所说的: 开一个分支然后将其本身关联给它的子组件。只是共享对象。
然后就是 @Component 变成了 @Subcomponent 。
关于 @Subcomponent 的用法,它的作用和 dependencys 相似,这里表示 FragmentComponent 是一个子组件,那么它的父组件是谁呢? 提供了获取 MainFragmentComponent 的组件,如 MainComponent中的 MainFragmentComponent mainFragmentComponent(),是这样做的关联。
@MainActivityScope
@Subcomponent
public interface MainFragmentComponent {
void inject(MainFragment mainFragment);
}
- 这里我们先来看看 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);
}
}
}
- 最后就是 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 的实例主要使用来传参数的,不用传递的参数可以忽略不写。
- 好,我们来看看 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);
}
- 再看看 ParameterComponent 。跟上面的 Subcomponent 一样。有一个与 Activity 关联的方法。
@Subcomponent(modules = ParameterModule.class)
public interface ParameterComponent {
void inject(ParameterActivity parameterActivity);
}
- 我们再来看一下数据模型 Bean 。它是一个带 @Inject 注解的类,不需要通过 module 就可以完成注入。但它是有参构造,实例化是需要传参数的。
public class Bean {
@Inject
public Bean(MvpView mvpView) {
}
@Override
public String toString() {
return "我是需要MvpView实例的数据bean";
}
}
- 再来看看我们创建的 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);
}
}
- 那么我们来看看 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的小伙伴。
如有修正的地方,请多多赐教!