现在基本上有一些规模的项目,都使用了Dagger2,也不是装13,个人感觉也的确是大势所趋,Dagger2的确有它的优势。借着手上的项目,学习了一下Dagger2,打算用在公司的项目中。今天就来以自己初学者的角度来谈谈dagger2的认识。
我是目前是移动端开发者,主要从事的是Android端开发。在andorid开发过程中,按照套路我们会有SharePreferenceManager、DatabaseManager、NetWorkManager、CacheManager等各种Manager,当然你的叫法也可以不同,他们都会存在初始化,考虑到有种可能的初始化顺序,如下图:
我们可能需要先使用Application初始化SpManager(SharePreferenceManager)拿到一些关键数据,从而通过这些数据来初始化DbManager,NWManager或者CacheManager。可以看到我们的各个对象就存在初始化先后的问题,试想如果系统过于复杂,像这种对象初始化有相互依赖的各种关系,想必是非常复杂的;如果是我们用代码去写,也是很繁杂的而且没多大效率的;还有一个问题,看下图:
如果有一天,我们的E对象突然改变了初始化方法,而且很多地方都需要E对象的初始化,那我们是不是很懵,因为有很多地方需要修改,那苦逼的程序员是不是很难受,这也就是我们常说的程序的耦合性,改一处,就要变100处地方。很显然dagger的出现就是为了解决这个问题的。具体怎么解决,我还有想用一张图来说明我的想法:
假设dagger在初始化某对象时,同时需要a b c g四个对象,它就会在容器中寻找(这里的寻找有可能是上一次创建的),找到了就拿来使用,没有找到就需要靠自身去创建了。你可以看到a d g对象都会存在,此时c对象不存在,那么此时的c对象的创建又变成了XObject对象的创建,此时需要找到c对象创建时,需要m对象和p对象,一环一环套下去,然后直到所有的需要的对象都创建完成,然后再递归到需要它需要创建的对象,直到所有的对象初始化完成。明白了这个道理,感觉学习dagger2就不难了。
AS , gradle配置:
//dagger2
compile 'com.google.dagger:dagger:2.14.1'
annotationProcessor 'com.google.dagger:dagger-compiler:2.14.1'
新建Student类:
public class Student {
private String name ;
private int score ;
@Inject
public Student(){
this.name = "zhangsan" ;
this.score = 30 ;
}
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
和普通java类一致,只是在构造方法上使用了注解@javax.inject.Inject注解,第一步完成;
创建接口StudentComponent,当然这个接口名字你可以乱写,但是为了规范我还是建议你初始化啥对象,就写这个名字,接口很简单:
@Component
public interface StudentComponent {
void inject(SecondActivity activity);
}
我们需要一个@dagger.Component的注解,和一个SecondActivity,这个SecondActivity是我们需要用到的Activity,在这个Activity中我们需要对Student对象进行初始化:
SecondActivity源码:
public class SecondActivity extends AppCompatActivity {
@Inject
Student su;
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
tv = findViewById(R.id.id_tv_content);
}
public void getStudentInfo(View view) {
tv.setText(su.toString());
}
}
在我们需要的初始化的对象上Student也加上@Inject注解,其他的好像真的完事了,就这么简单?当然,你需要将make一下你的project(这里吐槽一下当年使用window系统最终放弃了dagger2无数次,make项目绝对是我不想坚持下去的最大原因,太尼玛花时间了),然后加上一句话:
编译运行,我们可以看到:
的确,程序没有我们想象中报了异常,而是真的被初始化,而且被赋值了。第一次用Dagger2的感觉就是这么神奇。当然这算得上Dagger2最简单的应用场景吧。
不过,距离真正去使用Dagger2还有有一定的差距,因为我们还缺少一样叫module的类。真正的Dagger2是存在三部分的。
1. 对象的实例化部分,相当于我们的new Object(); Dagger提供类似容器功能,提供实例化的功能;这一个叫module
;
2. 需要实例化的部分,你实例化的对象需要被用得到,这是个非常抽象的部分,可以使是Activity
,也可以是Fragment
,或者是任意的Java对象
;
3. 连接部分,将实例化的对象和需要用到这些对象的部分连接起来。这一个在dagger中叫Component
。
好了,有了上面的简单说明,我们来个真正的dagger用法。来个Student2
对象,相当于我们的数据:
public class Student2 {
private String name ;
private int score ;
public Student2(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student2{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
现在我们需要module
组件,给我们提供对象实例化的,这个可能稍微有点拐弯:
@Module
public class Student2Module {
@Provides
public String provideName() {
return "lisi" ;
}
@Provides
public int provideScore(){
return 98 ;
}
@Provides
public Student2 provideStudent2(String name , int score){
return new Student2(name,score);
}
}
我们看到了@provides
,这是来自Dagger的注解,正如翻译过来,就是给你提供数据量,它提供了一个Student2
对象,那么问题来了,那name
和score
是哪里来的呢?同样我们看到了它提供另外两个方法provideScore()
和provideName()
,同样也被@Provides
注解修饰,意思就是我给你提供数据量。
已经有数据可以提供了,那哪里需要这个数据呢?来,看一下我们的Activity,在Activity中有地方需要它:
public class ThirdActivity extends AppCompatActivity {
@Inject //这里使用@Inject 说明我需要被初始化
Student2 stu ;
TextView tv ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
tv = findViewById(R.id.id_tv_content);
}
//这里是按钮的点击作用,我们需要看一下这个Student2对象是否被初始化
public void getStudentInfo(View v) {
tv.setText(stu.toString());
}
}
数据源和数据去向都有了,那我们就需要一个中间的连接着Component
了,好了,我们来造一个Component
,如下:
@Component(modules = Student2Module.class) //这就是中间的链接部分,数据源就是这个modules所对应的数据类
public interface Student2Component { //Component是接口,相当于中间件
void inject(ThirdActivity activity); //这个是数据去向,注意不要使用什么BaseActivity,一定要写具体的activity
}
好了,然后我们make(build,reBuild)一下我们的项目,然后在ThirdActivity
中添加一句代码:
这个DaggerStudentComponent
是Dagger框架自动生成的,它是由你的Component
接口然后加上Dagger
前缀生成的,作用是连接我们的数据源和数据接收者。点击按钮,可以看到结果:
结果就是我们想要的,也可以看到Student2Module
中提供的name和score的确在Activity
的Student2
对象中了。是不是很简单呢?是啊,我们的dagger注入框架就好了,这就是注入的效果。
但是你有没有一种感觉,为啥Student
类和Student2
对象中,Student
构造器中使用了@Inject
注解,但是Student2
构造器中没有使用@Inject
呢;为啥Student
对应的StudentComponent
没有提供获取Student
方法,而Student2Component
中提供了获取Student2
的方法呢?
这就需要我们理解Module
作为数据提供者,提供数据也是有先后顺序的,一般顺序如下:
1. 我现在module
数据提供者中找有没有提供初始化对象的方法;
2. 我找到了,我就返回该对象;
3. 我没有找到,我就去查看这个对象的构造器是否被@Inject
找到
4. 找到了,那么自己执行new Object过程;没有找到,对不起,编译出错,说明你dagger方法使用错误。
所以,综上所述,一个对象的构造器Constructor
没有被@Inject
修饰,那么在Module
中必须有方法提供初始化对象。
好了,我们接下来就说一说工作和生活中用得比较多的对象,也就是关于dagger的标识符问题了。
和Java思想中一致,就是单例的意思。这里的单例依据个人的理解,一个数据接收容器中存在多个数据接收者,那么多个数据接收者指向的对象是同一个。说的自己都有点不信了,举个例子:
定义StudentSingleton
数据结构:
@Singleton
public class StudentSingleton {
private String name ;
private int score ;
@Inject //看这里被@Inject修饰了
public StudentSingleton(String name, int score) {
this.name = name;
this.score = score;
}
}
数据提供者StudentSingletonModule
:
@Module
public class StudentSingletonModule {
@Provides
public String getName(){
return "tom";
}
@Provides
public int score() {
return 20;
}
}
数据需求者SingletonActivity
,可以看得到里面有两个StudentSingleton
对象等待被初始化:
public class SingletonActivity extends AppCompatActivity {
@Inject
StudentSingleton singleton1 ;
@Inject
StudentSingleton singleton2 ;
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
tv = findViewById(R.id.id_tv_content);
//build之后 进行对象注入
DaggerStudentSingleComponent.builder().build().inject(this);
}
public void getStudentInfo(View v) {
tv.setText(singleton1 + "\n" + singleton2);
}
}
我们看一下结果:
我们可以看到两个StudentSingleton
对象指向的相同的地址,那么说明它们是同一个对象。
同样,我们在另外一个Activity中也需要一个StudentSingleton,如下代码:
public class AnotherStudentSingletonActivity extends AppCompatActivity {
@Inject
StudentSingleton stu ;
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_another_student_singleton);
tv = findViewById(R.id.id_tv_content);
DaggerStudentSingleComponent.builder().build().inject(this);
tv.setText(stu.toString());
}
}
获取的结果为:
由此可见,@Singleton
注解并不是我们平常意义上的单例,只是在同一个容器的中,指向同一个对象才可能被称为单例。
当然了,你去掉@Singleton
注解,去看看在一个Activity同时获取两个StudentSingleton
对象,地址是不是一样呢?答案肯定是不一样的。
这个在Andorid中需求的比较多,很多时候我们需要一个Context对象,到处都是Context对象,举个例子:
先来个StudentContext
对象:
public class StudentContext {
private String name ;
private int score ;
private Context context;
public StudentContext(){}
@Inject //注意@Inject注解修饰的这个构造器
public StudentContext(String name, int score, Context context) {
this.name = name;
this.score = score;
this.context = context;
}
@Override
public String toString() {
return "StudentContext{" +
"name='" + name + '\'' +
", score=" + score +
", context=" + context +
'}';
}
}
在我们的Module
中,我们这时需要传入一下个Context对象:
@Module
public class StudentContextModule {
private Context context;
//此时需要你传入一个Context对象进入
public StudentContextModule(Context context) {
this.context = context;
}
@Provides
public String provideName() {
return "jerry" ;
}
@Provides
public int provideScore() {
return 20 ;
}
//这里提供一个Context对象方法
@Provides
public Context provideContext() {
return context;
}
}
我们桥接对象为:
@Component(modules = StudentContextModule.class)
public interface StudentContextComponent {
void inject(StudentContextActivity activity);
}
make
一下我们的项目,在数据接收容器StudentContextActivity
中,这么写:
public class StudentContextActivity extends AppCompatActivity {
@Inject
StudentContext sContext;
TextView tv ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
tv = findViewById(R.id.id_tv_content);
//这里我们就需要提供一个StudentContextModule对象
//是的,这个对象是我们New的
DaggerStudentContextComponent.builder().studentContextModule(new StudentContextModule(this)).build().inject(this);
}
public void getStudentInfo(View v) {
tv.setText(sContext.toString());
}
}
结果为:
你可以看到,我们的Context
对象也被传入进去了,其实就是我们的StudentContextModule
被new
时传入的this
写入的。
这个意义,就相当我们每个应用中只有一个Application
,我们的Activity
可以依赖于这个Application
,然后可以Toast
,SharePreference
我们想要的东西。那么就来一个AppModule
,全局来一个Context
:
@Module
public class AppModule {
private Context context ;
public AppModule(Context context) {
this.context = context;
}
@Provides
public Context provideContext() {
return context;
}
@Provides
public String provideGlobalName() {
return "globalName" ;
}
}
那么它对应的Component
为:
@Component(modules = AppModule.class)
public interface AppComponent {
/**
* 向下提供Context
* @return
*/
Context getContext();
/**
* 向下提供Name
* @return
*/
String getStringName();
}
这里有一点需要声明一下,APPComponent
将要被依赖,那么它需要暴露出提供初始化对象的方法,然后依赖它的Component
将找不到。
好了,基类被依赖的APPComponent
已经有了,现在来一个ActivityComponent
,它提供了StudentContext
对象方法和score
对象方法。
@Module
public class ActivityModule {
@Provides
public StudentContext provideStudentContext(Context context,String name , int score) {
return new StudentContext(name,score,context);
}
@Provides
public int provideActivityScore() {
return 78 ;
}
}
那么重点来了,ActivityComponent
将会依赖AppComponent
,AppComponent
将会提供Context
对象方法和name
对象方法,注意dependencies
关键字,它指向了依赖的对象,同时依赖的对象也是一个Component
。
@Component(modules = ActivityModule.class, dependencies = AppComponent.class)
public interface ActivityComponent {
void inject(DependencyActivity activity);
}
在DependencyActivity
中:
public class DependencyActivity extends AppCompatActivity {
@Inject
StudentContext sContext;
TextView tv ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
tv = findViewById(R.id.id_tv_content);
//先获取AppComponent对象
AppComponent appComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();
//ActivityComponent对象将会依赖于AppComponent
DaggerActivityComponent.
builder().
appComponent(appComponent).
build().inject(this);
}
public void getStudentInfo(View v) {
tv.setText(sContext.toString());
}
}
主要解决同一个容器中,同时又多个相同对象需要被注入,如果你不做任何指导,那么dagger就不确定数据注入的唯一性(数据提供者 -> 数据接收者),此时有一种方法就是使用@Name注解:
还是定义一个StudentForNameAt
对象,可以看到它有一个name
和context
属性
public class StudentForNameAt {
private String name ;
private Context context;
public StudentForNameAt(String name) {
this.name = name;
}
public StudentForNameAt(Context context) {
this.context = context;
}
@Override
public String toString() {
return "StudentForNameAt{" +
"name='" + name + '\'' +
", context=" + context +
'}';
}
}
那么NameAtModule
需要这么写:
@Module
public class NameAtModule {
private Context context ;
private String name ;
public NameAtModule(Context context,String name) {
this.context = context;
this.name = name;
}
@Provides
public Context provideContext() {
return context;
}
@Named("context") //这里使用"contex"标识
@Provides
public StudentForNameAt provideStudentForNameAtContext() {
return new StudentForNameAt(context);
}
@Named("name") //这里使用"name"标识
@Provides
public StudentForNameAt provideStudentForNameAtName(){
return new StudentForNameAt(name);
}
}
最后,在我们的NameAtActivity
中,同时也需要@Name("name")
和@Name("context")
标识对象的注入:
public class NameAtActivity extends AppCompatActivity {
@Named("name") //这里标识
@Inject
StudentForNameAt at1 ;
@Named("context") //这里标识
@Inject
StudentForNameAt at2 ;
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
tv = findViewById(R.id.id_tv_content);
DaggerNameAtComponent.builder().nameAtModule(new NameAtModule(this,"hello world")).build().inject(this);
}
public void getStudentInfo(View v) {
tv.setText(at1.toString() + "\n" + at2.toString());
}
}
获取结果为:
可以看到,我们的两个StudentForNameAt
对象分别被注入了Name
和Context
。
当然这只是其中一种方法,还有一种方法就是自定义的Qualifier标识符。
@Qualifier标识符也是为了解决上面的问题,我们来同时定义一下两个注解标识符@StudentForName
和StudentForScore
:
@Qualifier //dagger Qualifier标识符
@Retention(RetentionPolicy.RUNTIME) //标识为运行时状态
public @interface StudentForName {}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface StudentForScore {
}
定义一个StudentQualifier
对象,如下:
public class StudentQualifier {
private String name ;
private int score ;
public StudentQualifier(){ //空的构造器 name是Tom score是77
this.name = "Tom";
this.score = 77 ;
}
public StudentQualifier(String name , int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "StudentQualifier{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
数据提供者Module
为:
@Module
public class StudentQualifierModule {
@Provides
public String provideName(){
return "zhangsan" ;
}
@Provides
public int provideScore(){
return 22 ;
}
@StudentForScore //默认构造器被@StudentForScore修饰
@Provides
public StudentQualifier provideStudent(){
return new StudentQualifier();
}
@StudentForName //有参构造器被@StudentForName修饰
@Provides
public StudentQualifier provideStudentWithParams(String name , int score){
return new StudentQualifier(name,score);
}
}
然后在数据QualifierActivity
接收器中:
public class QualifierActivity extends AppCompatActivity {
TextView tv;
@StudentForName //被@StudentForName修饰,按道理应该是调用了有参构造器
@Inject
StudentQualifier qualifier1 ;
@StudentForScore //这个就是无参数的
@Inject
StudentQualifier qualifier2 ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
tv = findViewById(R.id.id_tv_content);
DaggerStudentQualifierComponent.builder().build().inject(this);
}
public void getStudentInfo(View v) {
tv.setText(qualifier1.toString() + "\n" + qualifier2.toString());
}
}
我们猜测一下结果,qualifier1
应该为zhangsan
和22
;qualifier2
应该为这Tom
和77
,结果为:
结果一致。
我也不知道为什么,上github上看dagger写的代码,都少不了这个两个注解。如果理解了@Singleton
注解的意义,那么@PerApp
和@PerActivity
意义就差不多。上面已经说过,一个App中只有一个Applicataion
,那么我们可以定义一个注解@PerApp
生命周期可以与Application一致,那么就可以理解为@PerApp
修饰的方法,提供的对象在全局范围是单例的;如果定义一个@PerActivity
,那么@PerActivity
修饰的方法返回的对象,生命周期是与Activity
是一致的。举个例子吧:
定义一个@AndroidPerApp
@Scope //这里是@Scope 注意不是@Qualifier
@Documented //文档标记
@Retention(RetentionPolicy.RUNTIME)
public @interface AndroidPerApp {
}
再定义一个Module
,此时有个方法被@AndroidPerApp
修饰:
@Module
public class AndroidPerAppModule {
private Context context;
public AndroidPerAppModule(Context context) {
this.context = context ;
}
@Provides
@AndroidPerApp //标记该方法只产生一个实例 该实例的生命周期与绑定的Context生命周期一致
public Context provideContext(){
return context;
}
}
此时我们的AndroidPerAppComponent
@AndroidPerApp //AndroidPerAppModule有方法被@AndroidPerApp修饰 那么此时AndroidPerAppComponent也要被@AndroidPerApp修饰
@Component(modules = AndroidPerAppModule.class)
public interface AndroidPerAppComponent {
/**
* 向下提供Context
* @return
*/
Context getContext();
}
此时,既然是AppComponent
,那么需要与App绑定了:
public class MyApp extends Application{
//这里使用静态的 因为MyApp全局就一个
static AndroidPerAppComponent component ;
@Override
public void onCreate() {
super.onCreate();
component = DaggerAndroidPerAppComponent.builder().androidPerAppModule(new AndroidPerAppModule(this)).build();
}
public static AndroidPerAppComponent getAppComponent(){
return component;
}
}
那么我们可以全局调用MyApp.getAppComponent()
了,这个等会再用,现在再来顶一个@PerActivity
:
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface AndroidPerActivity {}
在AndroidPerActivityModule
中,Activity生命周期范围内,返回的数据是单例的,其实是和@Singleton
作用是一致的,它并没有像市面上说的,具有和Activity相同生命周期的说法。
@Module
public class AndroidPerActivityModule {
@AndroidPerActivity
@Provides
public Student provideStudent(){
return new Student();
}
}
然后最重要的是PerActivityComponent
了:
@AndroidPerActivity
@Component(modules = AndroidPerActivityModule.class,
dependencies = AndroidPerAppComponent.class)
public interface AndroidPerActivityComponent {
void inject(AndroidAnnotationActivity activity);
}
在AndroidAnnotationActivity
中:
public class AndroidAnnotationActivity extends AppCompatActivity {
@Inject
Student st1 ;
@Inject
Student st2;
TextView tv ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
tv = findViewById(R.id.id_tv_content);
//直接使用App.getAppComponent()获取全局的AppComponent对象
DaggerAndroidPerActivityComponent.builder().androidPerAppComponent(MyApp.getAppComponent()).build().inject(this);
}
public void getStudentInfo(View v) {
//Toast.makeText(this, "---->>" + MyApp.getAppComponent(), Toast.LENGTH_SHORT).show();
tv.setText(st1.toString() + "--\n--" + st2.toString());
}
}
我们此时开看一下st1
和st2
的结果:
可以看出,它们在AndroidAnnotationActivity
范围内是单例的,这个和@Singleton
注解是一致的。工作中我们会用到@PerApp
,它解决了我们全局需要Context的问题。
根据词义,Lazy
相当于我们懒加载机制,调用了本方法才会去加载,没有调用就不加载;Provider
很类似@Provides
注解,但是它在的含义是每次调用都会去重复调用Module
被@Provides
修饰的方法。举个例子:
定义一个对象类型StudentOther
:
public class StudentOther {
private String name ;
private int score ;
private String address ;
//初始化过程中会使用random对象 以此来模拟是否为重复创建
public StudentOther(Random random) {
this.name = "zhansgan" + random.nextDouble() ;
this.score = random.nextInt(200);
this.address = "shanghai" + random.nextGaussian();
}
@Override
public String toString() {
return "StudentOther{" +
"name='" + name + '\'' +
", score=" + score +
", address='" + address + '\'' +
'}';
}
}
定义一个OtherActivityModule
对象,提供了Random方法,还有两个@StudentForName
和@StudentForScore
注解修饰的方法:
@Module
public class OtherActivityModule {
public Random random ;
public OtherActivityModule(){
random = new Random();
}
@Provides
public Random provideRandom(){
return random;
}
@StudentForName
@Provides
public StudentOther provideStudentOther(Random random){
return new StudentOther(random);
}
@StudentForScore
@Provides
public StudentOther provideStudentOtherWidth(Random random){
return new StudentOther(random);
}
}
在数据接收器OtherActivity
中,如下:
public class OtherActivity extends AppCompatActivity {
@StudentForScore
@Inject
Lazy other;
@StudentForName
@Inject
Provider other2 ;
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
tv = findViewById(R.id.id_tv_content);
DaggerOtherActivityComponent.builder().build().inject(this);
}
public void getStudentInfo(View v){
StudentOther innerOther = other.get(); //调用该方法时才会去创建
StudentOther innerOther2 = other2.get(); //调用该方法时才会去创建,但是每次都会重新加载Module中的方法 返回值有可能相同 也有可能不同
tv.setText(innerOther.toString() + "---\n---" + innerOther2.toString());
}
}
我们来看一下结果:
我们可以看到innerOther
始终是同一个值,而innerOther2
却一直是变化的,说明每次调用Provide.get()
时都去刷新了
@StudentForName
@Provides
public StudentOther provideStudentOther(Random random){
return new StudentOther(random);
}
方法。