以迅雷不及掩耳盗铃之势
dagger
前言
dagger翻译过来就是匕首的意思,是一个非常著名的依赖注入框架,由square出品,后来由伟大的google接手,推出第二代dagger2,然后这把匕首以完美适用mvp,高度解耦为大家熟知,不过也因为其学习成本过高,造成了很多人从入门到放弃,放弃到放弃,那这篇文章我们就来梳理一下dagger的使用方法,以便我们快速了解并熟悉这个框架.
既然要写,网上零零总总的dagger文章也都看了,有一篇说的特别好(一看就是大牛写的,后来翻不到了),他说你搞不明白是因为你对java注解不了解,对依赖注入手段不熟悉,对apt这种扫描解析注解的方式不熟悉,说白了就是基础不牢,所以你要是懂那真是见了鬼了...完全没毛病!物质基础决定上层建筑,基础真的很重要,正所谓万变不离其宗,有一个好基础,框架上手快,代码精简有效,而且对于阅读源码帮助也非常大,不过,注解原理不太明白也不是完全不能学习dagger的.
其实dagger这套东西就是用注解去解耦的(IOC),举个例子,如果用最通俗的语言来说,我之前写了一套代码,后来业务变了,我加个字段,正常我改一处就行了,但是我发现改一处不行,还要改100处,牵一发而动全身,本来5分钟解决的事情要弄1个小时,有人说程序员的懒惰推动了行业的进步(我说的),于是各种框架应运而生,这些框架的出现就是帮助我们以最短的时间,最小的代价达到目的,没错,一行代码也不愿多写...
引用
implementation 'com.google.dagger:dagger:2.21'
annotationProcessor 'com.google.dagger:dagger-compiler:2.21'
我们简单说一下第二个引用,传统的注解是运行时候利用反射去解析的,比较耗资源,而dagger跟butterknife及3.0之后的eventbus都是编译时注解,什么意思?就是我们写代码编译的时候,去扫描整个项目的dagger注解,然后生成java文件,帮助我们去生成实例,然后做这些事情的工具叫apt(Annotation Processing Tool)注解解析器,比如这个annotationProcessor.同理butterknife也是有的
//butterknife
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
基本用法
dagger2最基本的使用方法只需要4个注解:
- @Inject: 声明被注入的依赖
- @moudle:提供依赖
- @Provides:具体提供依赖的方法
- @Component:连接依赖及被注入方的纽带
接下来我们具体操作一下,需求:想在MainActivity中创建一块糖的实例,当然不能用new的
- 创建module及provides方法,并用相应的注解标注
@Module
public class MainModule {
@Provides
Sugar providesSugar() {
return new Sugar("红色", true);
}
}
实体类为:
public class Sugar {
...
public Sugar(String color, boolean isSweet) {
this.color = color;
this.isSweet = isSweet;
}
...
}
- 创建Component并声明注入方法,然后rebuild一下
@Component(modules = MainModule.class)
public interface MainComponent {
void inject(MainActivity mainActivity);
}
- 在MainActivity中标注@Inject.并且注入
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MAINACTIVITY";
@Inject
Sugar sugar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerMainComponent.create().inject(this);
if (sugar != null && sugar.isSweet()) {
Log.i(TAG, sugar.getColor() + "的糖有点甜");
}
}
}
//打印一下:
01-23 16:38:25.512 13799-13799/com.gaox.dagger2test I/MAINACTIVITY: 红色的糖有点甜
我们看到红色的糖作为依赖成功注入到了MainActivity,如果后续我还要给糖加形状,加类别,只需要修改module提供糖的方法就行了,哪怕就是把这块糖注入到1000个activity里,也只修改一处!然而上面代码onCreate方法中有一个陌生的DaggerMainComponent,这个是从哪来的?之前我们提到apt会扫描注解生成java文件,那在编译的时候这些文件又在哪里?
都在这个目录下:
├── app/bulid
│ ├── generated/source
│ ├── ├──apt/debug/包名
│ ├── ├──├──DaggerMainComponent
│ ├── ├──├──MainActivity_MembersInjector
│ ├── ├──├──MainModule_ProvidesSugarFactory
在debug下面自己的包名中发现了新出现了3个类,而DaggerMainComponent就在其中,接下来我我们分析一下,看看这3个类如何将依赖注入的?
DaggerMainComponent.create().inject(this);
从这行代码开始,展开DaggerMainComponent这个类
// Generated by Dagger (https://google.github.io/dagger).
package com.gaox.dagger2test;
import dagger.internal.Preconditions;
public final class DaggerMainComponent implements MainComponent {
private final MainModule mainModule;
private DaggerMainComponent(MainModule mainModuleParam) {
this.mainModule = mainModuleParam;
}
public static Builder builder() {
return new Builder();
}
public static MainComponent create() {
return new Builder().build();
}
@Override
public void inject(MainActivity mainActivity) {
injectMainActivity(mainActivity);
}
private MainActivity injectMainActivity(MainActivity instance) {
MainActivity_MembersInjector.injectSugar(
instance, MainModule_ProvidesSugarFactory.proxyProvidesSugar(mainModule));
return instance;
}
public static final class Builder {
private MainModule mainModule;
private Builder() {}
public Builder mainModule(MainModule mainModule) {
this.mainModule = Preconditions.checkNotNull(mainModule);
return this;
}
public MainComponent build() {
if (mainModule == null) {
this.mainModule = new MainModule();
}
return new DaggerMainComponent(mainModule);
}
}
}
- create方法中通过典型的建造者模式new Builder().build(),将mainModule 实例化了,并且做为参数在实例化了DaggerMainComponent,等于将mainModule放到了DaggerMainComponent容器中!
- inject方法,通过观察我们发现DaggerMainComponent是实现了MainComponent接口,为其实例,也就是调取了DaggerMainComponent的inject方法,调用方法顺序为:
inject(MainActivity mainActivity) ->
injectMainActivity(mainActivity) ->
MainActivity_MembersInjector.injectSugar(instance,
MainModule_ProvidesSugarFactory.proxyProvidesSugar(mainModule));
-
MainModule_ProvidesSugarFactory.proxyProvidesSugar(mainModule))提供了sugar
-
injectSugar
public static void injectSugar(MainActivity instance, Sugar sugar) {
instance.sugar = sugar;
}
将sugar赋值给activity中的sugar,这样依赖注入就完毕了,而且通过观察DaggerMainComponent,当然我们还可以写成这样:
DaggerMainComponent.builder()
.mainModule(new MainModule())
.build()
.inject(this);
//这样的话我们的module是可以带参数的!
以上就是最基本的使用方法,只要翻一翻生成的java文件就非常的清楚,报了错也可以很快定位,不会一脸懵逼,那除了这种方式,还有一种非常轻便的用法,使用起来更简单:
简单来说就是去掉module,inject直接声明在依赖的构造函数上,比如:
public class Sugar {
private String color;
private boolean isSweet;
@Inject
public Sugar(){
}
}
//component去掉module
@Component
public interface MainComponent {
void inject(MainActivity mainActivity);
}
//打印
if(sugar!=null){
Log.i(TAG, sugar.toString());
}
01-24 10:16:58.784 8093-8093/com.gaox.dagger2test I/MAINACTIVITY: Sugar{color='null', isSweet=false}
sugar已被实例化,只是没有赋值,说明也是成功的,我们之前的Sugar是在module中自己new出来的,由module提供给MainActivity,但是这种方式是什么地方实例化Sugar的呢?再来看 一下DaggerMainComponent中的injectMainActivity方法
private MainActivity injectMainActivity(MainActivity instance) {
MainActivity_MembersInjector.injectSugar(instance, new Sugar());
return instance;
}
这里有几点要注意一下:
- 用inject标注的构造函数,不一定是空参的,有其他的对象作为参数也是可以的,像这样:
@Inject
public Sugar(Butter butter) {
this.butter = butter;
}
//相对应的injectMainActivity:
private MainActivity injectMainActivity(MainActivity instance) {
MainActivity_MembersInjector.injectSugar(instance, getSugar());
return instance;
}
private Sugar getSugar() {
return new Sugar(new Butter());
}
思考:那如果我把参数由对象换成int,string,list啊等等行不行?
那肯定不行啊,上面很明显,apt帮我们实例化对象,都不是我们自己new的,人家又不知道你要赋什么值?当然不行了,有需求的话可以用moudle带参数
- 如果同时在构造方法上声明了inject,又引用了moudle,分别对应了两个构造方法,那到底执行那种呢? 答案是moudle,就不举例了
进一步使用
- module中的provides方法带参数
如果我们的provides方法中的实例需要一定的参数才可以初始化,比如:
//需要butter
new Sugar("红色", true, butter);
//当然我们可以将butter,new在这里,但是如果有100个需要butter的,难道要new100次吗?
那我们就可以让provides方法提供参数:
@Provides
Sugar providesSugar(Butter butter) {
return new Sugar("红色", true, butter);
}
参数中的Butter有两种初始化的方法:
- Butter这个类用Inject注解去标注构造函数
DaggerMainComponent中生成Sugar的时候会直接new出butter.
private Sugar getSugar() {
return MainModule_ProvidesSugarFactory.proxyProvidesSugar(mainModule, new Butter());
}
- 在本module提供一个全新的provides方法
@Provides
Butter providesButter() {
return new Butter();
}
DaggerMainComponent中生成Sugar的时候会提供一个全新的Butter方法:
private Sugar getSugar() {
return MainModule_ProvidesSugarFactory.proxyProvidesSugar(
mainModule, MainModule_ProvidesButterFactory.proxyProvidesButter(mainModule));
}
//proxyProvidesButter
public static Butter proxyProvidesButter(MainModule instance) {
return Preconditions.checkNotNull(
instance.providesButter(), "Cannot return null from a non-@Nullable @Provides method");
}
//providesButter
@Provides
Butter providesButter() {
return new Butter();
}
这样一来就很方便了,我们就可以统一管理实例,比如实际开发中通常会有一个HttpModule,用来提供网络配置的一些实例,如下:
private Retrofit createRetrofit(Retrofit.Builder builder, OkHttpClient client, String url) {
return builder
.baseUrl(url)
.client(client)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
}
@Singleton
@Provides
Retrofit.Builder provideRetrofitBuilder() {
return new Retrofit.Builder();
}
@Singleton
@Provides
OkHttpClient.Builder provideOkHttpBuilder() {
return new OkHttpClient.Builder();
}
@Singleton
@Provides
OkHttpClient provideClient(OkHttpClient.Builder builder) {
if (BuildConfig.DEBUG) {
builder.addInterceptor(new LoggingInterceptor());
}
//...太长省略
}
@Singleton
@Provides
Retrofit provideApiServiceRetrofit(Retrofit.Builder builder, OkHttpClient client) {
return createRetrofit(builder, client, APIService.TEST);
}
- @Name,@ Qualifier,@Scope
- @Name跟@Qualifier
假设一个module有两个方法提供同一个依赖,那么不管我们用上面说的哪种方式都会报错,因为dagger不知道我们到底想用哪一种,所以我们需要进行区分,非常简单,我们只需要额外添加一个@named,比如:
@Provides
@Named("sugarWithButter")
Sugar providesSugar(Butter butter) {
return new Sugar("红色", true, butter);
}
@Provides
@Named("sugarWithNone")
Sugar providesSugar2() {
return new Sugar();
}
同样inject也要加上
@Inject
@Named("sugarWithButter")
Sugar sugar;
@Inject
@Named("sugarWithNone")
Sugar sugar2;
@Qualifier跟@Name其实是一样的,只不过我们需要通过@Qualifier去自定义一个注解,然后替换掉@Name就可以了,这样不用写字符串,避免出错,例如:
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface SugarWithNone {
}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface SugarWithButter {
}
//替换掉
@Provides
@SugarWithButter
Sugar providesSugar(Butter butter) {
return new Sugar("红色", true, butter);
}
@Provides
@SugarWithNone
Sugar providesSugar2() {
return new Sugar();
}
//inject
@Inject
@SugarWithButter
Sugar sugar;
@Inject
@SugarWithNone
Sugar sugar2;
- @Scope
@Scope这个注解实际上就是定义作用域,我们最常用的@Singleton就是通过scope定义的,如下:
/**
* Identifies a type that the injector only instantiates once. Not inherited.
*
* @see javax.inject.Scope @Scope
*/
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}
假如我们在同一个activity中通过@Inject,注入很多相同的依赖,比如:
@Inject
@SugarWithNone
Sugar sugar2;
@Inject
@SugarWithNone
Sugar sugar3;
// com.gaox.dagger2test.simple.Sugar@b1753e2
// com.gaox.dagger2test.simple.Sugar@749c373
发现不是同一个对象,但是一旦我加上@Singleton
@Provides
@Singleton
@SugarWithNone
Sugar providesSugar2() {
return new Sugar();
}
@Singleton
@Component(modules = MainModule.class)
public interface MainComponent {
void inject(SimpleActivity simpleActivity);
}
// com.gaox.dagger2test.simple.Sugar@b1753e2
// com.gaox.dagger2test.simple.Sugar@b1753e2
打印内存地址一致,发现@Singleton这个注解可以让提供的依赖变成单例的,这里需要注意两点
- 由moudle提供的依赖,想让其成为单例,需要标注两处,一处是提供依赖的provides方法,一处是连接本module的component
- 如果是有构造方法直接inject提供依赖的,除了component之外,还需要在类名上标注@Singleton
@Singleton这个注解是dagger2默认实现的,严格来讲,他只是负责在component连接区域负责提供单例模式,并且其生命周期保持一致,那如果我想自己去划范围,那就需要自定义@Scope,比如说我们在实际开发中可以自定义AppScope,ActivityScope,FragmentScope,那其提供的依赖只要是用这些注解标注,其生命周期就各自对应一一绑定了,并且为单例,举个例子,自定义如下:
@Scope
@Retention(RUNTIME)
public @interface ActivityScope {
}
- Component依赖
component依赖类似于我们平常写代码将公共部分进行抽取,这样会避免使用的时候重复的去写moudle及依赖方法,我们只需要将重复的依赖抽取到基类Module,并在基类component中暴露出方法就可以,并且还可以给其指定是否为单例,最典型的例子,定义一个appModule及appComponent用于提供全局的appContext,流程如下:
- 定义appMoudle, appComponent并暴露方法
//appMoudle
@Module
public class AppModule {
private App appContext;
public AppModule(App appContext) {
this.appContext = appContext;
}
@Provides
@Singleton
public App providesAppContext() {
return appContext;
}
}
//appComponent
@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
App getAppContext();
}
AppComponent暴露出一个getAppContext的方法来提供AppModule中的上下文
- 定义activityModule,及activityComponent
//activityModule
@Module
public class ActivityModule {
@Provides
public RetrofitHelper providesRetrofitHeloper(App appContext) {
return new RetrofitHelper(appContext);
}
}
//activityComponent
@ActivityScope
@Component(dependencies = AppComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
void inject(DependencyActivity dependencyActivity);
}
这边要注意如果被依赖component的用到了scope,那依赖让也要定义(@ActivityScope)
- 在App中定义获取appComponent的方法
public static AppComponent getAppComponent() {
if (appComponent == null) {
appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(appContext))
.build();
}
return appComponent;
}
- activity进行注入
@Inject
RetrofitHelper retrofitHelper;
DaggerActivityComponent.builder()
.appComponent(App.getAppComponent())
.activityModule(new ActivityModule())
.build().inject(this);
只要依赖了,就会有一个.xxxComponent的的方法去绑定xxxComponent,我们这边只需要将App中定义的方法获取,扔进来就可以了.打印一下:
public class RetrofitHelper {
public RetrofitHelper(App appContext) {
Log.i("=======", "RetrofitHelper:" + appContext.toString());
}
}
//RetrofitHelper:com.gaox.dagger2test.dependencies.App@fe34ad
//retrofitHelper打印:
//com.gaox.dagger2test.dependencies.RetrofitHelper@b1753e2
我们发现,context首先实例化,然后做为参数将retrofitHelper成功实例化.
不仅仅是这样 App提供的是全局的,我还可以再定义一个类用构造函数进行注入:
public class ApiService {
@Inject
public ApiService(App appContext) {
}
}
//打印ApiService
//com.gaox.dagger2test.dependencies.ApiService@749c373
实际开发中,在mvp模式里经常会用到这种方式在presenter中去提供网络请求的一个工具类,如下:
@Inject
public MainPresenter(RetrofitHelper mRetrofitHelper) {
this.mRetrofitHelper = mRetrofitHelper;
}
而且,最简单粗暴,想用appContext,直接Inject,比如:
@Inject
App appContext;
//打印一下,发现跟上面一个地址,为单例
//com.gaox.dagger2test.dependencies.App@fe34ad
想一想,开发中放到baseActivity中,app上下文随便拿还是很方便的
- MVP+Dagger2
MVP是什么模式在这边我们就不介绍了,我们要想在mvp这种模式中去使用dagger,无非就是将P注入到V,再把V注入到P作为回调,我们之前总结的用法是完全可以实现的,在这边我们写一个简单的架子,以供参考
- 首先我们需要定义一个module去提供view跟presenter,再定义一个component作为连接桥梁
@Module
public class MVPModule {
private MVPContact.MvpView view;
public MVPModule(MVPContact.MvpView view) {
this.view = view;
}
@Provides
public MVPContact.MvpView providesMVPView() {
return view;
}
@Singleton
@Provides
public MVPresenter providesMVPresenter(MVPContact.MvpView mvpView){
return new MVPresenter(mvpView);
}
}
@Singleton
@Component(modules = MVPModule.class)
public interface MVPComponent {
void inject(MVPActivity mvpActivity);
}
其实通过看moudle就已经很清楚,我们利用了构造方法将当前的view传进来进行初始化,然后将view作为参数去初始化presenter,当然,这不是真正的v注到p,只是取了个巧,因为我认为再把
DaggerxxPresenterComopnent.build().moudle.....省略
写到presenter里有点不美好,所以就用构造方法传了..
- 接下来定义presenter 及 activity注入
public class MVPresenter {
private MVPContact.MvpView view;
public MVPresenter(MVPContact.MvpView view) {
this.view =view;
}
public void injectPSuccess(){
Log.i("=====","p被成功注入");
view.injectVSuccess();
}
}
//activity
DaggerMVPComponent.builder()
.mVPModule(new MVPModule(this))
.build()
.inject(this);
mvPresenter.injectPSuccess();
}
@Override
public void injectVSuccess() {
Log.i("=====","v被成功注入");
}
//打印
//=====: p被成功注入
//=====: v被成功注入
总结一下:
思路就是一个view需要一个moudle去提供p,而view又作为参数去生成p,
进而互相调用.
这个例子基本上就是mvp最简单的dagger使用了,不存在更简单的,实际开发中当然要根据业务需要进行进一步封装,之前说的component依赖肯定也要用上,用法了解了,都不困难.
这篇文章也是从17年开始写,写了一半,静静的躺在笔记里,到现在一晃又两年了,附demo,也希望自己可以变成自己想要成为的人.
参考资料
todo-mvp-dagger
dagger入门
网友评论