詳解Dagger2
原文鏈接 : Tasting Dagger 2 on Android
原文作者 : Fernando Cejas
譯者 : xianjiajun
校對者: chaossss
為什么使用依賴注入
首先我們需要知道,人們在很長的一段時間里都是利用控制反轉原則規定:應用程序的流程取決于在程序運行時對象圖的建立。通過抽象定義的對象交互可以實現這樣的動態流程。而使用依賴注入技術或者服務定位器便可以完成運行時綁定。
使用依賴注入可以帶來以下好處:
依賴的注入和配置獨立于組件之外。
因為對象是在一個獨立、不耦合的地方初始化,所以當注入抽象方法的時候,我們只需要修改對象的實現方法,而不用大改代碼庫。
依賴可以注入到一個組件中:我們可以注入這些依賴的模擬實現,這樣使得測試更加簡單。
可以看到,能夠管理創建實例的范圍是一件非常棒的事情。按我的觀點,你app中的所有對象或者協作者都不應該知道有關實例創建和生命周期的任何事情,這些都應該由我們的依賴注入框架管理的。
什么是JSR-330?
為了最大程度的提高代碼的復用性、測試性和維護性,java的依賴注入為注入類中的使用定義了一整套注解(和接口)標準。Dagger1和Dagger2(還有Guice)都是基于這套標準,給程序帶來了穩定性和標準的依賴注入方法。
Dagger1
這個版本不是這篇文章的重點,所以我只是簡略地說一下。不管怎樣,Dagger1還是做了很多的貢獻,可以說是如今Android上最流行的依賴注入框架。它是由Square公司受到Guice啟發創建的。
基本特點:
多個注入點:依賴,通過injected
多種綁定方法:依賴,通過provided
多個modules:實現某種功能的綁定集合
多個對象圖: 實現一個范圍的modules集合
Dagger1是在編譯的時候實行綁定,不過也用到了反射機制。但這個反射不是用來實例化對象的,而是用于圖的構成。Dagger會在運行的時候去檢測是否一切都正常工作,所以使用的時候會付出一些代價:偶爾會無效和調試困難。
Dagger2
Dagger2是Dagger1的分支,由谷歌公司接手開發,目前的版本是2.0。Dagger2是受到AutoValue項目的啟發。 剛開始,Dagger2解決問題的基本思想是:利用生成和寫的代碼混合達到看似所有的產生和提供依賴的代碼都是手寫的樣子。
如果我們將Dagger2和1比較,他們兩個在很多方面都非常相似,但也有很重要的區別,如下:
再也沒有使用反射:圖的驗證、配置和預先設置都在編譯的時候執行。
容易調試和可跟蹤:完全具體地調用提供和創建的堆棧
更好的性能:谷歌聲稱他們提高了13%的處理性能
代碼混淆:使用派遣方法,就如同自己寫的代碼一樣
當然所有這些很棒的特點都需要付出一個代價,那就是缺乏靈活性,例如:Dagger2沒用反射所以沒有動態機制。
深入研究
想要了解Dagger2,就必須要知道依賴注入的基礎和這其中的每一個概念:
@Inject: 通常在需要依賴的地方使用這個注解。換句話說,你用它告訴Dagger這個類或者字段需要依賴注入。這樣,Dagger就會構造一個這個類的實例并滿足他們的依賴。
@Module: Modules類里面的方法專門提供依賴,所以我們定義一個類,用@Module注解,這樣Dagger在構造類的實例的時候,就知道從哪里去找到需要的 依賴。modules的一個重要特征是它們設計為分區并組合在一起(比如說,在我們的app中可以有多個組成在一起的modules)。
@Provide: 在modules中,我們定義的方法是用這個注解,以此來告訴Dagger我們想要構造對象并提供這些依賴。
@Component: Components從根本上來說就是一個注入器,也可以說是@Inject和@Module的橋梁,它的主要作用就是連接這兩個部分。 Components可以提供所有定義了的類型的實例,比如:我們必須用@Component注解一個接口然后列出所有的@Modules組成該組件,如 果缺失了任何一塊都會在編譯的時候報錯。所有的組件都可以通過它的modules知道依賴的范圍。
@Scope: Scopes可是非常的有用,Dagger2可以通過自定義注解限定注解作用域。后面會演示一個例子,這是一個非常強大的特點,因為就如前面說的一樣,沒 必要讓每個對象都去了解如何管理他們的實例。在scope的例子中,我們用自定義的@PerActivity注解一個類,所以這個對象存活時間就和 activity的一樣。簡單來說就是我們可以定義所有范圍的粒度(@PerFragment, @PerUser, 等等)。
Qualifier: 當類的類型不足以鑒別一個依賴的時候,我們就可以使用這個注解標示。例如:在Android中,我們會需要不同類型的context,所以我們就可以定義 qualifier注解“@ForApplication”和“@ForActivity”,這樣當注入一個context的時候,我們就可以告訴 Dagger我們想要哪種類型的context。
不廢話上代碼
前面已經講了很多理論了,所以接下來讓我們看看如何使用Dagger2。首先還是要在我們的build.gradle文件中如下配置:
apply plugin: 'com.neenbedankt.android-apt' buildscript { repositories { jcenter() } dependencies { classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4' } } android { ... } dependencies { apt 'com.google.dagger:dagger-compiler:2.0' compile 'com.google.dagger:dagger:2.0' ... }
如上所示,我們添加了編譯和運行庫,還有必不可少的apt插件,沒有這插件,dagger可能不會正常工作,特別是在Android studio中。
例子
幾個月前,我寫了一篇關于如何在Android上實現bob叔叔的清晰架構的文章,強烈建議大家去看一下,看完之后,你將會對我們現在做的事情有更好的理解。言歸正傳,在我以前的方案中,構造和提供大多數對象的依賴的時候,會遇到問題,具體如下(見評注):
@Override void initializePresenter() { // All this dependency initialization could have been avoided by using a // dependency injection framework. But in this case this is used this way for // LEARNING EXAMPLE PURPOSE. ThreadExecutor threadExecutor = JobExecutor.getInstance(); PostExecutionThread postExecutionThread = UIThread.getInstance(); JsonSerializer userCacheSerializer = new JsonSerializer(); UserCache userCache = UserCacheImpl.getInstance(getActivity(), userCacheSerializer, FileManager.getInstance(), threadExecutor); UserDataStoreFactory userDataStoreFactory = new UserDataStoreFactory(this.getContext(), userCache); UserEntityDataMapper userEntityDataMapper = new UserEntityDataMapper(); UserRepository userRepository = UserDataRepository.getInstance(userDataStoreFactory, userEntityDataMapper); GetUserDetailsUseCase getUserDetailsUseCase = new GetUserDetailsUseCaseImpl(userRepository, threadExecutor, postExecutionThread); UserModelDataMapper userModelDataMapper = new UserModelDataMapper(); this.userDetailsPresenter = new UserDetailsPresenter(this, getUserDetailsUseCase, userModelDataMapper); }
可以看出,解決這個問題的辦法是使用依賴注入框架。我們要避免像上面這樣引用代碼:這個類不能涉及對象的創建和依賴的提供。 那我們該怎么做呢,當然是使用Dagger2,我們先看看結構圖:
接下來我們會分解這張圖,并解釋各個部分還有代碼。
Application Component: 生命周期跟Application一樣的組件。可注入到AndroidApplication和BaseActivity中類中。
@Singleton // Constraints this component to one-per-application or unscoped bindings. @Component(modules = ApplicationModule.class) public interface ApplicationComponent { void inject(BaseActivity baseActivity); //Exposed to sub-graphs. Context context(); ThreadExecutor threadExecutor(); PostExecutionThread postExecutionThread(); UserRepository userRepository(); }
我為這個組件使用了@Singleton注解,使其保證唯一性。也許你會問為什么我要將context和其他成員暴露出去。這正是Dagger中 components工作的重要性質:如果你不想把modules的類型暴露出來,那么你就只能顯示地使用它們。在這個例子中,我把這些元素暴露給子圖, 如果你把他們刪掉,編譯的時候就會報錯。
Application Module: module的作用是提供在應用的生命周期中存活的對象。這也是為什么@Provide注解的方法要用@Singleton限定。
@Module public class ApplicationModule { private final AndroidApplication application; public ApplicationModule(AndroidApplication application) { this.application = application; } @Provides @Singleton Context provideApplicationContext() { return this.application; } @Provides @Singleton Navigator provideNavigator() { return new Navigator(); } @Provides @Singleton ThreadExecutor provideThreadExecutor(JobExecutor jobExecutor) { return jobExecutor; } @Provides @Singleton PostExecutionThread providePostExecutionThread(UIThread uiThread) { return uiThread; } @Provides @Singleton UserCache provideUserCache(UserCacheImpl userCache) { return userCache; } @Provides @Singleton UserRepository provideUserRepository(UserDataRepository userDataRepository) { return userDataRepository; } }
Activity Component: 生命周期跟Activity一樣的組件。
@PerActivity @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { //Exposed to sub-graphs. Activity activity(); }
@PerActivity是一個自定義的范圍注解,作用是允許對象被記錄在正確的組件中,當然這些對象的生命周期應該遵循activity的生命周期。這是一個很好的練習,我建議你們都做一下,有以下好處:
注入對象到構造方法需要的activity。
在一個per-activity基礎上的單例使用。
只能在activity中使用使得全局的對象圖保持清晰。
看下代碼:
@Scope @Retention(RUNTIME) public @interface PerActivity {}
Activity Module: 在對象圖中,這個module把activity暴露給相關聯的類。比如在fragment中使用activity的context。
@Module public class ActivityModule { private final Activity activity; public ActivityModule(Activity activity) { this.activity = activity; } @Provides @PerActivity Activity activity() { return this.activity; } }
User Component: 繼承于ActivityComponent的組件,并用@PerActivity注解。我通常會在注入用戶相關的fragment中使用。因為 ActivityModule把activity暴露給圖了,所以在任何需要一個activity的context的時候,Dagger都可以提供注入, 沒必要再在子modules中定義了。
@PerActivity @Component(dependencies = ApplicationComponent.class, modules = {ActivityModule.class, UserModule.class}) public interface UserComponent extends ActivityComponent { void inject(UserListFragment userListFragment); void inject(UserDetailsFragment userDetailsFragment); }
User Module: 提供跟用戶相關的實例。基于我們的例子,它可以提供用戶用例。
@Module public class UserModule { @Provides @PerActivity GetUserListUseCase provideGetUserListUseCase(GetUserListUseCaseImpl getUserListUseCase) { return getUserListUseCase; } @Provides @PerActivity GetUserDetailsUseCase provideGetUserDetailsUseCase(GetUserDetailsUseCaseImpl getUserDetailsUseCase) { return getUserDetailsUseCase; } }
整合到一起
現在我們已經實現了依賴注入圖,但是我該如何注入?我們需要知道,Dagger給了我們一堆選擇用來注入依賴:
構造方法注入:在類的構造方法前面注釋@Inject
成員變量注入:在類的成員變量(非私有)前面注釋@Inject
函數方法注入:在函數前面注釋@Inject
這個順序是Dagger建議使用的,因為在運行的過程中,總會有一些奇怪的問題甚至是空指針,這也意味著你的依賴在對象創建的時候可能還沒有初始化 完成。這在Android的activity或者fragment中使用成員變量注入會經常遇到,因為我們沒有在它們的構造方法中使用。
回到我們的例子中,看一下我們是如何在BaseActivity中注入一個成員變量。在這個例子中,我們注入了一個叫Navigator的類,它是我們應用中負責管理導航的類。
public abstract class BaseActivity extends Activity { @Inject Navigator navigator; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.getApplicationComponent().inject(this); } protected ApplicationComponent getApplicationComponent() { return ((AndroidApplication)getApplication()).getApplicationComponent(); } protected ActivityModule getActivityModule() { return new ActivityModule(this); } }
Navigator類是成員變量注入的,由ApplicationModule里面@Provide注解顯示提供的。最終我們初始化 component然后調用inject()方法注入成員變量。我們通過在Activity的onCreate()方法中調用 getApplicationComponent(),完成這些操作。getApplicationComponent()方法放在這兒是為了復用性,它 的主要作用是為了獲取實例化的ApplicationComponent對象。
在Fragment的presenter中我們也做了同樣的事情,這兒的獲取方法有一點不一樣,因為問我們使用的是per-activity范圍限 定的component。所以我們注入到UserDetailsFragment中的UserComponent其實是駐留在 UserDetailsActivity中的。
private UserComponent userComponent;
我們必須在activity的onCreate()方法中用下面的方式初始化。
private void initializeInjector() { this.userComponent = DaggerUserComponent.builder() .applicationComponent(getApplicationComponent()) .activityModule(getActivityModule()) .build(); }
Dagger會處理我們的注解,為components生成實現并重命名加上“Dagger”前綴。因為這個是一個組合的component,所以在構建 的時候,我們必須把所有的依賴的傳進去(components和modules)。現在我們的component已經準備好了,接著為了可以滿足 fragment的依賴需求,我們寫一個獲取方法:
@Override public UserComponent getComponent() { return userComponent; }
我們現在可以利用get方法獲取創建的component,然后調用inject()方法將Fragment作為參數傳進去,這樣就完成了綁定UserDetailsFragment依賴。
@Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); this.getComponent.inject(this); }
想要查看完整的例子,可以去我的github.這里面有一些地方重構了的,我可以告訴你一個重要的思想(來自官方的例子)是:
public interface HasComponent<C> { C getComponent(); }
因此,客戶端(例如fragment)可以獲取并且使用component(來自activity):
@SuppressWarnings("unchecked") protected <C> C getComponent(Class<C> componentType) { return componentType.cast(((HasComponent<C>)getActivity()).getComponent()); }
這兒使用了強制轉換,不論這個客戶端不能獲取到能用的component,但是至少很快就會失敗。如果你有任何想法能夠更好地解決這個問題,請告訴我。
Dagger2生成的代碼
在了解Dagger的主要特征之后,我們再來看看內部構造。為了舉例說明,我們還是用Navigator類,看看它是如何創建和注入的。首先我們看一下我們的DaggerApplicationComponent。
@Generated("dagger.internal.codegen.ComponentProcessor") public final class DaggerApplicationComponent implements ApplicationComponent { private Provider<Navigator> provideNavigatorProvider; private MembersInjector<BaseActivity> baseActivityMembersInjector; private DaggerApplicationComponent(Builder builder) { assert builder != null; initialize(builder); } public static Builder builder() { return new Builder(); } private void initialize(final Builder builder) { this.provideNavigatorProvider = ScopedProvider.create(ApplicationModule_ProvideNavigatorFactory.create(builder.applicationModule)); this.baseActivityMembersInjector = BaseActivity_MembersInjector.create((MembersInjector) MembersInjectors.noOp(), provideNavigatorProvider); } @Override public void inject(BaseActivity baseActivity) { baseActivityMembersInjector.injectMembers(baseActivity); } public static final class Builder { private ApplicationModule applicationModule; private Builder() { } public ApplicationComponent build() { if (applicationModule == null) { throw new IllegalStateException("applicationModule must be set"); } return new DaggerApplicationComponent(this); } public Builder applicationModule(ApplicationModule applicationModule) { if (applicationModule == null) { throw new NullPointerException("applicationModule"); } this.applicationModule = applicationModule; return this; } } }
有兩個重點需要注意。第一個:由于我們要將依賴注入到activity中,所以會得到一個注入這個比成員的注入器(由Dagger生成的BaseActivity_MembersInjector):
@Generated("dagger.internal.codegen.ComponentProcessor") public final class BaseActivity_MembersInjector implements MembersInjector<BaseActivity> { private final MembersInjector<Activity> supertypeInjector; private final Provider<Navigator> navigatorProvider; public BaseActivity_MembersInjector(MembersInjector<Activity> supertypeInjector, Provider<Navigator> navigatorProvider) { assert supertypeInjector != null; this.supertypeInjector = supertypeInjector; assert navigatorProvider != null; this.navigatorProvider = navigatorProvider; } @Override public void injectMembers(BaseActivity instance) { if (instance == null) { throw new NullPointerException("Cannot inject members into a null reference"); } supertypeInjector.injectMembers(instance); instance.navigator = navigatorProvider.get(); } public static MembersInjector<BaseActivity> create(MembersInjector<Activity> supertypeInjector, Provider<Navigator> navigatorProvider) { return new BaseActivity_MembersInjector(supertypeInjector, navigatorProvider); } }
這個注入器一般都會為所有activity的注入成員提供依賴,只要我們一調用inject()方法,就可以獲取需要的字段和依賴。
第二個重點:關于我們的DaggerApplicationComponent類,我們有一個Provider,它不僅僅是一個提供實例的接口,它還是被ScopedProvider構造出來的,可以記錄創建實例的范圍。
Dagger還會為我們的Navigator類生成一個名叫ApplicationModule_ProvideNavigatorFactory的工廠,這個工廠可以傳遞上面提到的范圍參數然后得到這個范圍內的類的實例。
@Generated("dagger.internal.codegen.ComponentProcessor") public final class ApplicationModule_ProvideNavigatorFactory implements Factory<Navigator> { private final ApplicationModule module; public ApplicationModule_ProvideNavigatorFactory(ApplicationModule module) { assert module != null; this.module = module; } @Override public Navigator get() { Navigator provided = module.provideNavigator(); if (provided == null) { throw new NullPointerException("Cannot return null from a non-@Nullable @Provides method"); } return provided; } public static Factory<Navigator> create(ApplicationModule module) { return new ApplicationModule_ProvideNavigatorFactory(module); } }
這個類非常簡單,它代表我們的ApplicationModule(包含@Provide方法)創建了Navigator類。
總之,上面的代碼看起來就像是手敲出來的,而且非常好理解,便于調試。其余還有很多可以去探索,你們可以通過調試去看看Dagger如何完成依賴綁定的。
源碼:
例子: https://github.com/android10/Android-CleanArchitecture
相關文章:
Architecting Android…The clean way?.
Dagger 2, A New Type of Dependency Injection.
Dependency Injection with Dagger 2.
Dagger 2 has Components.
Dagger 2 Official Documentation.