网上对 Dagger2 进行介绍的文章也已经很多了,一开始看的时候却总是有种从入门到放弃的感觉,因为 Dagger2 中注解的配套使用是需要一定规则的,而文章介绍得并不算太详细,如果搭配不当,Dagger2 是不会为我们生成相应的文件的,这就导致应用在编译时总是遇到各种报错,然后就一脸蒙蔽,所以这就需要很多的实践操作了
这里我就将本人在学习 Dagger2 的过程中的实践记录下来,希望对你有所帮助
一、配置
dependencies { implementation 'com.google.dagger:dagger:2.16' annotationProcessor 'com.google.dagger:dagger-compiler:2.16'}
二、@Inject
假设当前有一个 Person 类,其声明如下所示
/** * 作者:叶应是叶 * 时间:2018/7/8 16:21 * 描述: */public class Person { private String name; public Person() { name = "person default name"; } public Person(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
在一般情况下,如果我们要使用到一个 Person 变量,就需要以如下方式来声明
Person person = new Person();
而这隐藏着一个问题,就是没当 Person 类的构造函数发生了变化时(参数变多或变少),所有使用到 Person 的代码就需要都修改一遍,这对于较大的项目来说是一件很耗时耗力的工作,Dagger2 就是用来解决这一问题的依赖注入框架
首先为 Person 的构造函数添加 @Inject 注解,指定 Dagger2 在为我们初始化 Person 变量时要调用的构造函数
public class Person { @Inject public Person() { name = "person default name"; } ··· }
此外,还需要一个接口来作为 Person 和需要进行依赖注入的类之间的桥梁
此处即为 PersonComponent 接口,该接口需要使用 @Component 进行注解,且包含一个方法用于将需要使用到依赖注入的类对象传递进来,此外为 MainActivity,注意此处需要是确切的对象,而不能是任何父类对象
此外,接口名和方法名没有硬性规定
/** * 作者:叶应是叶 * 时间:2018/7/8 16:35 * 描述: */@Componentpublic interface PersonComponent { void inject(MainActivity mainActivity); }
接下来就可以在 MainActivity 中进行依赖注入了
/** * 作者:叶应是叶 * 时间:2018/7/8 16:35 * 描述: */public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; @Inject Person person1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaggerPersonComponent.builder().build().inject(this); Log.e(TAG, "person1: " + person1); Log.e(TAG, "person1 name : " + person1.getName()); } }
在运行前需要先 build 工程,这样 DaggerPersonComponent 类才会生成,运行结果如下所示
person1: com.leavesc.dagger2samples.test1.Person@420a5ee0 person1 name : person default name
使用 @Inject 进行注解的 person1 变量我们并没有对其进行初始化,但是应用在运行时并没有报空指针异常,说明 Dagger2 在后台为我们进行初始化操作了
实际进行初始化操作的是以下代码
DaggerPersonComponent.builder().build().inject(this);
DaggerPersonComponent 是 Dagger2 依照 PersonComponent 的命名而生成的文件,可以点进去看下其源码
DaggerPersonComponent 实现了 PersonComponent 接口,在为 person1 赋值时是直接调用了 Person 类的无参构造函数。因为 MainActivity_MembersInjector 是依靠 MainActivity 对象引用到 person1 变量,因此在 person1 不能声明为私有的,否则引用不到 person1 也就无法实现依赖注入了
public final class DaggerPersonComponent implements PersonComponent { private DaggerPersonComponent(Builder builder) {} public static Builder builder() { return new Builder(); } public static PersonComponent create() { return new Builder().build(); } @Override public void inject(MainActivity mainActivity) { injectMainActivity(mainActivity); } private MainActivity injectMainActivity(MainActivity instance) { MainActivity_MembersInjector.injectPerson1(instance, new Person()); return instance; } public static final class Builder { private Builder() {} public PersonComponent build() { return new DaggerPersonComponent(this); } } }
public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> { ··· public static void injectPerson1(MainActivity instance, Person person1) { instance.person1 = person1; } }
三、@Module、@Provides
@Inject 注解在用于工程中自己建立的类时是可行的,但面对工程中依赖到的各种开源库却无能为力了,因为我们无法修改它们的构造函数,此时就需要用到 @Module 与 @Provides 注解
假设当前有个 User 类来自于项目中依赖到的开源库中,此时该类的构造函数并没有添加 @Inject 注解
/** * 作者:叶应是叶 * 时间:2018/7/8 17:11 * 描述: */public class User { private String name; public User() { name = "user default name"; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
新建 UserModule 类用于对外部提供 User 类的实例,@Provides 注解用于告诉 Dagger2 ,如果需要 User 类的实例就调用此方法来获取
/** * 作者:叶应是叶 * 时间:2018/7/8 17:12 * 描述: */@Modulepublic class UserModule { @Provides public User provideUser() { return new User(); } }
此时一样需要一个 Component 类来作为依赖注入的入口,并为 @Component 注解提供注解值 UserModule.class
/** * 作者:叶应是叶 * 时间:2018/7/8 17:14 * 描述: */@Component(modules = {UserModule.class})public interface UserComponent { void inject(Main2Activity mainActivity); }
/** * 作者:叶应是叶 * 时间:2018/7/8 16:35 * 描述: */public class Main2Activity extends AppCompatActivity { private static final String TAG = "Main2Activity"; @Inject User user1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaggerUserComponent.builder().build().inject(this); Log.e(TAG, "user1: " + user1); Log.e(TAG, "user1 name : " + user1.getName()); } }
运行结果如下所示
user1: com.leavesc.dagger2samples.test2.User@420cb218 user1 name : user default name
四、带有参数的依赖对象
修改 User 类,为之添加一个带有参数的构造函数
/** * 作者:叶应是叶 * 时间:2018/7/8 17:11 * 描述: */public class User { private String name; public User() { name = "user default name"; } public User(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
假设我们在 UserModule 中要调用的是 User 的有参构造函数,那此时就需要通过 UserModule 的构造函数从外部向它传入字符串参数了
此处也不直接将成员变量 name 传给 provideUser()
方法,而是新建一个 provideName()
方法用于实现依赖注入,这也是为了尽量解耦
/** * 作者:叶应是叶 * 时间:2018/7/8 17:12 * 描述: */@Modulepublic class UserModule { private String name; public UserModule(String name) { this.name = name; } @Provides public String provideName() { return name; } @Provides public User provideUser(String name) { return new User(name); } }
由于之前 UserModule 只有无参构造函数,所以在使用 DaggerUserComponent 进行注入时无需显式传入 UserModule 对象,此时 UserModule 的构造函数需要传入参数了,所以现在只能显示调用 userModule() 方法传入 UserModule 对象
/** * 作者:叶应是叶 * 时间:2018/7/8 16:35 * 描述: */public class Main2Activity extends AppCompatActivity { private static final String TAG = "Main2Activity"; @Inject User user1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaggerUserComponent.builder().userModule(new UserModule("leavesC")).build().inject(this); Log.e(TAG, "user1: " + user1); Log.e(TAG, "user1 name : " + user1.getName()); } }
运行结果如下所示
user1: com.leavesc.dagger2samples.test2.User@420c4a30 user1 name : leavesC
五、@Singleton
假设在 Main2Activity 中有两个 User 对象需要进行实例化,按照以上的使用方式,在依赖注入时是会为每个不同的变量重新 new 一个实例的
/** * 作者:叶应是叶 * 时间:2018/7/8 16:35 * 描述: */public class Main2Activity extends AppCompatActivity { private static final String TAG = "Main2Activity"; @Inject User user1; @Inject User user2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaggerUserComponent.builder().userModule(new UserModule("leavesC")).build().inject(this); Log.e(TAG, "user1: " + user1); Log.e(TAG, "user1 name : " + user1.getName()); Log.e(TAG, "user2: " + user2); Log.e(TAG, "user2 name : " + user2.getName()); } }
运行结果如下所示,可以看到 user1 和 user2 的内存地址并不相同
user1: com.leavesc.dagger2samples.test2.User@420cb780 user1 name : leavesC user2: com.leavesc.dagger2samples.test2.User@420cba90 user2 name : leavesC
而为了实现单例模式,此处需要使用到 @Singleton 注解
/** * 作者:叶应是叶 * 时间:2018/7/8 17:12 * 描述: */@Modulepublic class UserModule { private String name; public UserModule(String name) { this.name = name; } @Provides public String provideName() { return name; } @Provides public User provideUser(String name) { return new User(name); } }
/** * 作者:叶应是叶 * 时间:2018/7/8 17:14 * 描述: */@Component(modules = {UserModule.class})public interface UserComponent { void inject(Main2Activity mainActivity); }
此处重新运行应用,就可以看到 user1 和 user2 的内存地址是相同的了
user1: com.leavesc.dagger2samples.test2.User@420c86d8 user1 name : leavesC user2: com.leavesc.dagger2samples.test2.User@420c86d8 user2 name : leavesC
六、@Named
由于 User 类有两个构造函数,有时候我们也需要指定要由哪个构造函数来初始化 User,此时就需要用到 @Named 注解
修改 UserModule 类,增加 provideUser2()
方法,并为 provideUser2()
和 provideUser2()
方法声明 @Named 注解,注解值用于配对需要实现依赖注入的成员变量,只要成员变量声明的 @Named 注解的属性值与这两个方法的某个注解值相等,就会依赖该方法来初始化成员变量
/** * 作者:叶应是叶 * 时间:2018/7/8 17:12 * 描述: */@Modulepublic class UserModule { private String name; public UserModule(String name) { this.name = name; } @Provides public String provideName() { return name; } @Provides @Singleton @Named("no empty") public User provideUser(String name) { return new User(name); } @Provides @Singleton @Named("empty") public User provideUser2() { return new User(); } }
/** * 作者:叶应是叶 * 时间:2018/7/8 16:35 * 描述: */public class Main2Activity extends AppCompatActivity { private static final String TAG = "Main2Activity"; @Inject @Named("no empty") User user1; @Inject @Named("empty") User user2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaggerUserComponent.builder().userModule(new UserModule("leavesC")).build().inject(this); Log.e(TAG, "user1: " + user1); Log.e(TAG, "user1 name : " + user1.getName()); Log.e(TAG, "user2: " + user2); Log.e(TAG, "user2 name : " + user2.getName()); startActivity(new Intent(this, Main3Activity.class)); } }
运行结果如下所示
user1: com.leavesc.dagger2samples.test2.User@420cd128 user1 name : leavesC user2: com.leavesc.dagger2samples.test2.User@420cd438 user2 name : user default name
七、@Qualifier
先看下注解 @Named 的声明,该注解就使用到了 @Qualifier
@Qualifier@Documented@Retention(RUNTIME)public @interface Named { /** The name. */ String value() default ""; }
由于注解 @Named 是通过比较字符串的相等性来实现配对的,出错的可能性并不算低,而且也不够优雅,此时就可以通过 @Qualifier 来自己实现同样的功能
声明两个注解,用来表示在初始化 User 变量时是调用哪个构造函数
/** * 作者:叶应是叶 * 时间:2018/7/8 20:34 * 描述: */@Qualifier@Retention(RetentionPolicy.RUNTIME)public @interface UserWithoutParameter { }
/** * 作者:叶应是叶 * 时间:2018/7/8 20:34 * 描述: */@Qualifier@Retention(RetentionPolicy.RUNTIME)public @interface UserWithParameter { }
然后直接替换 @Named 即可实现与之相同的功能
@Provides @Singleton //@Named("no empty") @UserWithParameter public User provideUser(String name) { return new User(name); } @Provides @Singleton //@Named("empty") @UserWithoutParameter public User provideUser2() { return new User(); }
@Inject //@Named("no empty") @UserWithParameter User user1; @Inject //@Named("empty") @UserWithoutParameter User user2;
八、延迟加载
Dagger2 也支持延迟加载,在需要的时候才对成员变量进行初始化,需要依赖于泛型接口 Lazy
/** * 作者:叶应是叶 * 时间:2018/7/8 16:35 * 描述: */public class Main2Activity extends AppCompatActivity { private static final String TAG = "Main2Activity"; @Inject Lazy<User> user3; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaggerUserComponent.builder().userModule(new UserModule("leavesC")).build().inject(this); User user = user3.get(); Log.e(TAG, "user3-1: " + user); Log.e(TAG, "user3 name-1 : " + user.getName()); } }
九、强制加载
Dagger2 支持在每次获取成员变量值时都返回一个重新初始化的对象,除非你使用了 @Singleton 注解要求只实例化一次
/** * 作者:叶应是叶 * 时间:2018/7/8 16:35 * 描述: */public class Main2Activity extends AppCompatActivity { private static final String TAG = "Main2Activity"; @Inject Provider<User> user4; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaggerUserComponent.builder().userModule(new UserModule("leavesC")).build().inject(this); User user = user4.get(); Log.e(TAG, "user4-1: " + user); Log.e(TAG, "user4 name-1 : " + user.getName()); user = user4.get(); Log.e(TAG, "user4-2: " + user); Log.e(TAG, "user4 name-2 : " + user.getName()); user = user4.get(); Log.e(TAG, "user4-2: " + user); Log.e(TAG, "user4 name-2 : " + user.getName()); } }
运行结果如下所示,可以看到 user 变量的内存地址每次各不相同
user4-1: com.leavesc.dagger2samples.test2.User@420cad48 user4 name-1 : Hello user4-2: com.leavesc.dagger2samples.test2.User@420cb148 user4 name-2 : Hello user4-2: com.leavesc.dagger2samples.test2.User@420cb548 user4 name-2 : Hello
十、组件间的依赖
假设现在有个需求,在多个地方中都需要获取系统服务 LocationManager,而获取 LocationManager 是需要通过 Context 来获取的,为了避免需要重复传递 Context 对象,此时就可以选择通过组件间的依赖将 Context 的获取方法移交给另外的 Component
LocationManager locationManager = (LocationManager)this.getSystemService(Context.LOCATION_SERVICE);
首先,通过 ApplicationModule 和 ApplicationComponent 来统一对外提供 Context
/** * 作者:叶应是叶 * 时间:2018/7/8 19:25 * 描述: */@Modulepublic class ApplicationModule { private Context context; public ApplicationModule(Context context) { this.context = context; } @Provides public Context provideContext() { return context; } }
/** * 作者:叶应是叶 * 时间:2018/7/8 19:26 * 描述: */@Component(modules = {ApplicationModule.class})public interface ApplicationComponent { Context getContext(); }
然后在 Application 类中实现依赖注入,使得对外提供的 Context 对象统一都是 ApplicationContext
/** * 作者:叶应是叶 * 时间:2018/7/8 19:45 * 描述: */public class RealApplication extends Application { public static ApplicationComponent applicationComponent; @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.builder().applicationModule(new ApplicationModule(this)).build(); } }
通过注解值 dependencies 来指定 ActivityComponent 需要的 Context 要从哪个 Component 中获取
/** * 作者:叶应是叶 * 时间:2018/7/8 19:33 * 描述: */@Component(dependencies = {ApplicationComponent.class}, modules = {ActivityModule.class})public interface ActivityComponent { void inject(Main3Activity mainActivity); }
/** * 作者:叶应是叶 * 时间:2018/7/8 19:27 * 描述: */@Modulepublic class ActivityModule { @Provides LocationManager provideLocationManager(Context context) { return (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); } }
然后,在需要 LocationManager 的地方就可以通过 DaggerActivityComponent 来间接获取,而无需直接依赖于 Context 对象
/** * 作者:叶应是叶 * 时间:2018/7/8 16:35 * 描述: */public class Main3Activity extends AppCompatActivity { private static final String TAG = "Main3Activity"; @Inject LocationManager locationManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaggerActivityComponent.builder().applicationComponent(RealApplication.applicationComponent).build().inject(this); Log.e(TAG, "locationManager: " + locationManager); } }