简介


Dagger-匕首,鼎鼎大名的Square公司旗下又一把利刃(没错!还有一把黄油刀,唤作ButterKnife)
 Dagger2 是一个Android依赖注入框架,由谷歌开发,最早的版本Dagger1 由Square公司开发。依赖注入框架主要用于模块间解耦,提高代码的健壮性和可维护性。Dagger 这个库的取名不仅仅来自它的本意“匕首”,同时也暗示了它的原理。

  Android开发从一开始的MVC框架,到MVP,到MVVM,不断变化。现在MVVM的data-binding还在实验阶段,传统的MVC框架Activity内部可能包含大量的代码,难以维护,现在主流的架构还是使用MVP(Model + View + Presenter)的方式。但是 MVP 框架也有可能在Presenter中集中大量的代码,引入DI框架Dagger2 可以实现 Presenter 与 Activity 之间的解耦,Presenter和其它业务逻辑之间的解耦,提高模块化和可维护性。

为什么使用依赖注入

首先我们需要知道,人们在很长的一段时间里都是利用控制反转原则规定:应用程序的流程取决于在程序运行时对象图的建立。通过抽象定义的对象交互可以实现这样的动态流程。而使用依赖注入技术或者服务定位器便可以完成运行时绑定。

使用依赖注入可以带来以下好处:

依赖的注入和配置独立于组件之外。
因为对象是在一个独立、不耦合的地方初始化,所以当注入抽象方法的时候,我们只需要修改对象的实现方法,而不用大改代码库。
依赖可以注入到一个组件中:我们可以注入这些依赖的模拟实现,这样使得测试更加简单。

可以看到,能够管理创建实例的范围是一件非常棒的事情。按我的观点,你app中的所有对象或者协作者都不应该知道有关实例创建和生命周期的任何事情,这些都应该由我们的依赖注入框架管理的。

什么是JSR-330?
为了最大程度的提高代码的复用性、测试性和维护性,Java的依赖注入为注入类中的使用定义了一整套注解(和接口)标准。Dagger1和Dagger2(还有Guice)都是基于这套标准,给程序带来了稳定性和标准的依赖注入方法。

Dagger1
这个版本不是这篇文章的重点,所以我只是简略地说一下。不管怎样,Dagger1还是做了很多的贡献,可以说是如今Android上最流行的依赖注入框架。它是由Square公司受到Guice启发创建的。

基本特点:

多个注入点:依赖,通过injected
多种绑定方法:依赖,通过provided
多个modules:实现某种功能的绑定集合
多个对象图: 实现一个范围的modules集合
Dagger1是在编译的时候实行绑定,不过也用到了反射机制。但这个反射不是用来实例化对象的,而是用于图的构成。Dagger会在运行的时候去检测是否一切都正常工作,所以使用的时候会付出一些代价:偶尔会无效和调试困难。

Dagger2
Dagger2是Dagger1的分支,由谷歌公司接手开发,目前的版本是2.0。Dagger2是受到AutoValue项目的启发。 刚开始,Dagger2解决问题的基本思想是:利用生成和写的代码混合达到看似所有的产生和提供依赖的代码都是手写的样子。

如果我们将Dagger2和1比较,他们两个在很多方面都非常相似,但也有很重要的区别,如下:

再也没有使用反射:图的验证、配置和预先设置都在编译的时候执行。
容易调试和可跟踪:完全具体地调用提供和创建的堆栈
更好的性能:谷歌声称他们提高了13%的处理性能
代码混淆:使用派遣方法,就如同自己写的代码一样
当然所有这些很棒的特点都需要付出一个代价,那就是缺乏灵活性,例如:Dagger2没用反射所以没有动态机制。

小讲解—降低耦合度的方法

一旦 new出实例。那么就产生耦合

比如
A

new a

B

new b

C

new c

检验耦合度的方法

当我们删除 abc时 发现A,B,C都报错了。

而当我们使用工厂模式管理起 a b c的实例时。
删除 abc时 发现 只有Factory类编译报错。

三个类与一个类相比 当然是前者的耦合度高。

我们这里说的是降低耦合度,而不是去掉耦合。

环境搭建

