使用dagger2来做依赖注入(通俗易懂)
http://chriszou.com/2016/05/10/android-unit-testing-di-dagger.html 问题在前一篇文章中,我们讲述了依赖注入的概念,以及依赖注入对单元测试极其关键的重要性和必要性。在那篇文章的结尾,我们遇到了一个问题,那就是如果不使用DI框架,而全部采用手工来做DI的话,那么所有的Dependency都需要在最上层的client来生成,这可不是件好事情。继续用我们前面的例子来具体说明一下。 public class UserManager {
private final SharedPreferences mPref;
private final UserApiService mRestAdapter;
public UserManager(SharedPreferences preferences, UserApiService userApiService) {
this.mPref = preferences;
this.mRestAdapter = userApiService;
}
/**Other code*/
}
LoginPresenter的设计如下: public class LoginPresenter {
private final UserManager mUserManager;
private final PasswordValidator mPasswordValidator;
public LoginPresenter(UserManager userManager, PasswordValidator passwordValidator) {
this.mUserManager = userManager;
this.mPasswordValidator = passwordValidator;
}
/**Other code*/
}
在这种情况下,最终的client LoginActivity里面要new一个presenter,需要做的事情如下: public class LoginActivity extends AppCompatActivity {
private LoginPresenter mLoginPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
OkHttpClient okhttpClient = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.build();
Retrofit retrofit = new Retrofit.Builder()
.client(okhttpClient)
.baseUrl("https://api.github.com")
.build();
UserApiService userApiService = retrofit.create(UserApiService.class);
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
UserManager userManager = new UserManager(preferences, userApiService);
PasswordValidator passwordValidator = new PasswordValidator();
mLoginPresenter = new LoginPresenter(userManager, passwordValidator);
}
}
这个也太夸张了, 想想,如果能达到这样的效果,那该有多好:我们只需要在一个类似于dependency工厂的地方统一生产这些dependency,以及这些dependency的dependency。所有需要用到这些Dependency的client都从这个工厂里面去获取。而且更妙的是,一个client(比如说 有这样一个东西,帮我们实现这个效果吗?相信聪明的你已经猜到了,回答是肯定的,它就是我们今天要介绍的dagger2。 解药:Dagger2在dagger2里面,负责生产这些Dependency的统一工厂叫做 Module ,所有的client最终是要从module里面获取Dependency的,然而他们不是直接向module要的,而是有一个专门的“工厂管理员”,负责接收client的要求,然后到Module里面去找到相应的Dependency,提供给client们。这个“工厂管理员”叫做 Component。基本上,这是dagger2里面最重要的两个概念。 下面,我们来看看这两个概念,对应到代码里面,是怎么样的。 生产Dependency的工厂:Module首先是Module,一个Module对应到代码里面就是一个类,只不过这个类需要用dagger2里面的一个annotation @Module
public class AppModule {
public OkHttpClient provideOkHttpClient() {
OkHttpClient okhttpClient = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.build();
return okhttpClient;
}
public Retrofit provideRetrofit(OkHttpClient okhttpClient) {
Retrofit retrofit = new Retrofit.Builder()
.client(okhttpClient)
.baseUrl("https://api.github.com")
.build();
return retrofit;
}
}
在上面的Module( @Module
public class AppModule {
@Provides
public OkHttpClient provideOkHttpClient() {
OkHttpClient okhttpClient = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.build();
return okhttpClient;
}
@Provides
public Retrofit provideRetrofit(OkHttpClient okhttpClient) {
Retrofit retrofit = new Retrofit.Builder()
.client(okhttpClient)
.baseUrl("https://api.github.com")
.build();
return retrofit;
}
}
这种用来生产Dependency的、用 @Module
public class AppModule {
@Provides
public OkHttpClient provideOkHttpClient() {
OkHttpClient okhttpClient = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.build();
return okhttpClient;
}
@Provides
public Retrofit provideRetrofit(OkHttpClient okhttpClient) {
Retrofit retrofit = new Retrofit.Builder()
.client(okhttpClient)
.baseUrl("https://api.github.com")
.build();
return retrofit;
}
@Provides
public UserApiService provideUserApiService(Retrofit retrofit) {
return retrofit.create(UserApiService.class);
}
@Provides
public SharedPreferences provideSharedPreferences(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context);
}
@Provides
public UserManager provideUserManager(SharedPreferences preferences, UserApiService service) {
return new UserManager(preferences, service);
}
@Provides
public PasswordValidator providePasswordValidator() {
return new PasswordValidator();
}
@Provides
public LoginPresenter provideLoginPresenter(UserManager userManager, PasswordValidator validator) {
return new LoginPresenter(userManager, validator);
}
}
上面的代码如果你仔细看的话,会发现一个问题,那就是其中的SharedPreference provider方法 public class MyApplication extends Application {
private static Context sContext;
@Override
public void onCreate() {
super.onCreate();
sContext = this;
}
public static Context getContext() {
return sContext;
}
}
provider方法如下: @Provides
public Context provideContext() {
return MyApplication.getContext();
}
但是这种方法不是很好,为什么呢,因为context的获得相当于是写死了,只能从MyApplication.getContext(),如果测试环境下想把Context换成别的,还要给MyApplication定义一个setter,然后调用MyApplication.setContext(...),这个就绕的有点远。更好的做法是,把Context作为 @Module
public class AppModule {
private final Context mContext;
public AppModule(Context context) {
this.mContext = context;
}
@Provides
public Context provideContext() {
return mContext;
}
//其他的provider方法
}
是的,一个Module就是一个正常的类,它也可以有构造方法,以及其他正常类的特性。你可能会想那给构造函数的context对象从哪来呢?别急,这个问题马上解答。 Dependency工厂管理员:Component前面我们讲了dagger2的一半,就是生产Dependency的工厂:Module。接下来我们讲另一半,工厂管理员:Component。跟Module不同的是,我们在实现Component时,不是定义一个类,而是定义一个接口(interface): public interface AppComponent {
}
名字可以随便取,跟Module需要用 @Component
public interface AppComponent {
}
在实际情况中,可能有多个Module,也可能有多个Component,那么当Component接收到一个Client的Dependency请求时,它怎么知道要从哪个Module里面去找这些Dependency呢?它不可能遍历我们的每一个类,然后找出所有的Module,再遍历所有Module的Provider方法,去找Dependency,这样先不说能不能做到,就算做得到,效率也太低了。因此dagger2规定,我们在定义Component的时候,必须指定这个管理员“管理”哪些工厂(Module)。指定的方法是,把需要这个Component管理的Module传给 @Component(modules = {AppModule.class}) //<=
public interface AppComponent {
}
modules属性接收一个数组,里面是这个Component管理的所有Module。在上面的例子中, Component给Client提供Dependency的方法前面我们讲了Module和Component的实现,接下来就是Component怎么给Client提供Dependency的问题了。一般来说,有两种,当然总共不止这两种,只不过这两种最常用,也最好理解,一般来说用这两种就够了,因此这里不赘述其他的方法。 方法一:在Component里面定义一个返回Dependency的方法第一种是在Component里面定义一个返回Dependency的方法,比如LoginActivity需要LoginPresenter,那么我们可以在 @Component(modules = {AppModule.class})
public interface AppComponent {
LoginPresenter loginPresenter();
}
你可能会好奇,为什么Component只需要定义成接口就行了,不是应该定义一个类,然后自己使用Module去做这件事吗?如果是这样的话,那就太low了。dagger2的工作原理是,在你的java代码编译成字节码的过程中,dagger2会对所有的Component(就是用 因此,使用这种方式,当Client需要Dependency的时候,首先需要用 public class LoginActivity extends AppCompatActivity {
private LoginPresenter mLoginPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AppComponent appComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build(); //<=
mLoginPresenter = appComponent.loginPresenter(); //<=
}
}
总结一下,我们到现在为止,做了什么:
就这样,我们便可以使用
说白了,就是把文章开头我们写的那段代码又实现了一遍,而使用dagger2,我们就做了前面描述的两件事而已,这里面错综复杂的Dependency关系dagger2帮我们自动理清了,生成相应的代码,去调用相应的Provider方法,满足这些依赖关系。 说点题外话,这种把问题(我们这里是依赖关系)描述出来,而不是把实现过程写出来的编程风格叫Declarative programming,跟它对应的叫Imperative Programming,相对于后者,前者的优势是:可读性更高,side effect更少,可扩展性更高等等。这是一种编程风格,跟语言、框架无关。当然,有的语言或框架天生就能让程序员更容易的使用这种style来编程。这方面最显著的当属Prolog,有兴趣的可以去了解下,绝对mind-blowing! 方法二:Field Injection话说回来,我们继续介绍dagger2,前面我们介绍了Component给Client提供Dependency的第一种方式,接下来继续介绍第二种方式,这种方式叫 Field injection 。这里我们继续用 public class LoginActivity extends AppCompatActivity {
@Inject //<=
LoginPresenter mLoginPresenter; //<=
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
然后在onCreate()里面,我们把 public class LoginActivity extends AppCompatActivity {
@Inject
LoginPresenter mLoginPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AppComponent appComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build(); //<=
appComponent.inject(this); //<=
//从此之后,mLoginPresenter就被实例化了
//mLoginPresenter.isLogin()
}
}
当然,我们需要先在 @Component(modules = {AppModule.class})
public interface AppComponent {
void inject(LoginActivity loginActivity); //<=
}
public class LoginActivity extends AppCompatActivity {
@Inject
LoginPresenter mLoginPresenter;
@Inject
StatManager mStatManager; //<=
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AppComponent appComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();
appComponent.inject(this);
}
}
无论有多少个@Inject field,都只需要调用一次 @Singleton和Constructor Injection到这里,Client从Component获取Dependency的两种方式就介绍完毕。但是这里有个问题,那就是每次Client向Component索要一个Dependency,Component都会创建一个新的出来,这可能会导致资源的浪费,或者说很多时候不是我们想要的,比如说, @Module
public class AppModule {
@Provides
@Singleton //<=
public OkHttpClient provideOkHttpClient() {
OkHttpClient okhttpClient = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.build();
return okhttpClient;
}
//other method
}
这样,当Client第一次请求一个 这里再给大家一个bonus,如果你不需要做单元测试,而只是使用dagger2来做DI,组织app的结构的话,其实 public class LoginPresenter {
private final UserManager mUserManager;
private final PasswordValidator mPasswordValidator;
@Inject
public LoginPresenter(UserManager userManager, PasswordValidator passwordValidator) {
this.mUserManager = userManager;
this.mPasswordValidator = passwordValidator;
}
//other methods
}
dagger2会自动创建这个 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |