和很多自主学习的人,我接触Dagger 2 框架的原因是刚进公司的时候导师给安排的学习任务,学习方式是组内培训。
听到这个消息的我,以为是部门的人轮流给我讲课。
后来导师跟我说,组内培训的意思是,我先自己好好学这个框架,然后给组内的所有人搞个培训。
没办法,在网上看了很多相关博客,浪费了不少时间,终于还是学有所得,也记录一下我最近的学习进展。
就不多讲什么历史了,你能看到我这篇博客,想来历史什么的科普你都已经被塞到吐了,还是撸代码学得快。
一 环境配置
在module的build.gradle中添加代码:
dependencies { ...... //dagger2 implementation 'com.google.dagger:dagger:2.7' annotationProcessor 'com.google.dagger:dagger-compiler:2.7' //butterknife implementation 'com.jakewharton:butterknife:10.0.0' annotationProcessor 'com.jakewharton:butterknife-compiler:10.0.0' }
为了后面书写代码简便,将ButterKnife一起配置了。
在Project build.gradle中添加如下代码(这段代码是配置ButterKnife使用的,不配置可能就会报TimeOut的错误):
allprojects { repositories { google() maven { url "https://oss.sonatype.org/content/repositories/snapshots" } jcenter() } }
到这里配置就完成了,ButterKnife可以为我们省下很多代码,因为ButterKnife和Dagger的厂家是一样的,很多地方也是共通的,就不多解释原理,拿来就用。
二 源码分析
我们先创建一个简单的Tools类:
public class Tools { @Inject public Tools(){} }
不需要任何属性,只用@Inject标记一个空的构造方法,然后我们使用Ctrl+F9进行编译。
Dagger2 是通过标记来公式化编写代码,减轻我们重复编写代码的劳动。
这里提到个词“公式化”,其实很好理解。
比如,你要给你给你喜欢的女孩子表白,先跟你寝室兄弟们排练一百遍表白流程。到了女寝楼下,你清一清嗓子,就有人给你打好了灯光,你叫完女孩的名字,身后就有人放飞了粉红的爱心气球,你刚说完那羞羞的三个字,周围就全是大声呼喊“答应他、答应他......”
女孩十分感动,然后拒绝了你......
咳咳,讲偏了!
总之,公式化的东西就是这样,固定好的流程,你只需要打个特殊的手势,别人就知道该怎么做。为什么?因为流程都是公式化的,固定的,已经跑过千百次了,大家闭着眼睛都能敲出来。在程序里也是这样,不要重复造轮子。你只需要打个标记,就像上面提到的@Inject,剩下的繁琐的任务交给喜欢重复劳动的计算机。
那么,看到@Inject这个标记,计算机又做了什么呢?
编译过后会发现build中多了一个文件(你要问我路径在哪?后面我带你找,听我的,慢慢来!),叫做Tools_Factory类,内容如下:
public enum Tools_Factory implements Factory<Tools> { INSTANCE; @Override public Tools get() { return new Tools(); } public static Factory<Tools> create() { return INSTANCE; } }
这就是AS自动生成的代码。一个工厂类,类如其名,就是个工厂。通过create()方法进行创建,通过get()方法获得new Tools对象。
下面我就贴四大基本组件的代码了,先把框架搭起来:
首先是MainActicity:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this);//ButterKnife绑定Activity } @OnClick({R.id.turn_firstactivity}) public void onViewClicked(View view) { switch (view.getId()){ case R.id.turn_firstactivity: startActivity(new Intent(this, FirstActivity.class)); break; } } }
就简简单单一个跳转,跳转到我们接下来要使用的FirstActivity中。
贴一下activity_main这个layout,平时我看博客最讨厌别人不把代码贴完,我当然不会犯这个错。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/turn_firstactivity" android:text="四大基本组件"/> </LinearLayout>
接下来是FirstActivity,也就是我们第一个例子的主要Activitypublic class FirstActivity extends AppCompatActivity
@BindView(R.id.fist_text_1) TextView text1; @BindView(R.id.fist_text_2) TextView text2; @Inject Tools tool1; @Inject Tools tool2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.first); ButterKnife.bind(this);//绑定View和Activity } }
代码很好理解,用ButterKnife来直接绑定View和Activity,节省了很多代码,而节省的代码也是前面提到“公式化”的体现。
以及对应的layout的代码。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/fist_text_1" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/fist_text_2" /> </LinearLayout>
我们再新建接口FirstComponent:
@Component public interface FirstComponent { void inject(FirstActivity activity); }
这个方法名inject可以随意改写,但是为了方便后来人阅读,建议你还是按照约定俗成的规矩来写,让后来接手你代码的人少骂你两句。
到这一步,我们需要先Ctrl+F9编译一下了。因为有个文件需要Dagger2自动生成后,我们才能使用。
接下来在FirstActivity中添上代码:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.first); ButterKnife.bind(this); DaggerFirstComponent.builder() .build() .inject(this); text1.setText(tool1.toString()); text2.setText(tool2.toString()); }
这个多的文件就是DaggerFirstComponent.java,可以Ctrl+鼠标左键点击DaggerFirstComponent跳转到这个文件查看。
前面找不到自动生成文件在哪的,可以再点击一下这个像准星一样的图标,自动定位到对应的文件夹。
这个文件就是Dagger+Component的名字组合出来的,实际上也是继承了Component接口对其中的方法进行重写。
DaggerFirstComponent代码如下:
public final class DaggerFirstComponent implements FirstComponent { private MembersInjector<FirstActivity> firstActivityMembersInjector; private DaggerFirstComponent(Builder builder) { assert builder != null; initialize(builder); } public static Builder builder() { return new Builder(); } public static FirstComponent create() { return builder().build(); } @SuppressWarnings("unchecked") private void initialize(final Builder builder) { this.firstActivityMembersInjector = FirstActivity_MembersInjector.create(Tools_Factory.create()); } @Override public void inject(FirstActivity activity) { firstActivityMembersInjector.injectMembers(activity); } public static final class Builder { private Builder() {} public FirstComponent build() { return new DaggerFirstComponent(this); } } }
很明显的Builder建造者模式。
再反观一下Activity中对DaggerFirstComponent的调用
DaggerFirstComponent.builder() .build() .inject(this);
首先在里面先初始化一个Builder类,然后调用Builder类中的build()方法对DaggerFirstComponent进行构造。
DaggerFirstComponent构造函数中,有个initialize的初始化函数,给DaggerFirstComponent持有的一个内部成员firstActivityMembersInjector赋值。
注意这行代码:
this.firstActivityMembersInjector = FirstActivity_MembersInjector.create(Tools_Factory.create());
这又涉及到一个关键的文件,记住后缀MembersInjector就行了。
可能有的小伙伴就要问,为啥我不一次性把所有的自动生成文件都列出来?
按照逻辑一步步来,这样比较好理解。
这也是最后一个了:一个工厂类Factory、一个接口实现类DaggerComponent、加上组装类MemberInjector(如果你看见第四个,可能是ButterKnife生成的,有兴趣可以看看)
为啥要把MemberInject叫做组装类,听我慢慢讲来。
MemberInject.java源代码太长了,先只看create()
private final Provider<Tools> tool1AndTool2Provider; public FirstActivity_MembersInjector(Provider<Tools> tool1AndTool2Provider) { assert tool1AndTool2Provider != null; this.tool1AndTool2Provider = tool1AndTool2Provider; } public static MembersInjector<FirstActivity> create(Provider<Tools> tool1AndTool2Provider) { return new FirstActivity_MembersInjector(tool1AndTool2Provider); }
大概意思就是先传进来一个工厂类Factory.create(),前面讲过,工厂类就俩方法:create()和get()。然后把create好的工厂类赋给MemberInject类里的成员变量,大概就是把工厂类构建好,保存起来备用。
到这里我们就已经build()跑完了。捋一捋现在都干了啥,DaggerFirstComponent这个类调用了Factory.create(),然后把它给了MemberInject,自己保存了一个MemberInject的实例。到了这步,其实返回的还是个Component,然后继续执行Component里已有的方法而已。
注意这点,到这里返回的是个Component!划重点,后面要考的。
如果你看代码足够认真可能会注意到,DaggerFirstComponent中有个create()方法,对应的就是builder().build()。意味着依赖注入的代码可以简写成下面这种形式:
DaggerFirstComponent.create().inject(this);
没错,确实可以这样,但强烈不建议这样写。划重点,强烈不建议!
因为不符合开闭原则,原因后面会讲到。
DaggerFirstComponent类继承了FirstComponent接口,重写了inject方法:
@Override public void inject(FirstActivity activity) { firstActivityMembersInjector.injectMembers(activity); }
可以看到Inject方法只是给MemberInject的实例传了个Activity。
MemberInject都被喂了哪些东西?
工厂类create好了送进去,Activity也给它了。
为啥叫它组装类!看代码:
instance.tool1 = tool1Provider.get();
instance是Activity的实例,通过Activity找到之前声明的依赖注入实例tools,然后在工厂类实例tool1Provider中get()一个新的对象赋值给它。
东西都是DaggerFirstComponent给它准备好的,他只负责组装。
到这里,整个依赖注入全部完成。
运行结果如下:
注入两个不同的Tools。
回顾一下,其实逻辑很简单。
1、在需要注入依赖的Activity中,用@Inject标记所注入的依赖类的实例tools
2、在所需呀注入的类Tools的构造函数Tools()上标记@Inject,编译会根据此生成Factory工厂类
3、在接口FirstComponent上标记@Component,并写入Inject方法
4、编译生成DaggerFirstComponent.java
5、调用DaggerFirstComponent方法将工厂类的实例和Activity的实例都交给MemberInjecter
6、MemberInjecter组装完成,依赖成功注入。
等等,四大基本组件怎么只讲了两个?
因为两个就已经能基本完成一个简单的依赖注入了,下一章接着讲工厂类的另一种构成方法。