buildscript {repositories {jcenter()}dependencies {classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'}
}android {...
}#module apply plugin: 'com.neenbedankt.android-apt'dependencies {//dagger startapt 'com.google.dagger:dagger-compiler:2.0'//dagger endcompile 'com.google.dagger:dagger:2.0'compile 'org.glassfish:javax.annotation:10.0-b28'compile 'com.google.android.gms:play-services:8.4.0'...
}

@Inject与Compoent使用小结


用Inject注解标注目标类中其他类
用Inject注解标注其他类的构造函数
若其他类还依赖于其他的类,则重复进行上面2个步骤
调用Component(注入器)的injectXXX(Object)方法开始注入(injectXXX方法名字是官方推荐的名字,
以inject开始)

public class UserInfo {@Injectpublic UserInfo() {}public String name="黑马";}
public class MyActivity extends AppCompatActivity {@InjectUserInfo info;

使用@Component申明注入器

@Component
public interface MyActivityComponent {void inject(MyActivity activity);
}

调用apt编译后的Component

@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_my);//使用Component注入DaggerMyActivityComponent.builder().build().inject(this);}

@Module与@Provider使用小结

有Inject与Component不就够了?为啥又造出个Module?
现在有个新问题:项目中使用到了第三方的类库(比如是jar包类型的),不能修改,
所以根本不可能把Inject注解加入这些类中,这时我们的Inject就失效了。

Inject,Component,Module,Provides是dagger2中的最基础最核心的知识点。
奠定了dagger2的整个依赖注入框架。

Inject主要是用来标注目标类的依赖和依赖的构造函数
Component它是一个桥梁,一端是目标类,另一端是目标类所依赖类的实例,
它也是注入器(Injector)负责把目标类所依赖类的实例注入到目标类中,同时它也管理Module。
Module和Provides是为解决第三方类库而生的,Module是一个简单工厂模式,Module可以包含创建类实例的方法,
这些方法用Provides来标注

Qualifier(限定符)、Singleton(单例)、Scope(作用域)、Component的组织方式

#

基于同一个维度条件下,若一个类的实例有多种方法可以创建出来,
那注入器(Component)应该选择哪种方法来创建该类的实例呢?

Qualifier(限定符)就是解决依赖注入迷失问题的。
注意
dagger2在发现依赖注入迷失时在编译代码时会报错。

@SingleTon与@Qualifier与@Scope小结

Qualifier(限定符)、Singleton(单例)、Scope(作用域)、Component的组织方式

基于同一个维度条件下,若一个类的实例有多种方法可以创建出来,
那注入器(Component)应该选择哪种方法来创建该类的实例呢?

Qualifier(限定符)就是解决依赖注入迷失问题的。
注意
dagger2在发现依赖注入迷失时在编译代码时会报错。

Singleton注解标注一个创建类实例的方法,该创建类实例的方法就可以创建一个唯一的类实例。

一般在Application中使用比较好,因为Applilcation是一个应用只有一个实例。

在Module中定义创建全局类实例的方法
ApplicationComponent管理Module
保证ApplicationComponent只有一个实例(在app的Application中实例化ApplicationComponent)

Dagger2注解

Dagger2使用过程中我们通常接触到的注解主要包括:@Inject, @Module, @Provides, @Component, @Qulifier, @Scope, @Singleten。

@Inject:@Inject有两个作用,一是用来标记需要依赖的变量,以此告诉Dagger2为它提供依赖;二是用来标记构造函数,Dagger2通过@Inject注解可以在需要这个类实例的时候来找到这个构造函数并把相关实例构造出来,以此来为被@Inject标记了的变量提供依赖;

@Module:@Module用于标注提供依赖的类。你可能会有点困惑,上面不是提到用@Inject标记构造函数就可以提供依赖了么,为什么还需要@Module?很多时候我们需要提供依赖的构造函数是第三方库的,我们没法给它加上@Inject注解,又比如说提供以来的构造函数是带参数的,如果我们之所简单的使用@Inject标记它,那么他的参数又怎么来呢?@Module正是帮我们解决这些问题的。

