从零开始学dagger2
来源:互联网 发布:那年夏天宁静的海 知乎 编辑:程序博客网 时间:2024/06/05 19:50
前言
dagger2这个框架做安卓的同学应该都听说过,现在公司一般的项目架构都是Retrofit + rxjava + mvp,然后dagger2框架又很适用于mvp模式,所以它也十分主流,虽然十分主流,但是很多公司的项目依然没用用起来,为什么呢?因为它和rxjava一样,学习曲线很陡,刚学起来是比较吃力的,作者也是如此。
这篇文章题是从零开始学dagger2,对于作者而言是第三次从零开始,因为在去年的时候我也是花过时间和心思研究过这个框架,奈何水平有限还是比较晕,但是没关系,一遍不行就第二遍,再不行就加一遍,希望通过这篇文章同大家一起学习。
正文
dagger2是一个依赖注入的框架,什么叫依赖注入呢?假如我是一家卖手机的公司,我虽然卖手机,但是不可能所有的零件都是我自己造吧?CPU、内存这些要自己造可不简单,所以我就要找别的公司帮我造这些东西,依赖关系就产生了,手机公司就依赖生产CPU的公司了。
/** * 手机公司 */public class CellPhoneCompany { CpuCompany cpuCompany; public CellPhoneCompany() { cpuCompany = new CpuCompany(); } public CellPhoneCompany(CpuCompany cpuCompany) { this.cpuCompany = cpuCompany; } // 生产手机 public void makePhone() { cpuCompany.makeCpu(); }}/** * Cpu生产公司 */public class CpuCompany { public CpuCompany() { } public void makeCpu() { }}
如果使用dagger2会变成什么样呢?
/** * 手机公司 */public class CellPhoneCompany { @Inject CpuCompany cpuCompany; // 生产手机 public void makePhone() { cpuCompany.makeCpu(); }}/** * Cpu生产公司 */public class CpuCompany { @Inject public CpuCompany() { } public void makeCpu() { }}
在依赖类的构造方法和被使用的地方都添加了一个@Inject注解,当然光靠这个注解是无法使用的,还需要其他的注解配合起来才能使用。因为使用dagger2,CellPhoneCompany中减少了2个用来添加依赖的方法,不仅如此,假如我们在开发过程中CpuCompany的构造方法需要添加参数,也就是说所有依赖于CpuCompany的地方都需要做修改,那是很麻烦的。dagger2的作用就是降低这种依赖的耦合度。
在使用dagger2之前我们要把它引入项目。添加依赖:
// dagger2compile 'com.google.dagger:dagger:2.7'annotationProcessor 'com.google.dagger:dagger-compiler:2.7'
@Inject、@Component、@Module、@Provides
看到这4个注解不要慌,我也不会向你用语言去解释他们是干吗的,而是会带你从源码中反过来去理解它们是干吗的。
搭好dagger2之后就来使用吧,还是继续我们上面的例子
/** * Cpu生产公司 */public class CpuCompany { @Inject public CpuCompany() { } // 生产CPU public void makeCpu() { }}
这里在构造方法上加了一个@Inject的注解,被这个注解注释的类的构造方法,dagger2会通过内部机制找到它,然后初始化
然后还需要有一个对应的Component,它的作用就是桥接CpuCompany和CellPhoneCompany的
@Componentpublic interface CpuCompanyComponent { CpuCompany inject();}
这里使用的是接口,作者试了,使用class会报错,找不到这个CpuCompanyComponent。然后这里的inject()方法和前面的@Inject注解没有关系,你可以把它改成你想要的名字。
接下来是使用的方法:
public class CellPhoneActivity extends AppCompatActivity { private static final String TAG = "Dagger2Log"; @Inject CpuCompany cpuCompany; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main2); // 重点是这句 cpuCompany = DaggerCpuCompanyComponent.builder().build().inject(); Log.i(TAG, "onCreate: " + cpuCompany); }}
这里要注意的是,如果你直接使用DaggerCpuCompanyComponent是找不到这个类的,因为dagger2通过apt插件生成代码,需要你build/make project后才能使用。android studio的同学可以使用这个:
让我们来分析一下,首先DaggerCpuCompanyComponent这个类的名字是CpuCompanyComponent的前面加了一个“Dagger”,而我们的CpuCompanyComponent类确实是被@Component所注释的,这就是他的命名规则,然后我们看一下源码:
public final class DaggerCpuCompanyComponent implements CpuCompanyComponent { private DaggerCpuCompanyComponent(Builder builder) { assert builder != null; } public static Builder builder() { return new Builder(); } public static CpuCompanyComponent create() { return builder().build(); } @Override public CpuCompany inject() { return CpuCompany_Factory.create().get(); } public static final class Builder { private Builder() {} public CpuCompanyComponent build() { return new DaggerCpuCompanyComponent(this); } }}
这里做了几件事情:
1. builder()方法返回一个Builder实例,这个Builder是一个静态内部类
2. build()方法返回一个DaggerCpuCompanyComponent实例,这里会把Builder自身给传进去
3. inject()方法返回我们所需要的CpuCompany实例,这个inject()方法就是通过实现CpuCompanyComponent接口而来的
4. 这里有一个CpuCompany_Factory,看名字应该是用来创建CpuCompany的工厂,我们看一下:
public enum CpuCompany_Factory implements Factory<CpuCompany> { INSTANCE; @Override public CpuCompany get() { return new CpuCompany(); } public static Factory<CpuCompany> create() { return INSTANCE; }}public interface Factory<T> extends Provider<T> {}public interface Provider<T> { T get();}
发现了吗,这个类是个enum,我第一次见枚举还可以这样使用,枚举有线程安全、唯一的特点,这个枚举实现了Factory接口然后通过create()方法返回自身实例,再通过get()方法返回我们真正所需要的CpuCompany实例,这个get方法是从Provider接口所继承下来实现的,返回的类型就是我们注解的类型,这里记一下这个Provider接口,我们待会还会联系到这个接口。
关系是这样的:
要使用CpuCompany的地方–>DaggerCpuCompanyComponent–>CpuCompany_Factory–>CpuCompany
dagger2就是以这样一种方式,通过Component组件把调用者和被调用者联系起来。就做到了用
DaggerCpuCompanyComponent.builder().build().inject()代替new CpuCompany(),你可能会问,就这样我不但没减少代码,还增加了好多东西有个啥用呢?别急,这只是一个最简单例子来探索dagger2基本的实现,接下来让我们继续深入。
这里我们必须使用cpuCompany = DaggerCpuCompanyComponent.builder().build().inject()才能实例化,因为我们的inject()方法的返回类型是CpuCompany,那能不能更加简单一点呢?答案是肯定的
修改一下CpuCompanyComponent
@Componentpublic interface CpuCompanyComponent { void inject(CellPhoneActivity activity);}
重新make project一下,我们就可以把cpuCompany = DaggerCpuCompanyComponent.builder().build().inject()改成==> DaggerCpuCompanyComponent.builder().build().inject(this)了,给我的感觉是原先的方式支持在任何地方实例化,修改过后的方式变成了只能在CellPhoneActivity中实例化,有利有弊,就我们现在而言使用后者要更加方便
看一下源码做了什么改变:
public final class DaggerCpuCompanyComponent implements CpuCompanyComponent { private MembersInjector<CellPhoneActivity> cellPhoneActivityMembersInjector; private DaggerCpuCompanyComponent(Builder builder) { assert builder != null; initialize(builder); } public static Builder builder() { return new Builder(); } public static CpuCompanyComponent create() { return builder().build(); } @SuppressWarnings("unchecked") private void initialize(final Builder builder) { this.cellPhoneActivityMembersInjector = CellPhoneActivity_MembersInjector.create(CpuCompany_Factory.create()); } @Override public void inject(CellPhoneActivity activity) { cellPhoneActivityMembersInjector.injectMembers(activity); } public static final class Builder { private Builder() {} public CpuCompanyComponent build() { return new DaggerCpuCompanyComponent(this); } }}
修改的地方有:
1. 在DaggerCpuCompanyComponent的构造方法中添加了一个initialize方法
2. 新增了一个成员变量MembersInjector,看这个接口的名字和内容就知道是注册成员列表,也就是我们的CellPhoneActivity。
public interface MembersInjector<T>{ void injectMembers(T instance);}
- 这个MembersInjector是在initialize方法中被创建的,这里又有一个CellPhoneActivity_MembersInjector对象,让我们继续跟进去
public final class CellPhoneActivity_MembersInjector implements MembersInjector<CellPhoneActivity> { private final Provider<CpuCompany> cpuCompanyProvider; public CellPhoneActivity_MembersInjector(Provider<CpuCompany> cpuCompanyProvider) { assert cpuCompanyProvider != null; this.cpuCompanyProvider = cpuCompanyProvider; } public static MembersInjector<CellPhoneActivity> create(Provider<CpuCompany> cpuCompanyProvider) { return new CellPhoneActivity_MembersInjector(cpuCompanyProvider); } @Override public void injectMembers(CellPhoneActivity instance) { if (instance == null) { throw new NullPointerException("Cannot inject members into a null reference"); } instance.cpuCompany = cpuCompanyProvider.get(); } public static void injectCpuCompany( CellPhoneActivity instance, Provider<CpuCompany> cpuCompanyProvider) { instance.cpuCompany = cpuCompanyProvider.get(); }}
这里我们又看见Provider了,感情他就是个提供者的角色,提供一些参数。这段代码就做了2件事情:(一)create()方法返回一个带有CpuCompany实例的CellPhoneActivity_MembersInjector实例;(二)injectMembers()方法把CellPhoneActivity和CpuCompany给关联起来了,这样一来经过我们修改后的代码整个逻辑就通顺了。
你可能会问,假如CpuCompany的构造方法中从无参变成有参了呢?
/** * Cpu生产公司 */public class CpuCompany { String color; @Inject public CpuCompany(String color) { this.color = color; } // 生产CPU public void makeCpu() { }}
这里我们就要用到@Module和@Provides了
@Modulepublic class CpuCompanyModule { @Provides public String providesColor() { return "五颜六色"; }}
Component类中也要记得关联起来:
@Component(modules = CpuCompanyModule.class)public interface CpuCompanyComponent { void inject(CellPhoneActivity activity);}
让我们把程序跑起来,日志:
onCreate: 五颜六色
很棒,成功了。让我们再次一头扎进源码中。。
public final class DaggerCpuCompanyComponent implements CpuCompanyComponent { private Provider<String> providesColorProvider; private Provider<CpuCompany> cpuCompanyProvider; private MembersInjector<CellPhoneActivity> cellPhoneActivityMembersInjector; private DaggerCpuCompanyComponent(Builder builder) { assert builder != null; initialize(builder); } public static Builder builder() { return new Builder(); } public static CpuCompanyComponent create() { return builder().build(); } @SuppressWarnings("unchecked") private void initialize(final Builder builder) { this.providesColorProvider = CpuCompanyModule_ProvidesColorFactory.create(builder.cpuCompanyModule); this.cpuCompanyProvider = CpuCompany_Factory.create(providesColorProvider); this.cellPhoneActivityMembersInjector = CellPhoneActivity_MembersInjector.create(cpuCompanyProvider); } @Override public void inject(CellPhoneActivity activity) { cellPhoneActivityMembersInjector.injectMembers(activity); } public static final class Builder { private CpuCompanyModule cpuCompanyModule; private Builder() {} public CpuCompanyComponent build() { if (cpuCompanyModule == null) { this.cpuCompanyModule = new CpuCompanyModule(); } return new DaggerCpuCompanyComponent(this); } public Builder cpuCompanyModule(CpuCompanyModule cpuCompanyModule) { this.cpuCompanyModule = Preconditions.checkNotNull(cpuCompanyModule); return this; } }}
比起我们第二次修改,又多了代码,不要慌,每次增加代码的量不多,我们细细看。
修改的地方:
1. 新增了Provider和Provider两个成员变量,其中Provider不就是我们在CpuCompanyModule中proveidesColor方法提供的数据吗,这里就说明添加了@Provides注解的方法会在这里生成一个Provider对象。
2. 还记得我们前面的修改中Provider是怎么来的吗?是通过CpuCompany_Factory.create()来的吧,而且它就是new CpuCompany_Factory()就返回了,但是现在不行了,为什么呀?因为他构造方法要传参数了啊。这里我们先要提供一个Provider才能创建这个Provider。这个Provider通过CpuCompanyModule_ProvidesColorFactory.create(builder.cpuCompanyModule)创建,很明显,这里也是一个工厂类,用来创建Provider,还记得我们的CpuCompanyModule吗,这里就需要用到他了,这个Module是在build()方法里面实例化的,然后和Builder绑一起了,现在要用了,就从Builder里面取出来就好了这里的CpuCompanyModule_ProvidesColorFactory就不细看了,感觉现在所有的Factory里面都是一个套路。
文章上述内容主要提到了几个注解:
- @Inject 使用此注解的属性或构造方法Dagger2会生成对应实例
- @Module 带有此注解的类,用来提供依赖,里面用@Provides修饰的以provide开头的方法用来提供参数
- @Component 用来将@Inject和@Module联系起来的桥梁
@Scope
这个注解限定了实例的使用范围,就像我们的Activity有生命周期从onCreate到onDestroy,我们就可以定义一个Scope限定它的使用范围是从Activity的onCreate到onDestroy。
Scope我们可以自己定义,dagger2也提供了一个自带的@Singleton注解,看这个名字应该就知道这个注解代表的是单例吧?让我们一起验证一下,首先修改一下我们的例子
CpuCompanyModule中提供一个providesCpuCompany来自己提供实例创建的方法,这里不提供这个方法dagger2就会在内部帮我们创建实例,但是我们提供之后,dagger2就会跑我们提供的方法
@Modulepublic class CpuCompanyModule { @Provides public String providesColor() { return "五颜六色"; } @Provides public int providesSize() { return 3; } @Provides @Singleton public CpuCompany providesCpuCompany(String color, int size) { return new CpuCompany(color, size); }}
然后给providesCpuCompany加上@Singleton注解,再修改一下CpuCompanyComponent
@Singleton@Component(modules = CpuCompanyModule.class)public interface CpuCompanyComponent { void inject(CellPhoneActivity activity);}
再修改一下Activity
public class CellPhoneActivity extends AppCompatActivity { private static final String TAG = "Dagger2Log"; @Inject CpuCompany cpuCompany; @Inject CpuCompany cpuCompany1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main2); DaggerCpuCompanyComponent.create().inject(this); Log.i(TAG, "onCreate: " + cpuCompany); Log.i(TAG, "onCreate: " + cpuCompany1); }}
这里我们创建了2个CpuCompany实例,然后通过Log打印内存地址查看是否相同。日志:
onCreate: com.example.dagger2testmodule.test.CpuCompany@3a2fbbc9onCreate: com.example.dagger2testmodule.test.CpuCompany@3a2fbbc9
内存地址是相同的,说明是同一个对象,来继续看源码。被@Singleton修饰的方法会生成一个DoubleCheck的类,正是这个类控制了单例的生成
public final class DoubleCheck<T> implements Provider<T>, Lazy<T> { private static final Object UNINITIALIZED = new Object(); private volatile Provider<T> provider; private volatile Object instance = UNINITIALIZED; private DoubleCheck(Provider<T> provider) { assert provider != null; this.provider = provider; } @SuppressWarnings("unchecked") // cast only happens when result comes from the provider @Override public T get() { Object result = instance; if (result == UNINITIALIZED) { synchronized (this) { result = instance; if (result == UNINITIALIZED) { result = provider.get(); /* Get the current instance and test to see if the call to provider.get() has resulted * in a recursive call. If it returns the same instance, we'll allow it, but if the * instances differ, throw. */ Object currentInstance = instance; if (currentInstance != UNINITIALIZED && currentInstance != result) { throw new IllegalStateException("Scoped provider was invoked recursively returning " + "different results: " + currentInstance + " & " + result); } instance = result; /* Null out the reference to the provider. We are never going to need it again, so we * can make it eligible for GC. */ provider = null; } } } return (T) result; } /** Returns a {@link Provider} that caches the value from the given delegate provider. */ public static <T> Provider<T> provider(Provider<T> delegate) { checkNotNull(delegate); if (delegate instanceof DoubleCheck) { /* This should be a rare case, but if we have a scoped @Binds that delegates to a scoped * binding, we shouldn't cache the value again. */ return delegate; } return new DoubleCheck<T>(delegate); } /** Returns a {@link Lazy} that caches the value from the given provider. */ public static <T> Lazy<T> lazy(Provider<T> provider) { if (provider instanceof Lazy) { @SuppressWarnings("unchecked") final Lazy<T> lazy = (Lazy<T>) provider; // Avoids memoizing a value that is already memoized. // NOTE: There is a pathological case where Provider<P> may implement Lazy<L>, but P and L // are different types using covariant return on get(). Right now this is used with // DoubleCheck<T> exclusively, which is implemented such that P and L are always // the same, so it will be fine for that case. return lazy; } return new DoubleCheck<T>(checkNotNull(provider)); }}
这里我们其实只需要看最重要的get方法就好了,因为provide方法所提供的数据最后都是通过这个get方法来返回的。这个get方法一开始就做了一个doubleCheck,就是我们写单例类是判断对象是否为空的方法,然后把通过provider的get方法把对象(就是我们的CpuCompany实例)赋值给result,再返回result,等到下次再这个方法时判断条件不会成立就直接返回原来的CpuCompany实例了。当然我们也可以自定义Scope来限定作用域,比如说可以创建一个PerActivity的Scope
@Scope@Retention(RUNTIME)public @interface PerActivity {}
组件依赖
看了上面例子你有可能想问,假如在正式项目中使用,我要有一个SpUtils的单例不仅能在所有的Activity中使用,还要能在Application中使用,该怎么定义呢?接下来我们来学习一下组件依赖。Component之间是可以相互依赖的,但是有几个点要注意:
1. 依赖的组件之间的域不能相同,比如有一个组件是@Singleton,那么和他依赖的组件就不能使用这个域了
2. 父组件必须要提供子组件需要的元素,比如说我的SpUtils很明显要再Application中创建,但是又要在Activity中使用它,那么就必须在父组件中暴露给子组件它才能使用。
上代码,首先是我们的AppComponent,用@Singleton修饰的,作用域与Application相同:
@Singleton@Component(modules = AppModule.class)public interface AppComponent { void inject(MyApplication application); SpUtils sqUtils();// 这里必须提供给子组件}
然后是AppModule,这里给AppModule添加了一个构造方法把我们的Application传进去,通过provide方法提供出来,还有一个provideSqUtils用来提供SpUtils实例。这里都是用@Singleton修饰表示单例
@Modulepublic class AppModule { public final Application application; public AppModule(Application application) { this.application = application; } @Provides @Singleton Application provideApp() { return application; } @Provides @Singleton SpUtils provideSqUtils() { return new SpUtils(application); }}
接下来是ActivityComponent,这里就用到了上面我们自定义的@PerActivity,同时还添加了组件依赖,依赖的是AppComponent
@PerActivity@Component(modules = ActivityModule.class, dependencies = AppComponent.class)public interface ActivityComponent { void inject(MainActivity activity);}
然后是ActivityModule和SpUtils
@Modulepublic class ActivityModule {}
public class SpUtils { @Inject public SpUtils(Application application) {}}
自定义Application,这里需要把appComponent显示提供出去,因为在Activity中注册时需要用到:
public class MyApplication extends Application { private AppComponent appComponent; @Override public void onCreate() { super.onCreate(); appComponent = DaggerAppComponent.builder() .appModule(new AppModule(this)) .build(); } public static MyApplication get(Context context) { return (MyApplication) context.getApplicationContext(); } public AppComponent getAppComponent() { return appComponent; }}
再看Activity中是怎么使用的:
public class MainActivity extends AppCompatActivity { private static final String TAG = "mMainActivity"; @Inject SpUtils spUtils1; @Inject SpUtils spUtils2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaggerActivityComponent.builder() .appComponent(MyApplication.get(this).getAppComponent()) .build() .inject(this); Log.i(TAG, "onCreate: " + spUtils1); Log.i(TAG, "onCreate: " + spUtils2); }}
日志:
spUtils1=com.example.testmodule.SpUtils@cdc8c8espUtils2=com.example.testmodule.SpUtils@cdc8c8e
说明成功了。
有了这个组件依赖,加上前面讲的知识,对于dagger2 + mvp模式就能够自己消化了和封装了,祝你也能成功且理解dagger2的使用,它真的是一个很强大的框架。
总结
终于讲完了,如果,你觉得从头到位很懵逼,没关系,自己动手尝试,一个一个代码跟着敲,跑到源码里面自己去理逻辑,帖子里面所谓的总结总归都是从源码里面来的;如果你从头到位思路很清楚,那么恭喜你天赋很高。。。理解能力强,希望这篇帖子能给你带来一点帮助
- 从零开始学dagger2
- 看源码学dagger2
- 从零开始学
- 从零开始的Android新项目4 - Dagger2篇
- 从零开始搭建一个项目(rxJava+Retrofit+Dagger2)
- 动手动脑学Dagger2系列一
- --------------------《洪恩从零开始学日语》---------------------------
- 洪恩从零开始学日语
- 从零开始学Shark(转载)
- 从零开始学炒股
- 从零开始学计算机语言
- 从零开始学建网
- 高中毕业从零开始学
- 从零开始学GIS
- 怎样从零开始学J2EE
- 从零开始学Java (一)
- 从零开始学Android
- 从零开始学股票
- jquery ajax 操作
- 工作日志
- PageRank的简单实现(scala版)
- Flask应用部署
- 即拿即用-仿IOS风格的弹出框和对话框
- 从零开始学dagger2
- 安全渗透测试网站
- Huffman 编码 实验报告
- 梁广轩_1505010601_实验4
- Java开发的准备
- 389. Find the Difference
- 正则匹配主机及端口
- 搬运自己的mysql学习笔记1.工具类的抽取
- Redis之链表