首页 > 系统 > Android > 正文

Android神匕首—Dagger2依赖注入框架详解

2019-11-09 18:33:49
字体:
来源:转载
供稿:网友

这里写图片描述

简介

这里写图片描述 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 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' compile 'com.google.android.gms:play-services:8.4.0' ...}

@Inject与Compoent使用小结

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

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

使用@Component申明注入器

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

调用apt编译后的Component

@Override protected 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 startapt 'com.google.dagger:dagger-compiler:2.0'//dagger endcompile '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; @Inject ILoginPresenter loginPresneter; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); //ButterKnife找到相关控件 ButterKnife.inject(this);//loginPresneter = new MyLoginPresneter(this);原先时候New //依赖注入myLoginActivityModule DaggerMyLoginActivityComponent .builder() .myLoginActivityModule(new MyLoginActivityModule(this)) .build().inject(this); } @OnClick(R.id.login) public void onClick() { loginPresneter.loginLogic(username.getText().toString(), password.getText().toString()); } @Override public void showLoading(int visible) { pbar.setVisibility(visible); } @Override public void showFailure() { runOnUiThread(new Runnable() { @Override public void run() { pbar.setVisibility(View.INVISIBLE); Toast.makeText(MyLoginActivity.this, "账号或者密码出错", Toast.LENGTH_SHORT).show(); } }); } @Override public void showSuccess() { runOnUiThread(new Runnable() { @Override public 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;@Modulepublic class MyLoginActivityModule { private MyLoginActivity myLoginActivity; public MyLoginActivityModule(MyLoginActivity activity) { myLoginActivity = activity; } @Provides public 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(); } @Override public void loginLogic(final String username, final String pwd) { view.showLoading(View.VISIBLE); new Thread() { @Override public 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 { //列出需要处理的逻辑 @Override public 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 { @Inject Engine engine; public Car() { DaggerCarComponent.builder().build().inject(this); } public Engine getEngine() { return this.engine; }}

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

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

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

@Componentpublic 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 { @Inject Engine engine; public Car() { DaggerCarComponent.builder().markCarModule(new MarkCarModule()) .build().inject(this); } public Engine getEngine() { return this.engine; }}

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

@Modulepublic 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 { }

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

@Modulepublic class MarkCarModule { public MarkCarModule(){ } @QualifierA @Provides Engine provideEngineA(){ return new Engine("gearA"); } @QualifierB @Provides Engine 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:gearAGearName:gearB

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

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

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

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

@Modulepublic class MarkCarModule { public MarkCarModule() { } @Provides @CarScope Engine 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实现了局部的单例。


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表