@Provides:@Provides用于标注Module所标注的类中的方法,该方法在需要提供依赖时被调用,从而把预先提供好的对象当做依赖给标注了@Inject的变量赋值;

@Component:它是一个桥梁,一端是目标类,另一端是目标类所依赖类的实例,
它也是注入器(Injector)负责把目标类所依赖类的实例注入到目标类中,
同时它也管理Module。Module和Provides是为解决第三方类库而生的,

@Qulifier:@Qulifier用于自定义注解,也就是说@Qulifier就如同Java提供的几种基本元注解一样用来标记注解类。我们在使用@Module来标注提供依赖的方法时,方法名我们是可以随便定义的(虽然我们定义方法名一般以provide开头,但这并不是强制的,只是为了增加可读性而已)。那么Dagger2怎么知道这个方法是为谁提供依赖呢?答案就是返回值的类型,Dagger2根据返回值的类型来决定为哪个被@Inject标记了的变量赋值。但是问题来了,一旦有多个一样的返回类型Dagger2就懵逼了。@Qulifier的存在正式为了解决这个问题,我们使用@Qulifier来定义自己的注解,然后通过自定义的注解去标注提供依赖的方法和依赖需求方(也就是被@Inject标注的变量),这样Dagger2就知道为谁提供依赖了。—-一个更为精简的定义:当类型不足以鉴别一个依赖的时候,我们就可以使用这个注解标示;

@Scope:@Scope同样用于自定义注解,我能可以通过@Scope自定义的注解来限定注解作用域,实现局部的单例;

@Singleton:@Singleton其实就是一个通过@Scope定义的注解,我们一般通过它来实现全局单例。但实际上它并不能提前全局单例,是否能提供全局单例还要取决于对应的Component是否为一个全局对象。

我们提到@Inject和@Module都可以提供依赖,那如果我们即在构造函数上通过标记@Inject提供依赖,有通过@Module提供依赖Dagger2会如何选择呢?具体规则如下:

步骤1:首先查找@Module标注的类中是否存在提供依赖的方法。
步骤2:若存在提供依赖的方法,查看该方法是否存在参数。
a:若存在参数,则按从步骤1开始依次初始化每个参数;
b:若不存在,则直接初始化该类实例,完成一次依赖注入。
步骤3:若不存在提供依赖的方法,则查找@Inject标注的构造函数,看构造函数是否存在参数。
a:若存在参数,则从步骤1开始依次初始化每一个参数
b:若不存在,则直接初始化该类实例,完成一次依赖注入。

代码案例(MVP+Dagger2开发,进一步降低耦合性)

搭建dagger2的使用环境

在工作空间的gradle文件添加

classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'

apt annotation process tool 注解处理工具,在程序运行前自动生成一些代码。
在当前模块上面添加 使用

apply plugin: 'com.neenbedankt.android-apt'

依赖dagger2的相关库

//依赖 dagger2相关的库
//dagger start
apt 'com.google.dagger:dagger-compiler:2.0'
//dagger end
compile 'com.google.dagger:dagger:2.0'
compile 'org.glassfish:javax.annotation:10.0-b28'

分包

View包下

MyLoginActivity

public class MyLoginActivity extends AppCompatActivity implements ILoginView {@InjectView(R.id.username)EditText username;@InjectView(R.id.password)EditText password;@InjectView(R.id.login)Button login;@InjectView(R.id.pbar)ProgressBar pbar;@InjectILoginPresenter loginPresneter;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_login);//ButterKnife找到相关控件ButterKnife.inject(this);//loginPresneter = new MyLoginPresneter(this);原先时候New//依赖注入myLoginActivityModuleDaggerMyLoginActivityComponent.builder().myLoginActivityModule(new MyLoginActivityModule(this)).build().inject(this);}@OnClick(R.id.login)public void onClick() {loginPresneter.loginLogic(username.getText().toString(), password.getText().toString());}@Overridepublic void showLoading(int visible) {pbar.setVisibility(visible);}@Overridepublic void showFailure() {runOnUiThread(new Runnable() {@Overridepublic void run() {pbar.setVisibility(View.INVISIBLE);Toast.makeText(MyLoginActivity.this, "账号或者密码出错", Toast.LENGTH_SHORT).show();}});}@Overridepublic void showSuccess() {runOnUiThread(new Runnable() {@Overridepublic void run() {pbar.setVisibility(View.INVISIBLE);Toast.makeText(MyLoginActivity.this, "欢迎回来", Toast.LENGTH_SHORT).show();}});}
}

