- 原文地址:Dependency Injection with Dagger 2
- 原文做者:CodePath
- 译文出自:掘金翻译计划
- 译者: tanglie1993
- 校对者:mnikn, Zhiw
不少 Android 应用依赖于一些含有其它依赖的对象。例如,一个 Twitter API 客户端可能须要经过 Retrofit 之类的网络库来构建。要使用这个库,你可能还须要添加 Gson 这样的解析库。另外,实现认证或缓存的库可能须要使用 shared preferences 或其它通用存储方式。这就须要先把它们实例化,并建立一个隐含的依赖链。html
若是你不熟悉依赖注入,看看这个短视频。前端
Dagger 2 为你解析这些依赖,并生成把它们绑定在一块儿的代码。也有不少其它的 Java 依赖注入框架,但它们中大多数是有缺陷的,好比依赖 XML,须要在运行时验证依赖,或者在起始时形成性能负担。 Dagger 2 纯粹依赖于 Java 注解解析器以及编译时检查来分析并验证依赖。它被认为是目前最高效的依赖注入框架之一。java
这是使用 Dagger 2 的一系列其它优点:react
MyTwitterApiClient
或 SharedPreferences
的单例,就能够用一个简单的 @Inject
标注来声明域:public class MainActivity extends Activity {
@Inject MyTwitterApiClient mTwitterApiClient;
@Inject SharedPreferences sharedPreferences;
public void onCreate(Bundle savedInstance) {
// assign singleton instances to fields
InjectorClass.inject(this);
}复制代码
容易配置复杂的依赖关系。 对象建立是有隐含顺序的。Dagger 2 遍历依赖关系图,而且生成易于理解和追踪的代码。并且,它能够节约大量的样板代码,使你再也不须要手写,手动获取引用并把它们传递给其余对象做为依赖。它也简化了重构,由于你能够聚焦于构建模块自己,而不是它们被建立的顺序。android
更简单的单元和集成测试 由于依赖图是为咱们建立的,咱们能够轻易换出用于建立网络响应的模块,并模拟这种行为。ios
实例范围 你不只能够轻易地管理持续整个应用生命周期的实例,也能够利用 Dagger 2 来定义生命周期更短(好比和一个用户 session 或 Activity 生命周期相绑定)的实例。 git
默认的 Android Studio 不把生成的 Dagger 2 代码视做合法的类,由于它们一般并不被加入 source 路径。但引入 android-apt
插件后,它会把这些文件加入 IDE classpath,从而提供更好的可见性。github
确保升级 到最新的 Gradle 版本以使用最新的 annotationProcessor
语法: 后端
dependencies {
// apt command comes from the android-apt plugin
compile "com.google.dagger:dagger:2.9"
annotationProcessor "com.google.dagger:dagger-compiler:2.9"
provided 'javax.annotation:jsr250-api:1.0'
}复制代码
注意 provided
关键词是指只在编译时须要的依赖。Dagger 编译器生成了用于生成依赖图的类,而这个依赖图是在你的源代码中定义的。这些类在编译过程当中被添加到你的IDE classpath。annotationProcessor
关键字能够被 Android Gradle 插件理解。它不把这些类添加到 classpath 中,而只是把它们用于处理注解。这能够避免不当心引用它们。api
最简单的例子是用 Dagger 2 集中管理全部的单例。假设你不用任何依赖注入框架,在你的 Twitter 客户端中写下相似这些的东西:
OkHttpClient client = new OkHttpClient();
// Enable caching for OkHttp
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(getApplication().getCacheDir(), cacheSize);
client.setCache(cache);
// Used for caching authentication tokens
SharedPreferences sharedPrefeences = PreferenceManager.getDefaultSharedPreferences(this);
// Instantiate Gson
Gson gson = new GsonBuilder().create();
GsonConverterFactory converterFactory = GsonConverterFactory.create(gson);
// Build Retrofit
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(converterFactory)
.client(client) // custom client
.build();复制代码
你须要经过建立 Dagger 2 模块定义哪些对象应该做为依赖链的一部分。例如,假设咱们想要建立一个 Retrofit
单例,使它绑定到应用生命周期,对全部的 Activity 和 Fragment 均可用,咱们首先须要使 Dagger 意识到他能够提供 Retrofit
的实例。
由于须要设置缓存,咱们须要一个 Application context。咱们的第一个 Dagger 模块,AppModule.java
,被用于提供这个依赖。咱们将定义一个 @Provides
注解,标注带有 Application
的构造方法:
@Module
public class AppModule {
Application mApplication;
public AppModule(Application application) {
mApplication = application;
}
@Provides
@Singleton
Application providesApplication() {
return mApplication;
}
}复制代码
咱们建立了一个名为 NetModule.java
的类,并用 @Module
来通知 Dagger,在这里查找提供实例的方法。
返回实例的方法也应当用 @Provides
标注。Singleton
标注通知 Dagger 编译器,实例在应用中只应被建立一次。在下面的例子中,咱们把 SharedPreferences
, Gson
, Cache
, OkHttpClient
, 和 Retrofit
设置为在依赖列表中可用的类型。
@Module
public class NetModule {
String mBaseUrl;
// Constructor needs one parameter to instantiate.
public NetModule(String baseUrl) {
this.mBaseUrl = baseUrl;
}
// Dagger will only look for methods annotated with @Provides
@Provides
@Singleton
// Application reference must come from AppModule.class
SharedPreferences providesSharedPreferences(Application application) {
return PreferenceManager.getDefaultSharedPreferences(application);
}
@Provides
@Singleton
Cache provideOkHttpCache(Application application) {
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(application.getCacheDir(), cacheSize);
return cache;
}
@Provides
@Singleton
Gson provideGson() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
return gsonBuilder.create();
}
@Provides
@Singleton
OkHttpClient provideOkHttpClient(Cache cache) {
OkHttpClient client = new OkHttpClient();
client.setCache(cache);
return client;
}
@Provides
@Singleton
Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(mBaseUrl)
.client(okHttpClient)
.build();
return retrofit;
}
}复制代码
注意,方法名称(好比 provideGson()
, provideRetrofit()
等)是不要紧的,能够任意设置。@Provides
被用于把这个实例化和其它同类的模块联系起来。@Singleton
标注用于通知 Dagger,它在整个应用的生命周期中只被初始化一次。
一个 Retrofit
实例依赖于一个 Gson
和一个 OkHttpClient
实例,因此咱们能够在同一个类中定义两个方法,来提供这两种实例。@Provides
标注和方法中的这两个参数将使 Dagger 意识到,构建一个 Retrofit
实例 须要依赖 Gson
和 OkHttpClient
。
Dagger 使你的 activity, fragment, 或 service 中的域能够经过 @Inject
注解和调用 inject()
方法被赋值。调用 inject()
将会使得 Dagger 2 在依赖图中寻找合适类型的单例。若是找到了一个,它就把引用赋值给对应的域。例如,在下面的例子中,它会尝试找到一个返回MyTwitterApiClient
和SharedPreferences
类型的 provider:
public class MainActivity extends Activity {
@Inject MyTwitterApiClient mTwitterApiClient;
@Inject SharedPreferences sharedPreferences;
public void onCreate(Bundle savedInstance) {
// assign singleton instances to fields
InjectorClass.inject(this);
}复制代码
Dagger 2 中使用的注入者类被称为 component。它把先前定义的单例的引用传给 activity, service 或 fragment。咱们须要用 @Component
来注解这个类。注意,须要被注入的 activity, service 或 fragment 须要在这里使用 inject()
方法注入:
@Singleton
@Component(modules={AppModule.class, NetModule.class})
public interface NetComponent {
void inject(MainActivity activity);
// void inject(MyFragment fragment);
// void inject(MyService service);
}复制代码
注意 基类不能被做为注入的目标。Dagger 2 依赖于强类型的类,因此你必须指定哪些类会被定义。(有一些建议 帮助你绕开这个问题,但这样作的话,代码可能会变得更复杂,更难以追踪。)
Dagger 2 的一个重要特色是它会为标注 @Component
的接口生成类的代码。你可使用带有 Dagger
(好比 DaggerTwitterApiComponent.java
) 前缀的类来为依赖图提供实例,并用它来完成用 @Inject
注解的域的注入。 参见设置。
咱们应该在一个 Application
类中完成这些工做,由于这些实例应当在 application 的整个周期中只被声明一次:
public class MyApp extends Application {
private NetComponent mNetComponent;
@Override
public void onCreate() {
super.onCreate();
// Dagger%COMPONENT_NAME%
mNetComponent = DaggerNetComponent.builder()
// list of modules that are part of this component need to be created here too
.appModule(new AppModule(this)) // This also corresponds to the name of your module: %component_name%Module
.netModule(new NetModule("https://api.github.com"))
.build();
// If a Dagger 2 component does not have any constructor arguments for any of its modules,
// then we can use .create() as a shortcut instead:
// mNetComponent = com.codepath.dagger.components.DaggerNetComponent.create();
}
public NetComponent getNetComponent() {
return mNetComponent;
}
}复制代码
若是你不能引用 Dagger 组件,rebuild 整个项目 (在 Android Studio 中,选择 Build > Rebuild Project)。
由于咱们在覆盖默认的 Application
类,咱们一样须要修改应用的 name
以启动 MyApp
。这样,你的 application 将会使用这个 application 类来处理最初的实例化。
<application android:allowBackup="true" android:name=".MyApp">复制代码
在咱们的 activity 中,咱们只须要获取这些 components 的引用,并调用 inject()
。
public class MyActivity extends Activity {
@Inject OkHttpClient mOkHttpClient;
@Inject SharedPreferences sharedPreferences;
public void onCreate(Bundle savedInstance) {
// assign singleton instances to fields
// We need to cast to `MyApp` in order to get the right method
((MyApp) getApplication()).getNetComponent().inject(this);
}复制代码
若是咱们须要同一类型的两个不一样对象,咱们可使用 @Named
限定词注解。 你须要定义你如何提供单例 (用 @Provides
注解),以及你从哪里注入它们(用 @Inject
注解):
@Provides @Named("cached")
@Singleton
OkHttpClient provideOkHttpClient(Cache cache) {
OkHttpClient client = new OkHttpClient();
client.setCache(cache);
return client;
}
@Provides @Named("non_cached") @Singleton
OkHttpClient provideOkHttpClient() {
OkHttpClient client = new OkHttpClient();
return client;
}复制代码
注入一样须要这些 named 注解:
@Inject @Named("cached") OkHttpClient client;
@Inject @Named("non_cached") OkHttpClient client2;复制代码
@Named
是一个被 Dagger 预先定义的限定语,但你也能够建立你本身的限定语注解:
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface DefaultPreferences {
}复制代码
在 Dagger 2 中,你能够经过自定义做用域来定义组件应当如何封装。例如,你能够建立一个只持续 activity 或 fragment 整个生命周期的做用域。你也能够建立一个对应一个用户认证 session 的做用域。 你能够定义任意数量的自定义做用域注解,只要你把它们声明为 public @interface
:
@Scope
@Documented
@Retention(value=RetentionPolicy.RUNTIME)
public @interface MyActivityScope
{
}复制代码
虽然 Dagger 2 在运行时不依赖注解,把 RetentionPolicy
设置为 RUNTIME 对于未来检查你的 module 将是颇有用的。
利用做用域,咱们能够建立 依赖组件 或 子组件。上面的例子中,咱们使用了 @Singleton
注解,它持续了整个应用的生命周期。咱们也依赖了一个主要的 Dagger 组件。
若是咱们不须要组件老是存在于内存中(例如,和 activity 或 fragment 生命周期绑定,或在用户登陆时绑定),咱们能够建立依赖组件和子组件。它们各自提供了一种封装你的代码的方式。咱们将在下一节中看到如何使用它们。
在使用这种方法时,有若干问题要注意:
// parent component
@Singleton
@Component(modules={AppModule.class, NetModule.class})
public interface NetComponent {
// remove injection methods if downstream modules will perform injection
// downstream components need these exposed
// the method name does not matter, only the return type
Retrofit retrofit();
OkHttpClient okHttpClient();
SharedPreferences sharedPreferences();
}复制代码
若是你忘记加入这一行,你将有可能看到一个关于注入目标缺失的错误。就像 private/public 变量的管理方式同样,使用一个 parent 组件能够更显式地控制,也可保证更好的封装。使用子组件使得依赖注入更容易管理,但封装得更差。
两个依赖组件不能使用同一个做用域 例如,两个组件不能都用 @Singleton
注解设置定义域。这个限制的缘由在 这里 有所说明。依赖组件须要定义它们本身的做用域。
Dagger 2 一样容许使用带做用域的实例。你须要负责在合适的时机建立和销毁引用。 Dagger 2 对底层实现一无所知。这个 Stack Overflow 讨论 上有更多的细节。
若是你想要建立一个组件,使它的生命周期和已登陆用户的 session 相绑定,就能够建立 UserScope
接口:
import java.lang.annotation.Retention;
import javax.inject.Scope;
@Scope
public @interface UserScope {
}复制代码
接下来,咱们定义父组件:
@Singleton
@Component(modules={AppModule.class, NetModule.class})
public interface NetComponent {
// downstream components need these exposed with the return type
// method name does not really matter
Retrofit retrofit();
}复制代码
接下来定义子组件:
@UserScope // using the previously defined scope, note that @Singleton will not work
@Component(dependencies = NetComponent.class, modules = GitHubModule.class)
public interface GitHubComponent {
void inject(MainActivity activity);
}复制代码
假定 Github 模块只是把 API 接口返回给 Github API:
@Module
public class GitHubModule {
public interface GitHubApiInterface {
@GET("/org/{orgName}/repos")
Call<ArrayList<Repository>> getRepository(@Path("orgName") String orgName);
}
@Provides
@UserScope // needs to be consistent with the component scope
public GitHubApiInterface providesGitHubInterface(Retrofit retrofit) {
return retrofit.create(GitHubApiInterface.class);
}
}复制代码
为了让这个 GitHubModule.java
得到对 Retrofit
实例的引用,咱们须要在上游组件中显式定义它们。若是下游模块会执行注入,它们也应当被从上游组件中移除:
@Singleton
@Component(modules={AppModule.class, NetModule.class})
public interface NetComponent {
// remove injection methods if downstream modules will perform injection
// downstream components need these exposed
Retrofit retrofit();
OkHttpClient okHttpClient();
SharedPreferences sharedPreferences();
}复制代码
最终的步骤是用 GitHubComponent
进行实例化。这一次,咱们须要首先实现 NetComponent
并把它传递给 DaggerGitHubComponent
builder 的构造方法:
NetComponent mNetComponent = DaggerNetComponent.builder()
.appModule(new AppModule(this))
.netModule(new NetModule("https://api.github.com"))
.build();
GitHubComponent gitHubComponent = DaggerGitHubComponent.builder()
.netComponent(mNetComponent)
.gitHubModule(new GitHubModule())
.build();复制代码
示例代码 中有一个实际的例子。
使用子组件是扩展组件对象图的另外一种方式。就像带有依赖的组件同样,子组件有本身的的生命周期,并且在全部对子组件的引用都失效以后,能够被垃圾回收。此外它们做用域的限制也同样。使用这个方式的一个优势是你不须要定义全部的下游组件。
另外一个主要的不一样是,子组件须要在父组件中声明。
这是为一个 activity 使用子组件的例子。咱们用自定义做用域和 @Subcomponent
注解这个类:
@MyActivityScope
@Subcomponent(modules={ MyActivityModule.class })
public interface MyActivitySubComponent {
@Named("my_list") ArrayAdapter myListAdapter();
}复制代码
被使用的模块在下面定义:
@Module
public class MyActivityModule {
private final MyActivity activity;
// must be instantiated with an activity
public MyActivityModule(MyActivity activity) { this.activity = activity; }
@Provides @MyActivityScope @Named("my_list")
public ArrayAdapter providesMyListAdapter() {
return new ArrayAdapter<String>(activity, android.R.layout.my_list);
}
...
}复制代码
最后,在父组件中,咱们将定义一个工厂方法,它以这个组件的类型做为返回值,并定义初始化所需的依赖:
@Singleton
@Component(modules={ ... })
public interface MyApplicationComponent {
// injection targets here
// factory method to instantiate the subcomponent defined here (passing in the module instance)
MyActivitySubComponent newMyActivitySubcomponent(MyActivityModule activityModule);
}复制代码
在上面的例子中,一个子组件的新实例将在每次 newMyActivitySubcomponent()
调用时被建立。把这个子模块注入一个 activity 中:
public class MyActivity extends Activity {
@Inject ArrayAdapter arrayAdapter;
public void onCreate(Bundle savedInstance) {
// assign singleton instances to fields
// We need to cast to `MyApp` in order to get the right method
((MyApp) getApplication()).getApplicationComponent())
.newMyActivitySubcomponent(new MyActivityModule(this))
.inject(this);
}
}复制代码
从 v2.7 版本起可用
子组件 builder 使建立子组件的类和子组件的父类解耦。这是经过移除父组件中的子组件工厂方法实现的。
@MyActivityScope
@Subcomponent(modules={ MyActivityModule.class })
public interface MyActivitySubComponent {
...
@Subcomponent.Builder
interface Builder extends SubcomponentBuilder<MyActivitySubComponent> {
Builder activityModule(MyActivityModule module);
}
}
public interface SubcomponentBuilder<V> {
V build();
}复制代码
子组件是在子组件接口内部的接口中声明的。它必须含有一个 build()
方法,其返回值和子组件相匹配。用这个方法声明一个基接口是很方便的,就像上面的SubcomponentBuilder
同样。这个新的 builder 必须被加入父组件的图中,而这是用一个 "binder" 模块和一个 "subcomponents" 参数实现的:
@Module(subcomponents={ MyActivitySubComponent.class })
public abstract class ApplicationBinders {
// Provide the builder to be included in a mapping used for creating the builders.
@Binds @IntoMap @SubcomponentKey(MyActivitySubComponent.Builder.class)
public abstract SubcomponentBuilder myActivity(MyActivitySubComponent.Builder impl);
}
@Component(modules={..., ApplicationBinders.class})
public interface ApplicationComponent {
// Returns a map with all the builders mapped by their class.
Map<Class<?>, Provider<SubcomponentBuilder>> subcomponentBuilders();
}
// Needed only to to create the above mapping
@MapKey @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME)
public @interface SubcomponentKey {
Class<?> value();
}复制代码
一旦 builder 在出如今组件图中,activity 就能够用它来建立子组件:
public class MyActivity extends Activity {
@Inject ArrayAdapter arrayAdapter;
public void onCreate(Bundle savedInstance) {
// assign singleton instances to fields
// We need to cast to `MyApp` in order to get the right method
MyActivitySubcomponent.Builder builder = (MyActivitySubcomponent.Builder)
((MyApp) getApplication()).getApplicationComponent())
.subcomponentBuilders()
.get(MyActivitySubcomponent.Builder.class)
.get();
builder.activityModule(new MyActivityModule(this)).build().inject(this);
}
}复制代码
Dagger 2 应当在没有 ProGuard 时能够直接使用,可是若是你看到了 library class dagger.producers.monitoring.internal.Monitors$1 extends or implements program class javax.inject.Provider
,你须要确认你的 gradle 配置使用了 annotationProcessor
声明,而不是 provided
。
MemberInjector
和 actual and former argument lists different in length
错误。确保你 clean 过整个项目,而且把全部版本升级到和 Dagger 2 相匹配的版本。掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划。