世界是普遍联系的,任何事物和个体都直接或间接相互依赖,在时空长河中共同发展。在面向对象的世界中,更是如此,类与类之间的依赖,关联关系,模块(亦或是分层架构中的层)之间的耦合关系,都是我们在软件开发实践中,时刻在处理着的联系。
在软件开发中,我们一直尝试弱化这种联系,以便让软件程序更健壮,更灵活,便于维护和升级。从面向过程遍程中提倡方法的分离(过程分离),到面向对象开发中的减少类依赖,层间低耦合,层内高类聚,无一不体现了这一点。正因为在面向对象的世界中,我们更容易处理类(对象),模块(或层)之间的关系,所以面向对象开发才成为了更受欢迎的开发方式。
为了处理代码中的依赖关系,依赖注入框架应运而生。在 Java 的世界中,开源的依赖注入框架更是不胜枚举,如 PicoContainer ,google-guice,Butterfly Container 。。。。。而正如上一篇文章中提到的,Dagger2 是第一个使用生成代码的方式实现依赖注入的框架,它完全依赖 Java 的注解处理和编译时检查来分析和验证依赖,在代码执行效率上拥有无可比拟的优势,而大部分的依赖注入框架都有依赖 XML、运行期验证依赖或者影响应用启动速度的问题,所以它依然是我们今天要介绍的主角。
关于 Dagger2,已在上一篇文章中详细介绍,在 Android 开发中,我们可以使用 ButterKnife 来进行控件注入(但是在子 module 中使用会有问题),使用 Dagger2 来进行依赖注入。下面我们来看看在 Android 开发中,如何使用 Dagger2 来处理依赖关系。
使用依赖注入是为了降低类之间的依赖关系,从而达到被依赖的类(被依赖者)可以相对独立的变化而不影响其使用者(依赖者),同时在开发和测试过程中,也方便使用 mocking object 替代原有的被依赖类型,适应不同的开发环境和测试。如何处理好关系,关键在于划分二字,我们通常所说的责任、功能的划分,层、模块、类、方法的划分,都是为了更好的处理各种各样的关系。在 Dagger2 的使用中,我们依然需要良好划分它的各个组件以便更好的使用它。
下图是 Android 中推荐的组件级别划分,不熟悉 Component,Provide 和 Module 的小伙伴请看上一篇文章。
Application component/module 一般提供和应用相同生命周期的依赖,所以通常和 @Singleton 关联,并暴露必要的依赖以便 component dependency 使用(通常 application component 会最为父组件或者 component dependency 中的 dependency)。
Activity component/module 一般提供和 Activity 相同生命周期的依赖,通常和 @PerActivity 关联,并且一般是 SubComponent 或者 component dependency。使用 Activity component 有以下几个好处:
可在 Activity 中注入字段(暴露 inject 方法在 Activity 中调用)。
在 Activity 基类中注入和使用单例对象
全局对象图不用涉及只在 Activity 中用到的依赖。
User component/module 用于提供 User 相关的依赖,一般也和 @PerActivity 关联,通常会扩展 Activity Component,用于注入 Fragment。
通常我们会建立一个包含很多接口的主 Component,如果希望有多个不需要一直保留在内存中的 components(例如绑定到 Activity、Fragment 甚至用户会话期的 components),我们可以创建 Dependent component。使用 Dependent components 需要注意这些方面:
1. 两个 Dependent components 不能共享相同的 Scope。
2. 虽然 Dagger2 有创建 Scope 实例的能力,你依然有责任根据场景创建和删除(实例)应用。
3. 创建 Dependent components 的时候,父 component 需要明确暴露下游组件需要的依赖对象。
Dependent components 和 SubComponents 的差别在于:
注意
Dagger2 区别于其他诸如框架的主要优势在于它严格依靠生成代码实现(而非反射),这意味着它可以适用于 Android。当然,在 Android 应用中使用 Dagger 的时候仍然有一些需要考虑的地方。
因为 Android 应用采用 Java 语言来开发,从风格来说会非常不同。这种典型的区别是为了适应移动平台独特的表现考虑。
但是与其他的 Java 代码相反的是,很多模式在 Android 上都不适用,甚至《高效 Java 开发》中的大部分建议都被认为不适合 Android。
为了同时达到代码惯用(符合语言习惯)和轻便的目标,Dagger 依靠 ProGuard 来处理编译的字节码,这允许 Dagger 生成在 Server 和 Android 上看起来都很自然的代码(当使用不同的工具链来生成在这两个环境中都能高效执行的代码时)。更多的是,Dagger 有明确的目标来确保生成的代码和 ProGuard 的优化兼容一致。
当然不是所有的问题都能以这种方式定位,但是它确实是提供 Android 特定兼容的主要机制。
Dagger 假设 Android 开发者会使用 ProGuard。
我们以 AndroidStudio 作为 IDE,以下简称 AS。AS 默认不会把 Dagger2 生成的代码视作合法的 classes,我们可以通过添加 android-apt 插件将这些文件加入到 IDE 的 class path。我们可以在 Project 的 build.gradle 中添加工程依赖:
- dependencies {
- // other classpath definitions hereclasspath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
- }
然后在用到 Dagger2 的 Module 中(如 app)应用 apt 插件:
- // add after applying plugin: 'com.android.application' apply plugin: 'com.neenbedankt.android-apt'
同时添加如下 Module 依赖:
- dependencies {
- // apt command comes from the android-apt pluginapt 'com.google.dagger:dagger-compiler:2.2'
- compile 'com.google.dagger:dagger:2.2'
- provided 'javax.annotation:jsr250-api:1.0'
- }
Provided 关键词引用的依赖只会在编译时需要,apt 关键词是 android-apt 插件提供的,用于注解处理。
Coding
下面是一个简单的关于宠物的 Demo,仅用于说明 Dagger2 的使用。
一般情况下,在 Dagger2 中,我们直接使用到的是 Component,所以我们首先想到的是创建 Component,按照上文的建议和一般的开发习惯,我们会先创建 Application 级别的 Component,用于提供全局的服务或资源。
- public interface AppComponent {
- /**
- * Provide your application level handler instance.
- *
- * @return
- */
- Handler getHandler();
- /**
- * Provide your global http requester instance.
- *
- * @return
- */
- IHttpRequester getHttpRequester();
- /**
- * Provide your global storage service.
- *
- * @return
- */
- IStorage getStorage();
- }
我们声明了 AppComponent,大家可以发现它是一个接口文件,我们在里面我们提供了全局的 Handler、HttpRequester 和 Storage 服务,当然在实际工程中你可以提供更多的服务。
既然声明了接口,那必然需要有它的实现,但是 Dagger2 会为我们做这件事,我们需要做的是使用 Module 来为 Component 接口声明的方法提供实际的依赖。
- /**
- * Created by Irwin on 2016/5/16.
- */
- @Module
- public class AppModule {
- @Provides
- @Singleton
- public static Handler provideHandler() {
- return new Handler();
- }
- @Provides
- @Singleton
- public static IHttpRequester provideHttpProvider() {
- return new DefaultHttpRequester();
- }
- @Provides
- @Singleton
- public static IStorage provideStorage() {
- return new SqliteStorage();
- }
- }
我们实现一个 AppModule 来为 AppComponent 提供依赖,其中提供了实际的 Handler,HttpRequester 和 Storage。因为都是全局单例,所有都使用了 Singleon 关键字和 static 关键字。然后我们需要通过注解把 AppComponent 和 AppModule 关联起来,如下:
- /**
- * Created by Irwin on 2016/5/16.
- */
- @Component(modules = AppModule.class)
- @Singleton
- public interface AppComponent {
- ......
- }
完成后我们可以编译代码,如果没有出错,可以在 Build 目录下找到 Dagger2 为我们生成的 Component 接口的实现代码。
接下来,我们在 Application 中初始化 Component:
- public classMyApplicationextends Application {
- private AppComponent mAppComponent;
- privateString TAG = "ApplicationInfo";
- @Override
- public void onCreate() {
- super.onCreate();
- mAppComponent = DaggerAppComponent.create();
- }
- public AppComponent getAppComponent() {
- return mAppComponent;
- }
- }
为了增加趣味性,我们这个 Demo 是简单展示宠物的,所以接下来我们添加一个宠物类。
- /**
- * Created by Irwin on 2016/5/16.
- */
- public interface IPet {
- public int getPortrait();
- public String getType();
- public String getName();
- public String getStatus();
- public void enjoy(TextView view);
- }
- /**
- * Created by Irwin on 2016/5/16.
- */
- public abstract classAbsPetimplements IPet {
- private intmIndex = -1;
- private String mStatus;
- private int mPortrait;
- @Inject
- Handler mHandler;
- @Inject
- List mStatusList;
- @Inject
- protected String mName;
- @Inject
- @Descript("Yell")
- protected String mYell;
- publicAbsPet(int portrait) {
- mPortrait = portrait;
- }
- @Override
- public String getName() {
- return mName;
- }
- @Override
- public int getPortrait() {
- return mPortrait;
- }
- public Handler getHandler() {
- return mHandler;
- }
- publicList getStatusList() {
- return mStatusList;
- }
- @Override
- public String getStatus() {
- if(mStatus ==null) {
- mStatus = mYell;
- }
- return mStatus;
- }
- @Override
- public voidenjoy(final TextView view) {
- if(mIndex == -1) {
- view.setText(mYell);
- }
- Random random =new Random(System.currentTimeMillis());
- intseconds = random.nextInt(5);
- if(seconds < 1) {
- seconds = 1;
- }
- getHandler().postDelayed(new Runnable() {
- @Override
- public void run() {
- booleanhasNext = nextStatus();
- if (hasNext) {
- enjoy(view);
- }
- view.setText(getStatus() + (hasNext ? " Ing" : " ..."));
- }
- }, seconds * 1000);
- }
- public boolean nextStatus() {
- if(++mIndex < getStatusList().size()) {
- mStatus = getStatusList().get(mIndex);
- return true;
- }
- mIndex = -1;
- return false;
- }
- }
然后分别实现两只宠物,Dog 和 Cat,这是我们具体要依赖的对象,下面是 Dog 的代码。
- public classDogextends AbsPet {
- private String mType;
- @Inject
- publicDog(int portrait) {
- super(portrait);
- this.mType = "汪星人";
- }
- @Override
- public String getType() {
- return mType;
- }
- }
注意构造函数上的 @Inject 注解,它表明 Dagger2 可以自动 new 这个类的实例。
接下来,我们添加一个 Activity 来显示宠物,
- public classMainActivityextends AppCompatActivity {
- private static finalString TAG = "Info";
- private GifImageView mPetView;
- private TextView mTV_Info;
- private TextView mTV_Status;
- @Inject
- IPet mPet;
- ......
- }
如代码中所示,我们将 mPet 字段添加 @Inject 注解,通过依赖注入的方式提供值。
需要注入字段的类也需要有一个 Component 来实现注入,所以我们为此 Activity 添加一个 Component,在此之前我们先添加一个 Module 来为 Component 提供依赖,下面是提供 Dog 依赖的 Module
- @Module(includes = DogStatusModule.class)
- public class DogModule {
- @Provides
- @Activity
- public IPet provideDog(Dog dog) {
- return dog;
- }
- @Provides
- publicList provideStatus(Set set) {
- return newArrayList<>(set);
- }
- @Provides
- public String provideName() {
- return"二汪";
- }
- @Provides
- public int providePortrait() {
- return R.drawable.dog2;
- }
- @Provides
- @Descript("Yell")
- public String provideYell()
- {
- return"汪汪";
- }
- }
其中我们为 DogModule 提供了它所需要的状态 Module DogStatusModule。
接下来添加 Activity 的 Component 并关联 DogModule,
- @Component(dependencies = AppComponent.class, modules = DogModule.class)
- @Activity
- public interface PetActivityComponent {
- public void inject(MainActivity activity);
- public IPet getPet();
- }
完成并编译后,可以在 Build 目录下找到 Dagger2 为我们生成的 Component 接口的实现代码。接下来我们就可以在 Activity 中简单的使用:
- public classMainActivityextends AppCompatActivity {
- private static finalString TAG = "Info";
- private GifImageView mPetView;
- private TextView mTV_Info;
- private TextView mTV_Status;
- @Inject
- IPet mPet;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
- setSupportActionBar(toolbar);
- mPetView = (GifImageView) findViewById(R.id.IMV_Pet);
- mTV_Info = (TextView) findViewById(R.id.TV_Info);
- mTV_Status = (TextView) findViewById(R.id.TV_Status);
- PetActivityComponent component = DaggerPetActivityComponent.builder()
- .appComponent(((MyApplication) getApplicationContext()).getAppComponent())
- .build();
- //You must call inject first so that Dagger2 will inject fields for you, otherwise you'll get NullPointer exceptions.component.inject(this);
- FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
- fab.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- playWith(mPet);
- getHttpRequester().request(0, "http://www.baidu.com", "search=dagger2",newIResultHandler() {
- @Override
- public void onSuccess(String o) {
- Log.i(TAG, "Response: " + o);
- }
- @Override
- public void onFail(Throwable error) {
- }
- });
- }
- });
- bindPet(mPet);
- }
- public void bindPet(IPet pet) {
- mPetView.setImageResource(pet.getPortrait());
- mTV_Info.setText(getString(R.string.PetInfo, mPet.getType(), mPet.getName()));
- mTV_Status.setText(pet.getStatus());
- }
- public void playWith(IPet pet) {
- pet.enjoy(mTV_Status);
- }
- public IHttpRequester getHttpRequester() {
- return ((MyApplication) getApplicationContext()).getAppComponent().getHttpRequester();
- }
- public IStorage getStorage() {
- return ((MyApplication) getApplicationContext()).getAppComponent().getStorage();
- }
- }
在调用 Component 的 Inject 后,Dagger2 就为 mPet 字段注入了值,然后我们就可以放心的使用 mPet 字段了, 同时,我们还可以使用 AppComponent 中提供的所有服务或资源,如
- ((MyApplication) getApplicationContext()).getAppComponent().getHttpRequester().request(...);
至此,代码编写完成。当然,我们还添加了一些额外的代码,如 HttpRequester,不同的 Storage 实现,CatModule,全局都是通过 Dagger2 使用这些实现,所以你可以随意改变自己的实现或者替换依赖,而不影响上层代码的使用,可以使你的开发和测试变得更简单哦!
运行工程,就可以看到效果了,
此时,如果我们希望使用依赖对象的不同实现,简单的替换 Component 依赖的 Module, 或者直接修改 Module 中相应提供依赖的地方即可。下面我们将 Activity 中的关联的 DogModule 替换为 CatModule
- @Component(dependencies = AppComponent.class, modules = CatModule.class)
- @Activity
- public interface PetActivityComponent {
- ...
- }
替换后效果:
在实际的开发环境中,你可以随意替换不同的存储,网络请求实现来满足不同的场景和需求,是不是很方便:D
Android-apt 插件帮你把 annotation processors 和 AndroidStudio 结合起来使用,它主要有两个功能:
注:
Component 像连接 @Module 和 @Inject 的桥梁,将所有的东西联系起来。
Component 实现主要是通过生成的 builder 来实例化。使用它的 builder() 方法来获取一个 builder 实例。如果一个嵌套的 @Component.Builder 类型已经存在,builder() 方法会返回它的一个生成实现;如果嵌套的 @Component.Builder 不存在,返回的 builder 会提供设置每一个需要的 modules 和 component 依赖的方法(方法名是以小驼峰命名法命名的 module 和 component 类型名)。没有可见默认构造函数的 module 和 component 依赖必须明确设置,但是有默认或无参构造函数的可被 component 访问的 module 可以省略。下面是使用 component builder 的例子:(From:http://google.github.io/dagger/api/latest/dagger/Component.html)
- public static void main(String[] args) {
- OtherComponent otherComponent = ...;
- MyComponent component = DaggerMyComponent.builder()
- // required because component dependencies must be set
- .otherComponent(otherComponent)
- // required because FlagsModule has constructor parameters
- .flagsModule(new FlagsModule(args))
- // may be elided because a no-args constructor is visible
- .myApplicationModule(new MyApplicationModule())
- .build();
- }
在 component 只有无参 (构造函数)modules,没有 component 依赖的情况下,component 实现也会有一个工厂方法 create(),SomeComponent.create() 和 SomeComponent.builder().build()是等同的。
@Singleton 用来标记单例,但是是在 component 的生命周期而非应用的生命周期中的单例。
如果 T 被注解为单例(在 T 的类声明或在 provideT 上加 @Singleton,同时在 Component 上加 @Singleton), 引入 T 的 component 中的 TProvider 会使用如下方式生成 (其他的 Scope 也应如此):
TProvider= ScopedProvider.create(CoffeeMaker_Factory.create(…));
如果 T 未被标记为单例,TProvider 会这样生成:
- TProvider=T_Factory.create(…);
用 @Scope 注解组件就可以加上范围限定。在这种情况下,组件实现会保存对所有加上范围限定的类实例的引用以便重用。有 Scope 的带有 @Provides 方法的模块只能被拥有相同范围限定的组件引用。
每一个 Component 都可以用 Scope 注解来添加 Scope。Component 实现确保每个实例中每个 Scope 绑定只有一个规定。如果 Component 声明了一个 Scope,它就只能包括 Unscope 或者该 Scope 的绑定。例如:
- @Singleton @Component
- interface MyApplicationComponent {
- // this component can only inject types using unscoped or @Singleton bindings
- }
为了让 Scope 表现正确,何时初始化 component 实例是调用者的责任。比如说,一个单例组件,在一个应用中只应该被实例化一次,而一个 RequestScoped 的组件应该请求一次,实例化一次。因为组件是自维护(Self-contained)的实现,退出一个 scope 跟丢掉对 component 实例的所有引用一样简单。
Lazy injection 会等到第一次调用 Lazy<T>.get() 的时候创建 T 的实例并缓存。
Provider<T> 提供可以返回不同实例的功能,每一次调用 Provider<T>.get() 都会调用绑定逻辑,生成新的实例。
Qualifier 用于单靠类型无法识别依赖的时候(即多个依赖(可注入字段)类型相同时),使用 Qualifier 注解,Dagger 会同时通过类型和 qualifier 来识别依赖。
多重绑定可以把多个对象绑定到一个 Set 或 Map,通过 @Provides(type=SET|MAP)方法提供对象到集合中,对于 Map 的 Key,分为两种方式:一种是在运行时知道 Key 的情况,需要通过注解来指定 Key(支持复杂的 Mapkey);一种是在运行时才知道 Key 的情况,这时可以通过提供 Set<Map.Entry>,然后组装成 Map。
使用子组件可以把应用的对象图划分成子图,可以压缩应用的各个部分,或者在一个组件上使用多个范围限定。组件提供子组件有两种方式,一种是在组件中提供工厂方法来返回子组件或子组件的 Builder;第二种是注入子组件的 builder。
父子组件会共享相同的 Module 实例,父组件已经引用的 Module 类,子组件不需要再传入(包括工厂方法和 Builder 方法)。
Producers 引入了新的注解 @ProducerModule, @Produces, 和 @ProductionComponent,分别对应 @Module,@Provides, 和 @Component。我们把 @ProducerModule 注解的类视为 producer modules,@Produces 注解的方法视为 producer methods,@ProductionComponent 注解的接口视为 producer graphs(类似于 modules, provider methods, 和 object graphs)。
和原始 Dagger 最关键的不同在于 producer 方法会返回 ListenableFuture<T>,并且后面的 producer 会依赖于这个未封装的 T,当 future 可用的时候,框架会计划执行后续的方法。
通过绑定 @Production Executor 来指定一个线程池,每一个 Producer 的方法都会在这个线程池中被计划执行。
来源: http://www.cnblogs.com/oxgen/p/7116167.html