ILoginView

public interface ILoginView {//界面显示状态 在接口中使用方法列出来public void showLoading(int visible);public void showFailure();public void showSuccess();
}

DI包下

MyLoginActivityModule

import com.itheima.mymvp.presenter.ILoginPresenter;import com.itheima.mymvp.presenter.MyLoginPresenter;
import com.itheima.mymvp.view.MyLoginActivity;import dagger.Module;
import dagger.Provides;@Module
public class MyLoginActivityModule {private MyLoginActivity myLoginActivity;public MyLoginActivityModule(MyLoginActivity activity) {myLoginActivity = activity;}@Providespublic ILoginPresenter prvoideMyLoginPresenter() {return new MyLoginPresenter(myLoginActivity);}
}

MyLoginActivityComponent声明注入到哪

@Component(modules = MyLoginActivityModule.class)
public interface MyLoginActivityComponent {public  void inject(MyLoginActivity activity);
}

Presenter包下

ILoginPresenter

public interface ILoginPresenter {public void loginLogic(String username, String pwd);
}

MyLoginPresenter

public class MyLoginPresenter implements ILoginPresenter {private IMyUserModule model;private ILoginView view;public MyLoginPresenter(ILoginView view) {this.view = view;this.model=new MyUser();}@Overridepublic void loginLogic(final String username, final String pwd) {view.showLoading(View.VISIBLE);new Thread() {@Overridepublic void run() {super.run();boolean flag = model.checkUsernmaeAndPasswod(username, pwd);if (flag) {view.showSuccess();} else {view.showFailure();}}}.start();}
}

Model包下

IMyUserModule

public interface IMyUserModule {//列出需要处理的逻辑boolean checkUsernmaeAndPasswod(String username, String pwd);
}

MyUser

public class MyUser implements IMyUserModule {//列出需要处理的逻辑@Overridepublic boolean checkUsernmaeAndPasswod(String username, String pwd) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(pwd)) {if (TextUtils.equals("itheima", username) && TextUtils.equals("bj", pwd)) {return true;}}return false;}//如果非空 判断是否等于itheima bj
}

小案例进一步说明

1、案例A
Car类是需求依赖方,依赖了Engine类;因此我们需要在类变量Engine上添加@Inject来告诉Dagger2来为自己提供依赖。

public class Car {@InjectEngine engine;public Car() {DaggerCarComponent.builder().build().inject(this);}public Engine getEngine() {return this.engine;}
}

Engine类是依赖提供方,因此我们需要在它的构造函数上添加@Inject

public class Engine {@InjectEngine(){}public void run(){System.out.println("引擎转起来了~~~");}
}

接下来我们需要创建一个用@Component标注的接口CarComponent,这个CarComponent其实就是一个注入器,这里用来将Engine注入到Car中。

@Component
public interface CarComponent {void inject(Car car);
}

完成这些之后我们需要Build下项目,让Dagger2帮我们生成相关的Java类。接着我们就可以在Car的构造函数中调用Dagger2生成的DaggerCarComponent来实现注入(这其实在前面Car类的代码中已经有了体现)

public Car() {DaggerCarComponent.builder().build().inject(this);
}

2、案例B

如果创建Engine的构造函数是带参数的呢?比如说制造一台引擎是需要齿轮(Gear)的。或者Eggine类是我们无法修改的呢?这时候就需要@Module和@Provide上场了。

同样我们需要在Car类的成员变量Engine上加上@Inject表示自己需要Dagger2为自己提供依赖;Engine类的构造函数上的@Inject也需要去掉,应为现在不需要通过构造函数上的@Inject来提供依赖了。

public class Car {@InjectEngine engine;public Car() {DaggerCarComponent.builder().markCarModule(new MarkCarModule()).build().inject(this);}public Engine getEngine() {return this.engine;}
}

