- 原文地址: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、前端、後端、產品、設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃。