一、为什么要使用Dagger2
Dagger2是一个Android端的依赖注入框架,如果你不了解什么是依赖注入的话,我想你看这篇文章也没有必要,当然如果是为了学习新技术,推荐去google一下什么叫依赖注入,有很多好文章,这篇介绍的是dagger2的使用,就不介绍什么是依赖注入了和依赖注入的好处了。
二、基本使用
使用时我们要有一个基本思想,所谓的依赖注入是事先把要用到的对象实例化,然后利用注入器注入到要用这个对象的地方。所以我们要做两件事,第一,实例化对象。第二,写一个构造器。
1、利用@Inject直接定义构造方法
现在有这样一个类
public class Animal {
private String name;
public Animal(){
this.name ="puppy";
}
}
我们要注入这样一个类,可以分一下几步走
1)利用@Inject注解构造方法,这一步就是直接宣告这个类可以通过这个构造方法实例化。
public class Animal {
private String name;
@Inject
public Animal(){
this.name ="puppy";
}
public String getName(){
return name;
}
}
2)利用@Component声明一个注入器
@Component()
public interface AnimalMainComponet {
void inject( MainActivity mainActivity);
}
如果想要声明一个注入器,那么我们要创建一个接口,利用@Component声明,然后写一个inject方法,传入的必须是要注入的类的强类型,上面这个例子说明我们想注入到MainActivity中,注意,这里的方法可以随意命名,不会影响。当然,除了使用接口,我们也可以使用抽象类:
@Component()
public abstract class AnimalMainComponet {
abstract void inject( MainActivity mainActivity);
}
3)开始注入
因为dagger2是基于编译期生成代码的,所以先编译一下,就会自动生成一个以Dagger开头的类,利用这个类来注入
public class MainActivity extends AppCompatActivity {
@Inject Animal animal;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = (TextView)findViewById(R.id.tv);
DaggerAnimalMainComponet.builder().build().inject(this);
tv.setText(animal.getName());
}
}
利用@Inject直接声明下Animal,然后利用DaggerAnimalMainCompomet注入下就可以使用animal了。运行结果
利用@Inject直接注入构造方法非常简单,但是有缺点
public class User {
private String name;
private String age;
public User(Car car){
this.name =car.getName();
this.age ="345";
}
public User(String name,String age){
this.name =name;
this.age = age;
}
public String getInfo(){
return name+" "+age;
}
}
这里有两个构造方法,但是,@Inject只能用来指明使用一个构造方法注入,而且这个方法还必须是无参的,在这里就不能这么用了。另外,我们经常用到第三方的包,是不可能修改第三方包中的构造方法的。所以这种注入方式虽然简单,但是不怎么用。我们接着介绍第二种方法:
2、利用@Module,@Provides注入。
还是以这个Animal为例,假设我么无法修改这个Animal类:
public class Animal {
private String name;
public Animal(){
this.name ="puppy";
}
}
我们按照以下步骤来
1)利用@Module声明一个类:这步是为了宣告这个类可以用来提供实例化好了的对象
@Module
public class AnimalModule {
}
2)在@Module类中利用@Provides来预先生成要用的对象。
@Module
public class AnimalModule {
@Provides
Animal provideAnimal(){
return new Animal();
}
}
这里真正提供Animal对象的是利用@Provides注解的provideAnimal方法,需要注意的是提供对象的方法必须以provide开头。
3)利用@Component声明注入器
@Component(modules = AnimalModule.class)
public interface AnimalMainComponet {
void inject( MainActivity mainActivity);
}
这里和上面不同的是要指明我们从哪里获取需要的对象,用的就是
@Component(modules = AnimalModule.class)
如果这里要从多个地方获取不同的对象,可以这样写
@Component(modules = {AnimalModule.class,CarModule.class,UserModule.class})
4)开始注入
public class MainActivity extends AppCompatActivity {
@Inject Animal animal;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = (TextView)findViewById(R.id.tv);
DaggerAnimalMainComponet.builder().build().inject(this);
tv.setText(animal.getName());
}
}
真正注入的实现和第一种方法中提到的是一模一样的。三、复杂应用
1. 构造方法带参数怎么办
现在有这样一个类
public class Car {
private String name;
public Car(String name){
this.name = name;
}
public String getName(){
return name;
}
}
用上面的第一种方法肯定不行,那让我们用第二种方法来写
写一个@Module,并且提供一个@Provides方法
@Module
public class CarModule {
@Provides
Car provideCar(){
return new Car("宝马");
}
}
这里问题来了,我们只能在这部把传入的参数写死了,但是我想在用的时候再传入参数怎么办呢?可以利用CarModule传入
@Module
public class CarModule {
private String inputCarName;
public CarModule(String name){
inputCarName = name;
}
@Provides
Car provideCar(){
return new Car(inputCarName);
}
}
这里我们在CarModule中定义了一个参数用来接收传入的参数,然后在provideCar中使用,在MainActivity中这样使用
public class MainActivity extends AppCompatActivity {
@Inject Car car;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = (TextView)findViewById(R.id.tv);
DaggerCarMainComponent.builder().carModule(new CarModule("奔驰")).build().inject(this);
tv.setText(car.getName());
}
}
和之前的比起来,注入的时候多了一个carModule方法,这里我们可以实例化自己的CarModule,随意传入参数。看下运行结果
2. 有多个构造方法怎么办
有这样一个类要注入:
public class User {
private String name;
private int age;
public User(int age){
this.age =age;
name = "defaultName";
}
public User(String name){
this.name =name;
this.age =99;
}
public String getInfo(){
return name+" "+age;
}
}
这个类中有两个构造方法,那么我们在@Module中提供实例时也应该提供两个不同的方法,分别用不同的构造方法来生成对象,比如这样
@Provides
public User provideUserByName(){
return new User(name);
}
@Provides
public User provideUserByAge(){
return new User(age);
}
但是,很不幸,这样会报错,因为Dagger2框架不知道应该选哪个方法去生成User对象。知道原因就好解决了,我们可以用@Name注解表明下
@Module
public class UserModule {
private String name;
private int age;
public UserModule(String name,int age){
this.name =name;
this.age =age;
}
@Named("ByName")
@Provides
public User provideUserByName(){
return new User(name);
}
@Named("ByAge")
@Provides
public User provideUserByAge(){
return new User(age);
}
}
在使用的时候可以这样用
public class MainActivity extends AppCompatActivity {
@Named("ByName")
@Inject
User user1;
@Named("ByAge")
@Inject
User user2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = (TextView)findViewById(R.id.tv);
DaggerUserMainActivityComponent.builder().userModule(new UserModule("hello",100)).build().inject(this);
tv.setText("ByName: "+user1.getInfo()+" ByAge: "+user2.getInfo());
}
}
这样就知道user1 是用了provideUserByName()这个方法生成,user2是用了provideUserByAge()这个方法生成的。这样问题确实解决了,但是字符串很容易出错啊,一不小心拼错了怎么办? 为了更方便的使用,我们可以使用@Qualifier 注解来自定义一个注解,达到上面的字符串一样的效果。
首先自定义两个注解来代替上面的两个字符串:
用来代替ByName字符串的注解@ByName
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface ByName {
}
以及用来代替ByAge字符串的注解@ByAge
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface ByAge {
}
用法这样的,在@Module中
@ByName
@Provides
public User provideUserByName(){
return new User(name);
}
@ByAge
@Provides
public User provideUserByAge(){
return new User(age);
}
@ByName
@Inject
User user1;
@ByAge
@Inject
User user2;
3. 构造方法的参数中有要靠依赖注入的对象
先看下我们的项目结构,我们现在有三个类可以实现依赖注入了,现在我们有这样一个类
public class InfoUtil {
public Animal animal;
public Car car;
public User user;
public InfoUtil(Animal animal) {
this.animal = animal;
}
public InfoUtil(Car car) {
this.car = car;
}
public InfoUtil(User user) {
this.user = user;
}
public String getAnimalInfo(){
return "the name of animal is : "+animal.getName();
}
public String getCarInfo(){
return "the name of car is :"+car.getName();
}
public String getUserInfo(){
return ""+user.getInfo();
}
}
我们来看看这个类的module怎么写
@Module
public class InfoUtilModule {
@ByAnimal
@Provides
public InfoUtil provideInfoUtilByAnimal(Animal animal){
return new InfoUtil(animal);
}
@ByUser
@Provides
public InfoUtil provideInfoUtilByUser(@ByName User user){
return new InfoUtil(user);
}
@ByCar
@Provides
public InfoUtil provideInfoUtilByCar(Car car){
return new InfoUtil(car);
}
}
可以看到,这里定义了三个不同的注解来区分用不同的参数构造InfoUtil这个类,注意provideInfoUtilByUser这个方法,它的参数需要显示制定用那种方式注入,这里用的是@ByName。那么这些方法中的Animal ,User,Car从哪里来呢? 这里框架会自动从建立的依赖关系图中去获取,所以我们在写Component的时候就要指定提供这些对象的Module类,如下
@Component(modules = {InfoUtilModule.class, AnimalModule.class, CarModule.class, UserModule.class})
public interface InfoUtilComponent {
public void inject(MainActivity mainActivity);
}
四、巨坑
同一个Activity只能在一个Component中注入,否则会报错,就是说如果在不同Component中的 inject方法中传入了同一个Activity的引用会报错。