接着我们需要一个Module类来生成依赖对象。前面介绍的@Module就是用来标准这个类的,而@Provide则是用来标注具体提供依赖对象的方法(这里有个不成文的规定,被@Provide标注的方法命名我们一般以provide开头,这并不是强制的但有益于提升代码的可读性)。

@Module
public class MarkCarModule {public MarkCarModule(){ }@Provides Engine provideEngine(){return new Engine("gear");}
}

接下来我们还需要对CarComponent进行一点点修改,之前的@Component注解是不带参数的,现在我们需要加上modules = {MarkCarModule.class},用来告诉Dagger2提供依赖的是MarkCarModule这个类。

@Component(modules = {MarkCarModule.class})
public interface CarComponent {void inject(Car car);
}

Car类的构造函数我们也需要修改,相比之前多了个markCarModule(new MarkCarModule())方法,这就相当于告诉了注入器DaggerCarComponent把MarkCarModule提供的依赖注入到了Car类中。

public Car() {DaggerCarComponent.builder().markCarModule(new MarkCarModule()).build().inject(this);
}

这样一个最最基本的依赖注入就完成了,接下来我们测试下我们的代码。

public static void main(String[] args){Car car = new Car();car.getEngine().run();
}

输出

引擎转起来了~~~

3、案例C
那么如果一台汽车有两个引擎(也就是说Car类中有两个Engine变量)怎么办呢?没关系,我们还有@Qulifier!首先我们需要使用Qulifier定义两个注解:

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface QualifierA { }
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface QualifierB { }

同时我们需要对依赖提供方做出修改

@Module
public class MarkCarModule {public MarkCarModule(){ }@QualifierA@ProvidesEngine provideEngineA(){return new Engine("gearA");}@QualifierB@ProvidesEngine provideEngineB(){return new Engine("gearB");}
}

接下来依赖需求方Car类同样需要修改

public class Car {@QualifierA @Inject Engine engineA;@QualifierB @Inject Engine engineB;public Car() {DaggerCarComponent.builder().markCarModule(new MarkCarModule()).build().inject(this);}public Engine getEngineA() {return this.engineA;}public Engine getEngineB() {return this.engineB;}
}

最后我们再对Engine类做些调整方便测试

public class Engine {private String gear;public Engine(String gear){this.gear = gear;}public void printGearName(){System.out.println("GearName:" + gear);}
}

测试代码

public static void main(String[] args) {Car car = new Car();car.getEngineA().printGearName();car.getEngineB().printGearName();
}

执行结果:

GearName:gearA
GearName:gearB

4、案例D
接下来我们看看@Scope是如何限定作用域,实现局部单例的。

首先我们需要通过@Scope定义一个CarScope注解:

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface CarScope {}

接着我们需要用这个@CarScope去标记依赖提供方MarkCarModule。

@Module
public class MarkCarModule {public MarkCarModule() {}@Provides@CarScopeEngine provideEngine() {return new Engine("gear");}
}

同时还需要使用@Scope去标注注入器Compoent

@CarScope
@Component(modules = {MarkCarModule.class})
public interface CarComponent {void inject(Car car);
}

为了便于测试我们对Car和Engine类做了一些改造:

public class Car {@Inject Engine engineA;@Inject Engine engineB;public Car() {DaggerCarComponent.builder().markCarModule(new MarkCarModule()).build().inject(this);}
}
public class Engine {private String gear;public Engine(String gear){System.out.println("Create Engine");this.gear = gear;}
}

如果我们不适用@Scope,上面的代码会实例化两次Engine类,因此会有两次”Create Engine”输出。现在我们在有@Scope的情况测试下劳动成果:

public static void main(String[] args) {Car car = new Car();System.out.println(car.engineA.hashCode());System.out.println(car.engineB.hashCode());
}

输出

Create Engine

bingo!我们确实通过@Scope实现了局部的单例。

Android神匕首—Dagger2依赖注入框架详解相关推荐

  1. spring依赖注入原理详解(转载)

    spring依赖注入原理详解----转载 所谓依赖注入就是指:在运行期,由外部容器动态地将依赖对象注入到组件中.当spring容器启动后,spring容器初始化,创建并管理bean对象,以及销毁它.所 ...

  2. Dagger Hilt - Android官方推荐的依赖注入框架

    Dagger Hilt Android端有不少DI框架可供选择 – 例如用于控件注入的ButterKnife.用于Kotlin的Koin等,但唯有Dagger才称得上是谷歌官方认可的DI方案. Dag ...

  3. Android hilt 依赖注入使用详解

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/128424833 本文出自[赵彦军的博客] 文章目录 官方文档 作用域于树图 添加依 ...

  4. 控制反转(IoC)与依赖注入(DI)详解

    文章目录 什么是控制反转(IoC) 控制反转(IoC)有什么作用 控制反转(IoC)是怎么分类的 依赖注入 接口注入 Setter方法注入 构造器注入 依赖查找 上下文依赖查找(Contextuali ...

  5. Spring 依赖注入方式详解

    平常的Java开发中,程序员在某个类中需要依赖其它类的方法. 通常是new一个依赖类再调用类实例的方法,这种开发存在的问题是new的类实例不好统一管理. Spring提出了依赖注入的思想,即依赖类不由 ...

  6. spring依赖注入原理详解

    所谓依赖注入就是指:在运行期,由外部容器动态地将依赖对象注入到组件中.当spring容器启动后,spring容器初始化,创建并管理bean对象,以及销毁它.所以我们只需从容器直接获取Bean对象就行, ...

  7. golang 依赖注入 dig详解

    golang的依赖注入dig 一篇文章带你了解golang依赖注入 dig介绍: dig 库是一个为 go 提供依赖注入 (dependency injection) 的工具包,基于 reflecti ...

  8. spring的依赖注入方式详解

    第一种:使用setter注入 直接使用bean元素的子元素property进行相关的设置 ①简单类型,直接使用value进行赋值. <bean id="somebean" c ...

  9. 深入浅出依赖注入框架Dagger2

    目录 目录 依赖注入 依赖注入实现的三种方式 1. 构造注入 2. 属性注入 3. 接口注入 Dagger2 Dagger2的引入 不带Module的Inject方式(Inject+Component ...

最新文章

  1. Linux版本之redhat9---gFtp中文乱码解决方案
  2. 基于FPGA的目标点的提取与定位系统设计
  3. 八十一、使用Springboot实现发邮件的需求
  4. java.lang.NumberFormatException: multiple points错误问题
  5. MyBatis总结七:动态sql和sql片段
  6. 常用idea快捷键大全
  7. php foreach 时间,PHP利用for,while,foreach遍历时间比较的简单示例
  8. 【Java/Android性能优 7】Android公共库——图片缓存 网络缓存 下拉及底部更多ListView 公共类...
  9. android源码编译完成之后
  10. 存储在U盘中的文件被误删后怎么免费恢复
  11. dos2unix命令
  12. c++语言循环读写文件夹,【C++探索之旅】第一部分第十课:文件读写,海阔凭鱼跃...
  13. 腾讯防水墙(滑动验证码)的简单使用 https://007.qq.com
  14. Ubuntu Server 20.04 下 HustOJ 安装
  15. Netty 心跳机制及断线重连
  16. 医保不用选也能报销的北京市医保定点专科和A类医院名单
  17. tZERO母公司Overstock股票五个月暴涨37倍背后:已深耕区块链业务数年
  18. Python:for循环语句
  19. P4090 [USACO17DEC]Greedy Gift Takers
  20. 给游戏设计新人的一些建议

热门文章

  1. 笔记本电脑性价比排行2020榜单“黑马”开箱体验大分享
  2. 奇瑞无界Pro来啦,身虽小内心很强大
  3. 印象笔记三级目录_09小马哥:如何建立印象笔记的分级目录。
  4. redis java zset_Redis从入门到放弃系列(五) ZSet
  5. 演进:如何在工作的前三年里快速成长
  6. vite创建的vue项目公众号本地开发内网穿透
  7. 人机交互中情境认知的研究
  8. 惠逛街:用一件白衬衫,撑起你该有的美貌!
  9. 计算机工作两年,决定考研的思考过程
  10. 我的两年考研路[已上岸]