在学习android architecture components(简称acc)时下载了google官方demo,demo里有一部分是关于dagger+mvvm(mvp)+acc的,本以为阅读起来没有压力但结果却是一脸懵逼,是的,dagger的写法以及注解完全陌生。难道dagger更新了?本着落后就要挨打的原则去google了一番,发现国内资料通篇都是dagger原本的用法,并没有找到我需要的。最后终于在国外网站找到了一篇dagger2.1新用法的介绍,配合dagger官网终于了解了一二,这也坚定了我从头写一篇Dagger文章的决心。
本文适合人群:1、从未了解过dagger,2、仅简单使用API但不了解为何这样使用,3、可以很好的使用并理解dagger,仅需了解新版本(可以直接跳到第四部分)
翻译成中文也就是依赖注入,我们先用一个很简单的例子来了解一下
class Clothes {
final int Defense = 5;
}
class Pants {
final int Defense = 10;
}
class hero {
void printDefense() {
Clothes clothes = new Clothes();
Pants pants = new Pants();
System.out.print("您的角色拥有防御值: "+clothes.Defense + pants.Defense);
}
}
这是一段很简单的代码,初始化角色时,拥有衣服裤子两件装备。但仔细查看,在hero类里创建了clothes和pants两个类,造成了耦合,当你修改clothes或pants时很可能影响到hero类。所以我们通常会将上述代码优化如下:
class hero {
private Clothes clothes;
private Pants pants;
public hero(Clothes clothes, Pants pants) {
this.clothes = clothes;
this.pants = pants;
}
void printDefense() {
System.out.print("您的角色拥有防御值: "+clothes.Defense + pants.Defense);
}
}
没错放到构造方法里,这就是'依赖注入'了,hero依赖了clothes和pants,同样的,set方法也可以进行依赖注入。大家是不是在不知不觉中也使用过类似的方式呢:)
对比一下两种方式,前者的缺点如下:
1、clothes和pants无法重用,降低了代码的重用性
2、增加了代码的耦合性,一旦clothes和pants更改可能需要修改hero类很多地方
3、很难进行单元测试
好的,接下来我们就开始用依赖注入的方法创建hero
public static void main(String[] args){
Clothes clothes = new Clothes();
Pants pants = new Pants();
Hero hero = new Hero(clothes, pants);
hero.printDefense();
}
这样子看起来没什么大问题,但是当依赖多了,或者依赖也依赖了其他依赖,就会相当臃肿。有点绕吗?没关系我们看代码:
public static void main(String[] args) {
Color red = new Color();
Clothes clothes = new Clothes(red);
Pants pants = new Pants();
Shoes shoes = new Shoes();
Hat hat = new Hat();
....
Hero hero = new Hero(clothes, pants, shoes, hat, ....);
hero.printDefense();
}
上面的代码中Clothes又依赖了Color,说不定Color又可能会依赖些什么,加上下面的Pants、Shoes、hat......等等,构造我们的Hero需要太多依赖,这样我们使用hero时就非常不方便,主方法就会越来越难以维护。
这样,Dagger就孕育而生了,他是一个DI框架,会让我们的依赖注入工作显得非常轻松。自动生成依赖,而我们只需要添加注解即可。
我们用Dagger的方式来重写上面的代码:
class Clothes {
final int Defense = 5;
@Inject
public Clothes() {
}
}
class Pants {
final int Defense = 10;
@Inject
public Pants() {
}
}
class Hero {
private static final String TAG = "Hero";
private Clothes clothes;
private Pants pants;
@Inject
public Hero(Clothes clothes, Pants pants) {
this.clothes = clothes;
this.pants = pants;
}
public void printDefense() {
Log.e(TAG, "您的角色拥有防御值: " + (clothes.Defense + pants.Defense));
}
}
是的,就是这么简单,只需要添加@Inject注解,Dagger就能帮我们自动构建Hero对象,当我们使用时就像这样:
Component
interface HeroComponent {
Hero getHero();
}
public static void main(String[] args){
// Clothes clothes = new Clothes();
// Pants pants = new Pants();
// Hero hero = new Hero(clothes, pants);
// hero.printDefense();
HeroComponent component = DaggerHeroComponent.create();
Hero hero = component.getHero();
hero.printDefense();
}
我们来梳理一下:
1、首先创建一个component接口并添加@component注解,添加获取Hero的方法
2、给Hero构造方法添加@Inject注解,表示需要自动生成该类
3、给依赖类添加@Inject,表示改类也需要自动生成,如此递归添加直到所有依赖都能自动生成
4、构建项目(必须先构建,Dagger会帮你生成一些辅助类),使用名为‘Dagger+YourComponent’的类创建Hero对象
5、愉快的使用吧!
这样一来无论Hero需要多少依赖,我们都可以很简单的生成,不会造成类的臃肿和耦合,使用Hero时不用关心依赖从哪里来,怎么来的,就算Hero或依赖构建方式改变,也不需要修改调用的main函数,很好的解耦。
这时有些童鞋就会问了,如果我需要使用类似Retrofit、OkHttp之类的三方库,而我们却无法去库里添加@Inject注解,该怎么办?没关系,Dagger同样为大家准备了对策:@Module、@Provides
@Module:类似一个仓库,提供Retrofit之流的实例
@Provides:仓库里具体产品的标志,表示该产品对外提供
@Module
public class MyModule {
@Provides
public Retrofit ProvideRetrofit() {
return new Retrofit.Builder()
.baseUrl("www.google.com")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(setupClient())
.build();
}
}
这样就写好了一个提供Retrofit的方法,我们还需要将module提交到component:
Component(modules = MyModule.class)
interface HeroComponent {
Hero getHero();
Retrofit getRetrofit();
}
这样,我们就可以愉快的使用Retrofit了,使用方式和Hero一样,我就不累述了。
关于其他API大家可以查阅资料,本文重在思路介绍。
建议初学者在阅读以下内容前先了解更多Dagger2的使用,推荐文章:给初学者的Dagger2
因为以下内容仅适合使用过Dagger2的童鞋了!!!
打起精神,重点来了!!
在讲解重点之前,我们先来看一段代码:
((MyApplication) getApplication())
.getAppComponent()
.myActivity(new MyActivityModule(userId))
.build()
.inject(this);
在熟悉使用过Dagger2的coder眼中,这段代码几乎贯穿了整个应用程序,每一个activity都会调用同样的代码,相信大家也想尽办法去优化该段代码,包括将其放入Base中或者做了各种各样的封装,可能都不太尽人意(至少笔者没有找到最优解-_-)。
没错!官方这次升级完美的解决了该问题,以前的实现方式我就不多说了,直接讲述新版本方案。
我先按流程走一遍,让大家熟悉熟悉:
1、老规矩,gradle添加依赖
implementation 'com.google.dagger:dagger-android:2.15'
implementation 'com.google.dagger:dagger-android-support:2.15' // if you use the support libraries
annotationProcessor 'com.google.dagger:dagger-android-processor:2.15'
annotationProcessor 'com.google.dagger:dagger-compiler:2.15'
笔者写的时候最新版为2.15,大家使用时可以更换为官方最新版。
2、创建AppComponent
@Component(modules = {
AndroidSupportInjectionModule.class,
AppModule.class,
ActivityBuilder.class})
public interface AppComponent extends AndroidInjector {
@Override
void inject(MyApplication app);
@Component.Builder
interface Builder {
@BindsInstance
Builder application(Application application);
AppComponent build();
}
}
@Component.Builder:自定义component构造器.如果看过Dagger2源码的童鞋应该知道component是用Builder模式创建的,而这里提供了一个自定义构造器。目前先不管,你甚至可以删除这个Builder都没有影响,Dagger会自动生成,这里我只是想展示一下这个注解
继承了AndroidInjector类,重写了他的Inject方法,这个方法必须写,初始化AppComponent用。
@Component(modules = {
AndroidSupportInjectionModule.class,
AppModule.class,
MainActivityModule.class})
Component注解里添加了需要使用到的Module
AndroidSupportInjectionModule.class:这是Dagger2.1初始化必须的module,由Dagger内部完成,用于提供各种AndroidInjector,我们只需要固定加上即可
AppModule.class:用于Application需要初始化提供的全局module,比如刚刚提到的Retrofit、Okhttp等全局对象
ActivityBuilder.class:这就是我们Activity所需的Module了
前两者我们先不关注,重点看看ActivityBuilder,这和我们以前的使用方式就不大相同了,先来看看他的实现。
3、创建ActivityBuilder
@Module
public abstract class ActivityBuilder {
@Binds
@IntoMap
@ActivityKey(MainActivity.class)
abstract AndroidInjector.Factory extends Activity> bindMainActivity(MainActivityComponent.Builder builder);
}
这里是所有Activity(Fragment等)的Component.builder的集合,需要添加@Binds @IntoMap @ActivityKey注解,暂且不管他们的具体作用,只需要了解@Binds就类似于@Provides就可以了。这个类的作用就是让Dagger知道我们所有的使用地。
4、创建MainActivityComponent
@Subcomponent(modules = {MainActivityModule.class})
public interface MainActivityComponent extends AndroidInjector {
@Subcomponent.Builder
public abstract class Builder extends AndroidInjector.Builder {
}
}
需要使用@Subcomponent注解,所以不需要再像以前写Inject之类的方法,modules和以前一样
值得一说的就是内部类Builder,是固定写法,和第三步配套使用。
5、创建MainActivityModule
@Module
public class MainActivityModule {
@Provides
MainView provideMainView(MainActivity mainActivity){
return mainActivity;
}
@Provides
MainPresenter provideMainPresenter(MainView mainView, ApiService apiService) {
return new MainPresenter(mainView, apiService);
}
}
这个也和以前一样,提供需要的依赖
不要忘记在Appmodule里添加subComponent注释,如果Activity多了这里就会添加非常多的XXComponent
@Module(subcomponents = {MainActivityComponent.class,XX,XX,XX.....})
public abstract class AppModule {
}
6、初始化Application
public class MyApplication extends Application implements HasActivityInjector {
@Inject
DispatchingAndroidInjector dispatchingAndroidInjector;
@Override
public void onCreate() {
super.onCreate();
DaggerAppComponent.builder().application(this).build().inject(this);
}
@Override
public AndroidInjector activityInjector() {
return dispatchingAndroidInjector;
}
}
继承HasActivityInjector,重写activityInjector方法,返回一个DispatchingAndroidInjector
然后在oncreate里初始化
7、MainActivity使用
public class MainActivity extends AppCompatActivity implements MainView {
@Inject
MainPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
presenter.loadMain();
}
@Override
public void onMainLoaded() {
((TextView)findViewById(R.id.hw)).setText("hello Dagger2.1");
}
}
调用AndroidInjection.inject(this)即可,无须再调用一长串Dagger链。
到这里,初步使用就已经结束了,看到这里可能会觉得‘尼玛,好像更麻烦了’,‘绕来绕去头都大了’的感慨,没错,确实是更麻烦了,不过别着急,前面我只是希望大家看看最基础的用法,然后再介绍更简洁的方式,不然大家很可能只会用而不知道其中的来历。
优化一:继承DaggerApplication、DaggerActivity等类
大家应该还记得刚刚提到的Application类的实现,需要:
1、继承HasActivityInjector,2、重写activityInjector方法,3、返回一个DispatchingAndroidInjector
其实不仅是Application,在Activity、Fragment里只要有subcomponent就必须要实现类似的类,举个栗子:
一个Activity里有一个Fragment,fragment需要依赖注入,这时候Activity就必须
1、继承HasFragmentInjector, HasSupportFragmentInjector
2、重写supportFragmentInjector、fragmentInjector方法
3、返回DispatchingAndroidInjector
这样的话将会非常麻烦而且重复,但只需要继承了DaggerApplication、DaggerActivity,一切都变得简单了,Dagger2.1都帮你搞定了。这里放出优化后的Application:
public class MyApplication extends DaggerApplication {
@Override
protected AndroidInjector extends DaggerApplication> applicationInjector() {
AppComponent appComponent = DaggerAppComponent.builder().application(this).build();
appComponent.inject(this);
return appComponent;
}
}
对比上面的是不是简单了很多,其实Activity会更明显,而且每个Activity节省的代码加起来是不可计数的,就留给大家自己去尝试了。
优化二:使用注解@ContributesAndroidInjector
这是Dagger2.1最强大的注释,会帮助你节省数以万计的代码,可能你看到之前的介绍还并不希望升级/使用Dagger2.1,但是用了他之后你就会爱上他。
还记得我们的ActivityBuilder吗?多了@Binds @IntoMap @ActivityKey注解,可能之前我们根本没有用到过,并用他们关联到Component.Builder,同样是重复的工作。利用@ContributesAndroidInjector可以省略这一切,于是我们的ActivityBuilder就变成了这样:
@Module
public abstract class ActivityBuilder {
@ContributesAndroidInjector(modules = MainActivityModule.class)
abstract MainActivity bindMainActivity();
@ContributesAndroidInjector(modules = {SecondActivityModule.class, SecondFragmentProvider.class})
abstract SecondActivity bindSecondActivity();
}
而我们的MainActivityComponent类就可以删掉了,是的没错,直接删掉吧!Dagger会帮我们自动生成,这样每个Activity只需要对应一个Module即可,还记得以前每个Activity对应一个Component和一个Module吗?
AppModule也可以不需要列出所有Module了,直接删掉!
通过优化后,你的代码已经非常简洁了,大家可以尝试用新版的Dagger来写,然后对比以前的方式,你一定会迫不及待使用新版的。
本文完整代码已经提交到GitHub
分为两个分支,master中使用了初始版,complete中使用了简洁的方式,并利用Dagger实现了MVP模式以及绑定Fragment的方式,大家一定要下载阅读,这样更有利于理解。
当然如果觉得有用的话记得点一下star,这是对作者的最大鼓励。
地址:https://github.com/mrqatom/DaggerInjection
下期预告:@Binds详细讲解、Dagger2.1源码讲解
参考文档:
https://medium.com/@iammert/new-android-injector-with-dagger-2-part-1-8baa60152abe
https://medium.com/@harivigneshjayapalan/dagger-2-for-android-beginners-dagger-2-part-i-f2de5564ab25
https://google.github.io/dagger/android