现在android开发越来越多的应用到各种框架,一个好的app架构不但可以支持好的性能,同时也可以更方便快捷的开发功能。依赖注入是java后端开发spring的核心技术,而对于android来说现在由谷歌爸爸亲管的dagger则是不二之选。
使用Android Studio 创建一个新的项目,在Project的 build.gradle文件添加以下内容:
buildscript {
dependencies {
// 顺便也配置了支持lambda表达式
classpath 'me.tatarka:gradle-retrolambda:3.2.4'
// classpath 'com.android.tools.build:gradle:3.0.1' 正常gradle插件配置
}
}
并在Module下的build.gradle添加以下内容:
apply plugin: 'me.tatarka.retrolambda'
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
annotationProcessor 'com.google.dagger:dagger-compiler:2.4'
compile 'com.google.dagger:dagger:2.4'
provided 'org.glassfish:javax.annotation:10.0-b28'
}
注意:使用新插件时,必须使用 annotationProcessor 依赖项配置将注解处理器添加到处理器类路径中。
dagger的基本使用主要是通过各项注解来实现的,所以搞清楚常用注解的作用十分重要
1.通过@Inject、@Component实现依赖注入(最简单的方式):
提供一个依赖注入的对象,比如User
public class User {
private String name;
// 这个@Inject表示可以提供User类型的实例
@Inject
public User() {
this.name = "this is test";
}
public String getName() {
return name;
}
}
提供一个宿主,比如Activity
public class MainActivity extends AppCompatActivity {
@Inject
User user;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
注意:需要注意的是使用@Inject注解的变量不能是private变量,否则会报以下错误:Error:(13, 20) 错误: Dagger does not support injection into private fields
提供一个桥梁,即Component
@Component()
public interface TestMainComponent {
/**
* 必须让Component知道需要往哪个类中注入
* 这个方法名可以是其它的,但是推荐用inject
* 目标类MainActivity必须精确,不能用它的父类
*/
void inject(MainActivity activity);
}
此时Make Project就会在build文件夹内生成对应的代码。TestMainComponent接口会生成一个以Dagger为前缀的DaggerTestMainComponent类。采用这个DaggerTestMainComponent就能完成依赖对象的注入。可以在Activity的onCreate方法中调用如下代码初始化注入。这样的话MainActivity 中的成员变量user就完成了注入过程(也就是变量赋值过程)。
DaggerTestMainComponent.builder().build().inject(this);
到此最简单的dagger使用已经完成了,但是用@Inject提供实例有一定的缺陷:
此时,就要使用@Provides和@Module配合提供实例。
2.通过@Provides、@Module提供依赖
改造上面的User构造函数
public class User {
private String name;
// 这个@Inject表示可以提供User类型的实例
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
创建一个Module来管理@Provides注解的方法
@Module
public class MainModule {
@Provides
@Singleton
User provideUser() {
return new User("test");
}
}
改造上面的Component
@Component(modules = {MainModule.class})
public interface TestMainComponent {
void inject(MainActivity activity);
}
编译之后,我们就可以在目标类MainActivity 中进行初始化注入了。
DaggerTestMainComponent.builder().mainModule(new MainModule()).build().inject(this);
// 如果MainModule只有一个无参构造方法,可用如下初始化
DaggerTestMainComponent.create().inject(this);
注意:在提供依赖对象这一层面上,@Provides级别高于@Inject。(具体可以测试证明,这里不作说明)
3.初始化依赖实例的步骤
注意:
1.既没有@Module提供的实例,也没有@Inject标记的构造方法编译期就会报错。
2.若实例化时需要的参数没有提供,则实例化后值为null
上面的@Inject、@Component、@Module、@Provides是dagger实现依赖注入的主要工具,另外的@Qualifier和@Scope则起到了辅助作用
1.@Qualifier限定符的作用
Dagger2中已经有一个定义好的限定符@Named:
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
/** The name. */
String value() default "";
}
同时我们也可以自定义@ForTest这样的限定符
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ForTest {
}
还是以上面的为例,改造MainModule
@Module
public class MainModule {
@Provides
@Named("name")
User provideUser() {
return new User("user");
}
@Provides
@ForTest
User provideUser1() {
return new User("user1");
}
@Provides
String provideUserName(@ForTest User user) {
return user.getName();
}
}
此处provideUserName方法需要一个User参数,通过@Qualifier指定所需要的那个实例
在宿主中使用时
@Inject
@Named("name")
User user;
@Inject
@ForTest
User user1;
使用时也可以通过@Qualifier来选择所需要的那个实例
2.@Scope作用域的使用
个人认为@Scope的作用主要是在组织Component和Module的时候起到一个实例作用范围的提醒(类似是生命周期),Dagger2中有一个默认的作用域@Singleton:
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {
}
@Singleton并不是我们传统单例模式的那种作用,并不能直接帮我们创建单例对象,但它通常用于整个的应用全局对象,配合android中application的特殊性,就可以实现单例(此处不讨论多进程)
改造上面的MainModule
@Module
public class MainModule {
@Provides
@Singleton
User provideUser() {
return new User("test");
}
}
注意:需要说明的是:Module中的方法 使用@Singleton标注后,那对应的Component也必须采用@Singleton标注,表明它们的作用域一致,否则编译的时候会报作用域不同的错误。
所以需要改造上面的Component
@Component(modules = {MainModule.class})
@Singleton
public interface TestMainComponent {
void inject(MainActivity activity);
}
然后在自定义的Application中实例化Component
public static TestMainComponent component;
public static AppComponent getAppComponent(){
if (component == null) {
component = DaggerAppComponent.builder()
.mainModule(new MainModule())
.build();
}
return component;
}
这样就实现了一个常规意思上的单例,但是可以发现这个依赖实例其实只是每次都由同一个Component注入器对象提供,重新生成一个Component对象的话注入的依赖实例就不再是同一个。所以想要用Component只提供同一个实例对象,就必须保证Component只初始化一次。
我们可以自定义作用域,比如@PerActivity、@PerFragment设置@PerUser
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface PerActivity {
}
使用方法和@Singleton一样,但具体的差别需要在实际项目中测试体会,后续有机会再具体解释(可用来控制对象的生命周期,快捷实现一些常规功能)
通过上面可以发现dagger的基本使用十分简单,但难点其实在于合理的组织component,一个应用,多个activity,多个fragment,多个数据对象等。
Component组织方式分为3种:
- 依赖方式:一个Component可以依赖一个或多个Component,采用的是@Component的dependencies属性。
- 包含方式:这种就需要@SubComponent注解,用@SubComponent标记接口或者抽象类,表示它可以被包含。一个Component可以包含一个或多个Component,而且被包含的Component还可以继续包含其他的Component(跟Activity包含Fragment方式类似)
- 继承方式:——用一个Component继承另外一个Component。
下面借用其他项目的组织方式Android-CleanArchitecture:
可以看到这么划分的思想是:
个人觉得,实际开发中用@Singleton标记来表示在App生命周期内全局的对象,然后用自定义的@PerActivity、@PerFragment等来表示跟Activity、Fragment生命周期一致比较好。
现在我们采用依赖、包含、继承的方式来演示Component的组织方式
Module类如下:
@Module
public class AppModule {
private final Application application;
public AppModule(Application application) {
this.application = application;
}
@Provides
@Singleton
Context getAppContext() {
return application;
}
}
AppComponent如下:
@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
/**
* Exposed to sub-graphs.
* 其他的依赖想要用这个Context,必须显式的暴露。
* 因为,其它依赖这个的Component需要Context,然后这个Context会去AppModule中找对应的Context
* 与方法名无关,只与返回类型有关
* 举个例子:小弟B依赖大哥A,A有一把杀猪刀。哪天小弟碰上事了,找大哥借一把刀,
* 如果大哥把刀藏起来不给小弟用,小弟会因为找不到刀用很崩溃的。(程序编译报错),
* 所以必须是大哥把刀拿出来给小弟用,小弟才能拿出去用啊。(代码正常)
*
*/
Context context();
}
其它依赖这个AppComponent的Component想使用全局的Appliation Context,我们就必须显式地在AppComponent中暴露出去。(直接使用的话不需要)这个AppComponent接口内没有inject方法,因为具体地注入哪个类,是由依赖它的Component决定的。
自定义Appliation:
public class App extends Application {
private static AppComponent sAppComponent = null;
@Override
public void onCreate() {
super.onCreate();
if (sAppComponent == null) {
sAppComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.build();
}
}
public AppComponent getAppComponent() {
//向外界的依赖提供这个AppComponent
return sAppComponent;
}
}
再次强调:这个AppComponent只能初始化一次
有一个ActivityComponent,需要依赖这个AppComponent:
@PerActivity
//@Singleton 不能与依赖的AppComponent的作用域相同,否则会报错
@Component(dependencies = AppComponent.class, modules = ActModule.class)
public interface ActivityComponent {
void inject(DependenceTestActivity DependenceTestActivity);
void inject(SubComponentTestActivity subComponentTestActivity);
// 包含SubComponent,这样的话该SubComponent也可以拿到ActivityComponent中能提供的依赖。
ActSubComponent getActSubComponent();
}
Component依赖另一个Component,它们的作用域不能相同。所以我们自定义了一个@PerActivity作用域。这个ActivityComponent本身也可以需要Module提供依赖实例,如ActModule:
@Module
public class ActModule {
@Provides
@PerActivity
ActEntity getActEntity() {
return new ActEntity("我是ActEntity");
}
}
初始化注入是这个样子:
DaggerActivityComponent.builder()
.appComponent(((App) getApplication()).getAppComponent())
.build()
.inject(this);
上面的ActSubComponent 是被包含,它需要有个@Subcomponent注解,如果是包含的方式,作用域可以与包含它的Component一致。(2.8版本开始好像会报错)
@Subcomponent
@PerAct // 作用域与上一层的Component相同是否可行要看版本
public interface ActSubComponent {
void inject(SubFragment subFragment);
}
初始化方式:
// 上一层在activity中实例化
mActivityComponent = DaggerActivityComponent
.builder()
.appComponent(((App) getApplication()).getAppComponent())
.actModule(new ActModule())
.build();
// 然后在Fragment中拿到这个mActivityComponent :
((SubComponentTestActivity) getActivity()).getActivityComponent()
.getActSubComponent()
.inject(this);
ExtendTestComponent继承了ActivityComponent,那么ActivityComponent中需要的Module我们就必须提供。有的人可能会问ActivityComponent并没有AppModule啊,那是因为ActivityComponent依赖了AppComponent,由AppComponent提供了AppModule。
ExtendTestComponent有@Singleton标记,这是因为AppModule中有@Singleton作用域。如果ActModule中有一个@PerActivity作用域的话,这个Component必须要再加上@PerActivity。(注意是否会报错)
/**
* ExtendTestComponent继承了ActivityComponent,
* 如果ActivityComponent中的modules定义了创建实例的方法,
* ExtendTestComponent中也必须提供相应的modules。
*/
@Singleton
@PerActivity
@Component(modules = {ActModule.class, AppModule.class})
public interface ExtendTestComponent extends ActivityComponent {
void inject(ExtendTestActivity extendTestActivity);
}
初始化注入:
DaggerExtendTestComponent.builder()
.appModule(new AppModule(getApplication()))
.actModule(new ActModule())
.build()
.inject(this);
通过以上至少可以了解Dagger2中常用方式,这边提一下注意点:
Dagger的使用远远不仅于此,还有很多可以优化加深的地方,以后有机会再记录,接下来会分享下之前项目中实际应用的方式。(本文内容都是个人理解与网络借鉴,有错误和遗漏之处,欢迎指正,共同学习。)