依赖注入简单的说就是将实例对象交由第三方管理,目的是降低耦合度。dagger2的依赖注入是通过自动生成代码的方式来进行注入的。
添加dagger依赖:
annotationProcessor "com.google.dagger:dagger-compiler:$rootProject.daggerVersion"
compile "com.google.dagger:dagger :$rootProject.daggerVersion"
compile "com.google.dagger:dagger-android:$rootProject.daggerVersion"
annotationProcessor "com.google.dagger:dagger-android-processor:$rootProject.daggerVersion"
其中daggerVersion为2.13
github代码:https://github.com/dtjc/DaggerExample
(注:每一个commit相当于本教程的一个步骤)
创建一个LoginContract接口和一个LoginActivity,接口如下:
public interface LoginContract {
interface LoginView extends BaseView{
void finishLogin();
}
interface LoginPresenter extends BasePresenter{
void attachView(LoginView view);
void login(String user, String password);
}
}
BaseView和BasePresenter是一个基本接口。
LoginPresenterImp类,@Inject注解的意思是让dagger使用这个构造器创建实例。
public class LoginPresenterImpl implements LoginContract.LoginPresenter {
private LoginContract.LoginView loginView;
@Inject
public LoginPresenterImpl(){}
@Override
public void attachView(LoginContract.LoginView view) {
loginView = view;
}
@Override
public void login(String user, String password) {}
}
创建一个LoginComponent,component是用来完成注入过程的一个桥梁,调用其inject()函数后即可完成注入。
@Component
public interface LoginComponent {
void inject(LoginActivity loginActivity);
}
然后在LoginActivity中声明LoginPresenterImpl对象并使用@Inject注解对其进行注入,注意其访问属性不可以是私有的:
@Inject
LoginPresenterImpl presenter;
onCreate函数:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
DaggerLoginComponent.builder().build().inject(this);
presenter.attachView(this);
}
DaggerLoginComponent是dagger自动生成的类(build后会生成),dagger会为每个component生成一个以Dagger为前缀的类,真正的注入是发生在LoginActivity_MembersInjector的injectMembers(LoginActivity instance)中:
@Override
public void injectMembers(LoginActivity instance) {
if (instance == null) {
throw new NullPointerException("Cannot inject members into a null reference");
}
instance.presenter = presenterProvider.get();
}
看到instance.presenter=presenterProvide.get(),这也是为什么presenter不能是私有的原因。到这,一个简单的dagger依赖注入就完成了。
对于第三方库和接口,inject就显得无能为力了,这时就需要@Module和@Provides了。Provides需要在Module内使用。与对类构造器标注@Inject类似,@Provides用于提供实例对象。
@Module
public class AppModule {
@Provides
public ExecutorService provideExecutorService(){
return Executors.newCachedThreadPool();
}
}
为LoginComponent添加AppModule.class,其中module可以有多个,中间用’,’隔开:
@Component(modules = {AppModule.class})
public interface LoginComponent {
void inject(LoginActivity loginActivity);
}
在LoginPresenterImpl中标注@Inject
@Inject
ExecutorService es;
在login中使用
@Override
public void login(final String user,final String password) {
es.submit(new Runnable() {
@Override
public void run() {
Log.i("login","user: " + user + ", password: " + password);
}
});
}
需要在activity的onCreate中调用
presenter.login("abc","123456");
可以声明一个有参构造函数的module
public AppModule(Context context){
this.context = context;
}
为Context提供实例
@Provides
public Context context(){
return context;
}
注入
DaggerLoginComponent.builder()
.appModule(new AppModule(getApplicationContext()))
.build()
.inject(this);
这里会有一个问题,就是对于每一次inject注入,dagger都会生成一个实例,而对于ExecutorsService,应该是全局单例的,而不是每次生成一个实例。@Singleton与@Scope能解决上面的问题,其中@Singleton是@Scope的一个实现。在AppModule中为provideExecutorService加上@Singleton注解.
@Provides
@Singleton
public ExecutorService provideExecutorService(){
return Executors.newCachedThreadPool();
}
module中Scope注解必须与component一致,因此也要在LoginComponent接口声明@Singleton注解.同时创建一个AppComponent接口,如下所示:
@Component(modules = {AppModule.class})
@Singleton
public interface AppComponent {
void inject(MyApplication myApplication);
}
在MyApplication和LoginPresenterImpl中进行注入,并打印ExecutorService的地址。MyApplication继承自Application.
public class MyApplication extends Application {
private static final String TAG = "MyApplication";
@Inject
ExecutorService es;
@Override
public void onCreate() {
super.onCreate();
DaggerAppComponent.builder().build().inject(this);
Log.i(TAG,"ExecutorService: " + es.toString());
}
}
然而你会发现,这两个并不是同一个实例。说好的singleton呢???这是因为scope(注:singleton是scope默认实现) 作用域是与component绑定的,LoginComponent与AppComponent是两个完全独立的component,因此会生成两个实例。解决方案有两个,一是使用component的依赖关系,即component依赖component; 二是使用subcomponent。
component依赖component,component的scope不能相同,因此先实现一个ActivityScoped(当然还可以实现一个FragmentScoped):
@Scope
@Documented
@Retention(RUNTIME)
public @interface ActivityScoped {}
在LoginPresenterImpl上加上@ActivityScoped
@ActivityScoped
public class LoginPresenterImpl implements LoginContract.LoginPresenter {
LoginComponent的module不应该是AppModule,所以创建一个LoginModule,并提供一个LoginPresenter接口实例,方法参数也是通过注入的形式来传递的(相当于方法注入,也就是说下面的@ActivityScoped可省略,这里加上只是为了易读性)。通过@Inject LoginContract.LoginPresenter presenter进行注入。注意与之前的@Inject LoginPresenterImpl present的微小区别,如果不在LoginModule中提供以下方法,则无法通过@Inject LoginContract.LoginPresenter presenter进行注入。通过使用@ActivityScoped,可以保证在LoginComponent里的LoginPresenter 都是同一个实例。
@Module
public class LoginModule {
@Provides
@ActivityScoped
public LoginContract.LoginPresenter providePresenter(LoginPresenterImpl presenter){
return presenter;
}
}
修改LoginComponent的注解为:
@Component(modules = {LoginModule.class},dependencies = {AppComponent.class})
@ActivityScoped
还需要在AppComponent中将ExecutorService暴露给LoginComponent,即在AppComponent中加入如下方法:
ExecutorService executorService();
在MyApplication中声明一个AppComponent;
public AppComponent appComponent;
MyApplication注入
appComponent = DaggerAppComponent.builder().build();
appComponent.inject(this);
LoginActivity注入
DaggerLoginComponent.builder()
.appComponent(((MyApplication)getApplicationContext()).appComponent)
.build()
.inject(this);
这样就完成了component之间的依赖注入。
在android中使用更多的是subcomponent,这是因为activity是app的组件,而fragment又是activity的组件。将LoginComponent改为subcomponent。需要注意的是子组件与父组件的scope也不能是相同的。
@Subcomponent(modules = {LoginModule.class})
@ActivityScoped
public interface LoginComponent {
void inject(LoginActivity loginActivity);
@Subcomponent.Builder
interface Builder{
LoginComponent build();
}
}
@Subcomponent.Builder用于指定接口,提供必要的方法来构造subcomponent。
将子组件添加到父组件中很简单,在父组件的module中添加subcomponent,并在父组件中声明子组件的builder构造器,如下:
@Module(subcomponents = {LoginComponent.class})
public class AppModule {
在AppComponent中声明builder
LoginComponent.Builder loginComponent();
在LoginActivity中注入:
((MyApplication)getApplicationContext()).appComponent
.loginComponent()
.build()
.inject(this);
Lazy注入就是在使用时再进行初始化。通过@Inject Lazy进行注入,调用get()方法获取实例。
@Inject
Lazy presenterLazy;
presenterLazy.get().attachView(this);
presenterLazy.get().login("abc","123456");
Provider的使用与Lazy一样,也是通过@Inject Provider进行注入,调用get()方法获取实例。对于没有提供scope作用域的@Providers方法和类,调用get()方法后,会创建一个新的对象并返回;对于提供了scope作用域的@Providers方法和类,则会返回同一个实例。
在AppModule中加入下面的代码,build的时候dagger就会报错,这是因为有两个Provides可以提供ExecutorService,注入时,dagger不知道应该调用哪个方法进行注入
@Provides
@Singleton
ExecutorService provideSingleThreadPool(){
return Executors.newSingleThreadExecutor();
}
使用@Named
@Provides
@Singleton
@Named("cacheThreadPool")
ExecutorService provideExecutorService(){ return Executors.newCachedThreadPool(); }
@Provides
@Singleton
@Named("singleThreadPool")
ExecutorService provideSingleThreadPool(){ return Executors.newSingleThreadExecutor(); }
@Named注入所需要的实例
@Inject
@Named("cacheThreadPool")
ExecutorService es;
使用@Qualifier,自定义两个qualifier注解:
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheThreadPool {}
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface SingleThreadPool {}
使用自定义的qualifier注解对方法注解:
@Provides
@Singleton
@CacheThreadPool
ExecutorService provideExecutorService(){ return Executors.newCachedThreadPool(); }
@Provides
@Singleton
@SingleThreadPool
ExecutorService provideSingleThreadPool(){ return Executors.newSingleThreadExecutor(); }
注入
@Inject
@SingleThreadPool
ExecutorService es;
@BindsInstance使得component可以在构建时绑定实例:
@Component.Builder
interface Builder {
@BindsInstance
AppComponent.Builder application(Context context);
AppComponent build();
}
构建时绑定
appComponent = DaggerAppComponent.builder().application(this).build();
绑定后可以像@Provides和@Inject一样提供实例
@Inject
Context context;
@BInds注解可以用来代替@Provides,被@Binds注解的方法返回该方法参数,被@Binds注解的方法必须是抽象的:
@Module
public abstract class LoginModule {
@Binds
abstract LoginContract.LoginPresenter providePresenter(LoginPresenterImpl presenter);
}
该注解在Module里使用,与@Binds一样,被@ContributesAndroidInjector注解的方法必须是抽象的,返回的是一个具体的android组件,方法不应该有参数。该注解会为android组件生成subcomponent,而不需要像之前那样自己实现@Subcomponent。如:
@Module
public abstract class ActivityBindingModule {
@ActivityScoped
@ContributesAndroidInjector(modules = LoginModule.class)
abstract LoginActivity loginActivity();
}
@ContributesAndroidInjector只有配合dagger的android组件,使用起来才更为简便。即使用DaggerApplication,DaggerAppCompatActivity,DaggerFragment,DaggerService以及由dagger实现的另外两大Android组件。当然可以根据Dagger*模仿其代码以及实现其接口。关于这个可以查看Google的todo示例,分支为todo-mvp-dagger.