注:本文是小生自学时做(fanyi)的笔记,可能含有少儿不宜的误导性内容,学习Dagger请移步原博。
原博地址:http://frogermcs.github.io/
Dagger2使用:
需求:主页上有一个TextView,在TextView中显示人名和年龄
使用Dagger2做依赖注入,需要创建两样东西:
1.Component接口: 想象成是注射器的针管,单词的含义貌似跟作用完全没关系啊,待我查查看。Component类使用 @Component(modules = {xxx.class}) 注解。
UserComponent:
@Component(modules = {UserModule.class})
public interface UserComponent {
void inject(MainActivity mainActivity);
}
2.Module: 想象是注射器的针筒。注意,Module不是提供的依赖(注射液),而是提供依赖的组件(针筒)。Module类使用 @Module 注解,提供依赖的方法用 @Provides 标识,方法名以 provide 开头。
@Module
public class UserModule {
UserModule() {}
@Provides
UserModel provideUsers() {
UserModel user = new UserModel();
user.setName("lala");
user.setAge(18);
return user;
}
}
以上就是我们为了使用Dagger需要额外添加的东西,针筒加针管,还缺少:1.注射器的活塞。2.需要注射的液体。3.接受注射的病人。
1.注射器的活塞
也就是用来真正实施注入的东西。Dagger会根据注解生成一个实现了MembersInjector接口的类。MembersInjector直译过来就是成员注射器,我们把它看成是注射器的活塞。此接口只有一个方法,就是 void injectMembers(T instance)
.调用生成类的 injectMembers 方法即相当于推动活塞的动作,真正实施注射。对于这个东西我们不需要添加额外的代码,它由Dagger在预编译时期自动生成。
2.需要注射的液体
这就是我们需要注入的依赖了。它可以是任何可以实例化的东西。
UserModel:
//UserModel没有添加任何额外的东西
public class UserModel {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
3.接受注射的病人
也就是接收依赖的需求者。我们用 @inject 注解来标识。
MainActivity:
//需要注入的依赖使用 @Inject 标记
public class MainActivity extends AppCompatActivity {
private TextView textView;
@Inject
UserModel user;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.text_view);
//进行真正的注入操作
DaggerUserComponent.builder()
.userModule(new UserModule())
.build()
.inject(this);
textView.setText("Name:" + user.getName() + "::Age:" + user.getAge());
}
}
效果:
咦,这么看起来整个实现还是比较麻烦的。直接在MainActivity new一个user出来不就完了,为什么要用Dagger绕这么大一圈。
先看看什么是依赖。
在MainActivity需要一个UserModel的实例,此时,我们称MainActivity对UserModel有依赖,如果我们要改UserModel,比如在构造函数里就传进去姓名和年龄,而不通过set方法。此时,我们就要更改MainActivity中的代码,这里的MainActivity和UserModel是高耦合的。
而使用Dagger后,即使UserModel有了改变,我们只要改一下Module里的provide方法即可,而不用动MainActivity的代码,此时,MainActivity和UserModel是低耦合的。真真是在平坦的路面上曲折前行啊...
上面是使用Dagger做一个最基本的依赖注入。下面来详解一下各个部分。
- @Inject 注解
构造函数注入
public class LoginActivityPresenter {
private LoginActivity loginActivity;
private UserDataStore userDataStore;
private UserManager userManager;
@Inject
public LoginActivityPresenter(LoginActivity loginActivity,
UserDataStore userDataStore,
UserManager userManager) {
this.loginActivity = loginActivity;
this.userDataStore = userDataStore;
this.userManager = userManager;
}
}
直接在构造函数上加 @Inject 注解,所有的参数都从Module里获取。同时,这个类也可以被用于注入。如下:
public class LoginActivity extends BaseActivity {
@Inject
LoginActivityPresenter presenter;
//...
}
限制是一个类中只能有一个构造函数有 @Inject 注解。
成员注入
public class SplashActivity extends AppCompatActivity {
@Inject
LoginActivityPresenter presenter;
@Inject
AnalyticsManager analyticsManager;
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
getAppComponent().inject(this);
}
}
也就是上面我们使用的注入方式,这种方式比较好理解,不过需要手动调用注入才能完成注入过程。注入前该成员一直都是null。
限制是被注入成员不能是private的,因为Dagger生成的代码是这么注入的:
splashActivity.analyticsManager = analyticsManagerProvider.get();
这个后面分析生成代码的时候会谈到。
方法注入
public class LoginActivityPresenter {
private LoginActivity loginActivity;
@Inject
public LoginActivityPresenter(LoginActivity loginActivity) {
this.loginActivity = loginActivity;
}
@Inject
public void enableWatches(Watches watches) {
watches.register(this); //Watches instance required fully constructed LoginActivityPresenter
}
}
被注解的方法的所有参数都由Module提供。当被注入方需要当前类的实例(this)时可以用这种方式把自己传给被注入方。注入方法会在构造函数调用完毕后立马被调用。
@Module 注解
用来标注那些提供依赖的类,Dagger通过这个注解来找到提供依赖的类。
@Module
public class GithubApiModule {
@Provides
@Singleton
OkHttpClient provideOkHttpClient() {
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setConnectTimeout(60 * 1000, TimeUnit.MILLISECONDS);
okHttpClient.setReadTimeout(60 * 1000, TimeUnit.MILLISECONDS);
return okHttpClient;
}
@Provides
@Singleton
RestAdapter provideRestAdapter(Application application, OkHttpClient okHttpClient) {
RestAdapter.Builder builder = new RestAdapter.Builder();
builder.setClient(new OkClient(okHttpClient))
.setEndpoint(application.getString(R.string.endpoint));
return builder.build();
}
}
@Provides 注解
标注返回依赖的方法。
@Module
public class GithubApiModule {
//...
@Provides //This annotation means that method below provides dependency
@Singleton
RestAdapter provideRestAdapter(Application application, OkHttpClient okHttpClient) {
RestAdapter.Builder builder = new RestAdapter.Builder();
builder.setClient(new OkClient(okHttpClient))
.setEndpoint(application.getString(R.string.endpoint));
return builder.build();
}
}
@Component 注解
标注Module与Inject之间的桥梁接口。在这个接口中定义从哪个module获取依赖,也用于定义那些Module可以用于注入,以及可以注入对象到哪里。
这个例子表示:当前Component使用两个Modules,可以注入依赖到GithubClientApplication,可以使三个依赖公开可见:
@Singleton
@Component(
modules = {
AppModule.class,
GithubApiModule.class
}
)
public interface AppComponent {
void inject(GithubClientApplication githubClientApplication);
Application getApplication();
AnalyticsManager getAnalyticsManager();
UserManager getUserManager();
}
Component也可以依赖别的Component,也可以有自己的生命周期(详见下面的Scope)
@ActivityScope
@Component(
modules = SplashActivityModule.class,
dependencies = AppComponent.class
)
public interface SplashActivityComponent {
SplashActivity inject(SplashActivity splashActivity);
SplashActivityPresenter presenter();
}
@Scope 注解
@Scope
public @interface ActivityScope {
}
用于定义自定义作用域注解。有点类似于单例,不同的是,单例的作用域是整个application,而自定义作用域可以自定义(特么废话么)。下面还会详解Scope,这里按下不表。先说好,Scope是拿来干这个的:保持对象的单一实例。
然后附赠一些不咋用,不太重要的东西:
@MapKey 注解
用于定义依赖的集合。
定义:
@MapKey(unwrapValue = true)
@interface TestKey {
String value();
}
提供依赖:
@Provides(type = Type.MAP)
@TestKey("foo")
String provideFooKey() {
return "foo value";
}
@Provides(type = Type.MAP)
@TestKey("bar")
String provideBarKey() {
return "bar value";
}
使用:
@Inject
Map<String, String> map;
map.toString() // => „{foo=foo value, bar=bar value}”
@Qualifier 注解
给高阶程序猿(强迫症)准备的,用于为继承自同一个接口的依赖打 TAG 来区分他们。比如有两个Adapter继承自同一个Adapter接口,你还想让代码看上去整齐有型,就这么干。
命名依赖:
@Provides
@Singleton
@GithubRestAdapter //Qualifier
RestAdapter provideRestAdapter() {
return new RestAdapter.Builder()
.setEndpoint("https://api.github.com")
.build();
}
@Provides
@Singleton
@FacebookRestAdapter //Qualifier
RestAdapter provideRestAdapter() {
return new RestAdapter.Builder()
.setEndpoint("https://api.facebook.com")
.build();
}
注入依赖:
@Inject
@GithubRestAdapter
RestAdapter githubRestAdapter;
@Inject
@FacebookRestAdapter
RestAdapter facebookRestAdapter;
@Singleton 注解(Java自带)
Dagger不单可以注入实例,还可以注入单例。当年老大要咱们做单元测试,结果因为项目里各种单例用得太乱导致大量代码处于不可测的状态。一开始注意到Dagger也是因为有了Dagger,就不需要写那种很难Mock对象的单例了。
我们更改一下一开始的示例的界面,加一个TextView,MainActivity如下:
public class MainActivity extends AppCompatActivity {
private TextView textView1;
private TextView textView2;
@Inject
UserModel user1;
@Inject
UserModel user2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView1 = (TextView) findViewById(R.id.text_view_1);
textView2 = (TextView) findViewById(R.id.text_view_2);
DaggerUserComponent.builder()
.userModule(new UserModule())
.build()
.inject(this);
user2.setName("Stark");
user2.setAge(10);
textView1.setText("Name:" + user1.getName() + "::Age:" + user1.getAge());
textView2.setText("Name:" + user2.getName() + "::Age:" + user2.getAge());
}
}
可以看到这里我注入了两个UserModel的实例,在注入后重新设置user2的name和Age,然后显示在两个TextView上。结果如图:
可以看到,上面的user1还是我在Module中创建的UserModel实例,user2是另一个独立的对象,所以它们之间互不干扰。
好,下面我们改动一丢丢Component和Module的代码,如下:
@Component(modules = {UserModule.class})
@Singleton
public interface UserComponent {
void inject(MainActivity mainActivity);
}
@Module
public class UserModule {
UserModule() {}
@Provides
@Singleton
UserModel provideUsers() {
UserModel user = new UserModel();
user.setName("lala");
user.setAge(18);
return user;
}
}
为Component加了 @Singleton 的类注解,为Module创建相应实例的方法加了同样的 @Singleton 注解,再运行一下看看。
恩,就是这么简单,我只更改了user2的数据,结果user1也更改了,因为它们指向同一个对象。就这样,成功使用Dagger注入了单例。且这个单例的代码是可测的,因为它的实例很容易Mock。
Scope 详解
Scope,直译过来是范围、圈子的意思。就像前面提到的,在Dagger中,Scope用来保证在指定作用域中,拿到的依赖对象是唯一的(指定范围内是单例)。比如,我有一个登录系统,用户登录后,一直到登出前,拿到的UserModel就应该是单例。此时,我可以自定义一个UserScope的作用域,即实现一个 @UserScope 注解,用此注解标识的Component在指定的范围内(从用户登录到用户登出)注入的一定是同一个实例。有没有很爽的感觉。。。
Dagger是没有默认自带的各种Scope的,什么 @ActivityScope 啊,@ApplicationScope 啊统统没有,只有Java自带的一个@Singleton,也就是上面讲到的那个,它与Apllication处于同一作用域,从App开启到结束只有一个实例。那么下面我们来看看如何自定义一个 Scope 注解。
Scope的实现在Dagger2里头就是对Component做正确的配置工作。有两种方式实现自定义Scope,使用 @Subcomponent 注解 或 使用 Components 依赖。这两种实现方式最终出来的结果是不同的,使用 @Subcomponent 注解的话,子域可以获取到父域的所有依赖;而如果使用 Components 依赖,则子域只能获取到父域通过Component接口暴露出来的依赖。
先来看看使用 @Subcomponent 注解的方式实现:
@Singleton
@Component(
modules = {
AppModule.class,
GithubApiModule.class
}
)
public interface AppComponent {
UserComponent plus(UserModule userModule);
SplashActivityComponent plus(SplashActivityModule splashActivityModule);
}
可以看到,在AppComponent接口中有两个plus方法,这两个方法的意思是,我们可以从APPComponent中创建两个子Component: UserComponent 和 SplashActivityComponent. 因为它们是AppComponent的子Component,所以都可以获取到AppModule和GithubApiModule产生的实例。
规则:返回类型是子Component类型,方法名任性着来,参数是子Component所需的就行。
可以看到,UserComponent需要另一个Module,该Module被当做plus方法的参数传入。这样,我们我们用新Module产生的额外对象拓展了AppComponent可注入的依赖表。UserComponent如下:
@UserScope
@Subcomponent(
modules = {
UserModule.class
}
)
public interface UserComponent {
RepositoriesListActivityComponent plus(RepositoriesListActivityModule repositoriesListActivityModule);
RepositoryDetailsActivityComponent plus(RepositoryDetailsActivityModule repositoryDetailsActivityModule);
}
@UserScope 注解肯定是自定义的咯:
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface UserScope {
}
在UserComponent中,可以创建另外两个子Component: RepositoriesListActivityComponent 和 RepositoryDetailsActivityComponent.
重要的东西在这儿呢。所有从UserComponent拿到的从AppComponent继承下来的实例都是单例(范围是Application)。而那些UserModule自己产生的实例都是“本地单例”,其周期就是UserComponent存在的周期。
所以,每次我们调用UserComponent userComponent = appComponent.plus(new UserComponent(user));
时,从userComponent拿到的对象都是不同的实例。
对于这个我们自定义的UserScope,它的生命周期当然也得咱们自己维护,要维护它的初始化和销毁。
public class GithubClientApplication extends Application {
private AppComponent appComponent;
private UserComponent userComponent;
//...
public UserComponent createUserComponent(User user) {
userComponent = appComponent.plus(new UserModule(user));
return userComponent;
}
public void releaseUserComponent() {
userComponent = null;
}
//...
}
当我们从网络获取到User对象时,调用createUserComponent方法,UserScope从此时开始。当RepositoriesListActivity finish时,调用releaseUserComponent终结UserScope。
到此为止,对于Dagger的使用已经学习地差不多了。但是只知道怎么用怎么能满足咱们欲求不满的内心呢?所以下一篇要赏析一下Dagger自动生成的代码,来看一下整个注入流程是如何走通的。