<font color=" ff0000" <strong 以下內容為原創,歡迎轉載,轉載請註明來自天天博客:<http://www.cnblogs.com/tiantianbyconan/p/5095426.html </strong </font 使用Dagger 2依賴註入 自定義Scope....
以下內容為原創,歡迎轉載,轉載請註明
來自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5095426.html
使用Dagger 2依賴註入 - 自定義Scope
原文:http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/
這章是展示使用Dagger 2在Android端實現依賴註入的系列中的一部分。今天我會花點時間在自定義Scope(作用域)上面 - 它是很實用,但是對於剛接觸依賴註入的人會有一點困難。
Scope - 它給我們帶來了什麼?
幾乎所有的項目都會用到單例 - 比如API clients,database helpers,analytics managers等。因為我們不需要去關心實例化(由於依賴註入),我們不應該在我們的代碼中考慮關於怎麼得到這些對象。取而代之的是@Inject
註解應該提供給我們適合的實例。
在Dagger 2中,Scope機制可以使得在scope存在時保持類的單例。在實踐中,這意味著被限定範圍為@ApplicationScope
的實例與Applicaiton對象的生命周期一致。@ActivityScope
保證引用與Activity的生命周期一致(舉個例子我們可以在這個Activity中持有的所有fragment之間分享一個任何類的單例)。
簡單來說 - scope給我們帶來了“局部單例”,生命周期取決於scope自己。
但是需要弄清楚的是 - Dagger 2預設並不提供@ActivityScope
或者/並且 @ApplicationScope
這些註解。這些只是最常用的自定義Scope。只有@Singleton
scope是預設提供的(由Java自己提供)。
Scope - 實踐案例
為了更好地去理解Dagger 2中的scope,我們直接進入實踐案例。我們將要去實現比Application/Activity scope更加複雜一點的scope。為此我們將使用 上一文章 中的 GithubClient 例子。我們的app應需要三種scope:
- __@Sigleton__ - application scope
- __@UserScope__ - 用於與被選中的用戶聯繫起來的類實例的scope(在真實的app中可以是當前登錄的用戶)。
- __@ActivityScope__ - 生命周期與Activity(在我們例子中的呈現者)一致的實例的scope
講解的@UserScope
是今天方案與以前文章之間的主要的不同之處。從用戶體驗的角度來說它沒有帶給我們任何東西,但是從架構的觀點來說它幫助我們在不傳入任何意圖參數的情況下提供了User實例。使用方法參數獲取用戶數據的類(在我們的例子中是RepositoriesManager
)中我們可以通過構造參數(它將通過依賴圖表提供)的方式來獲取User實例併在需要的時候被初始化,而不是在app啟動的時候創建它。這意味著RepositoriesManager
可以在我們從Github API獲取到用戶信息(在RepositoriesListActivity
呈現之前)之後被創建。
這裡有個我們app中scopes和components呈現的簡單圖表。
單例(Application scope)是最長的scope(在實踐中是與application一樣長)。UserScope作為Application scope的一個子集scope,它可以訪問它的對象(我們可以從父scope中得到對象)。ActivityScope(生命周期與Activity一致)也是如此 - 它可以從UserScope和ApplicationScope中得到對象。
Scope生命周期的例子
這裡有一個我們app中scope生命周期的案例:
單例的生命周期是從app啟動後的所有的時期,當我們從Github API(在真實app中是用戶登錄之後)得到了User
實例時UserScope被創建了,然後當我們回退到SplashActivity(在真實app中是用戶退出之後)時被銷毀。
實現
在Dagger 2中,Scope的實現歸結於對Components的一個正確的設置。一般情況下我們有兩種方式 - 使用Subcomponent
註解或者使用Components依賴。它們兩者最大的區別就是對象圖表的共用。Subcomponents可以訪問它們parent的所有對象圖表,而Component依賴只能訪問通過Component介面暴露的對象。
我選擇第一種使用 @Subcomponent
註解,如果你之前使用過Dagger 1,它幾乎與從ObjectGraph
創建一個subgraphs(子圖表)是一樣的。此外,對於創建一個subgraphs的方法我們會使用類似的命名法則(但這不是強制性的)。
我們從AppComponent
的實現開始:
@Singleton
@Component(
modules = {
AppModule.class,
GithubApiModule.class
}
)
public interface AppComponent {
UserComponent plus(UserModule userModule);
SplashActivityComponent plus(SplashActivityModule splashActivityModule);
}
它將會是其它subcomponents的根Components:UserComponent
和Activities Components。正如你註意到的那樣(尤其如果你在前面的文章中看過的AppComponent 實現)所有的返回依賴圖表對象的公開方法全部消失了。因為我們有subcomponents了,我們不需要去公開去暴露依賴了 - 無論如何subgraphs都可以訪問它們全部了。
作為替代,我們新增了兩個方法:
UserComponent plus(UserModule userModule);
SplashActivityComponent plus(SplashActivityModule splashActivityModule);
這表示,我們可以從AppComponent
創建兩個子Components(subcomponents):UserComponent
和SplashActivityComponent
。因為它們都是AppComponent的subcomponents,所以它們兩者都可以訪問AppModule
和GithubApiModule
創建的實例。
這些方法的命名法則是:返回類型是subcomponent類,方法名字隨意,參數是這個subcomponent需要的modules。
如你所見,UserComponent
需要另一個module(它通過plus()
方法的參數傳入)。這樣,我們通過增加一個新的用於生成對象的module,繼承AppComponent
圖表。UserComponent
類看起來這樣:
@UserScope
@Subcomponent(
modules = {
UserModule.class
}
)
public interface UserComponent {
RepositoriesListActivityComponent plus(RepositoriesListActivityModule repositoriesListActivityModule);
RepositoryDetailsActivityComponent plus(RepositoryDetailsActivityModule repositoryDetailsActivityModule);
}
當然@UserScope
註解是我們自己創建的:
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface UserScope {
}
我們可以從UserComponent
創建另外兩個subcomponents:RepositoriesListActivityComponent
和RepositoryDetailsActivityComponent
。
@UserScope
@Subcomponent(
modules = {
UserModule.class
}
)
public interface UserComponent {
RepositoriesListActivityComponent plus(RepositoriesListActivityModule repositoriesListActivityModule);
RepositoryDetailsActivityComponent plus(RepositoryDetailsActivityModule repositoryDetailsActivityModule);
}
並且更重要的是所有scope的東西都發生在這裡。所有UserComponent
中從AppComponent
繼承過來的仍然shi是單例的(是 Applicaton scope)。但是UserModule
(UserComponent
的那部分)創建的對象將會是“局部單例”,它的生命周期跟UserComponent
實例是一樣的。
所以,每次一創建另一個UserComponent
實例將會調用:
UserComponent appComponent = appComponent.plus(new UserModule(user))
從UserModule
中獲取的對象將是不同的實例。
但是這裡很重要的一點是 - 我們要負責UserComponent
的生命周期。所以我們應該關心它的初始化和釋放。在我們的例子中,我為它增加了兩個額外的方法:
public class GithubClientApplication extends Application {
private AppComponent appComponent;
private UserComponent userComponent;
//...
public UserComponent createUserComponent(User user) {
userComponent = appComponent.plus(new UserModule(user));
return userComponent;
}
public void releaseUserComponent() {
userComponent = null;
}
//...
}
createUserComponent()
方法會在我們從Github API(在SplashActivity
中)獲取到User
對象時調用。releaseUserComponent()
方法會在我們從RepositoriesListActivity
(這個時候我們不再需要user scope了)中返回時調用。
Dagger 2中的Scope - 內部實現
查看它的內部的工作原理是很不錯的。通常在這種情況下可以確定,在Dagger 2的scope機制下並不存在什麼魔法。
我們從UserModule.provideRepositoriesManager()
方法開始研究。它提供了RepositoriesManager
實例,它應該使用@UserScope
Scope。我們來檢驗這個方法哪裡被調用(第8行):
@Generated("dagger.internal.codegen.ComponentProcessor")
public final class UserModule_ProvideRepositoriesManagerFactory implements Factory<RepositoriesManager> {
//...
@Override
public RepositoriesManager get() {
RepositoriesManager provided = module.provideRepositoriesManager(userProvider.get(), githubApiServiceProvider.get());
if (provided == null) {
throw new NullPointerException("Cannot return null from a non-@Nullable @Provides method");
}
return provided;
}
public static Factory<RepositoriesManager> create(UserModule module, Provider<User> userProvider, Provider<GithubApiService> githubApiServiceProvider) {
return new UserModule_ProvideRepositoriesManagerFactory(module, userProvider, githubApiServiceProvider);
}
}
UserModule_ProvideRepositoriesManagerFactory
僅僅是一個工廠模式的現實,它從UserModule
中獲取到RepositoriesManager
實例。我們應該往更深層次挖掘。
UserModule_ProvideRepositoriesManagerFactory
在UserComponentImpl
中被使用 - 我們component的實現(line 15):
private final class UserComponentImpl implements UserComponent {
//...
private UserComponentImpl(UserModule userModule) {
if (userModule == null) {
throw new NullPointerException();
}
this.userModule = userModule;
initialize();
}
private void initialize() {
this.provideUserProvider = ScopedProvider.create(UserModule_ProvideUserFactory.create(userModule));
this.provideRepositoriesManagerProvider = ScopedProvider.create(UserModule_ProvideRepositoriesManagerFactory.create(userModule, provideUserProvider, DaggerAppComponent.this.provideGithubApiServiceProvider));
}
//...
}
provideRepositoriesManagerProvider
對象在我們每次請求它時負責提供RepositoriesManager
實例。如我們所見,provider是通過ScopedProvider
實現的。來看下它的部分代碼:
public final class ScopedProvider<T> implements Provider<T> {
//...
private ScopedProvider(Factory<T> factory) {
assert factory != null;
this.factory = factory;
}
@SuppressWarnings("unchecked") // cast only happens when result comes from the factory
@Override
public T get() {
// double-check idiom from EJ2: Item 71
Object result = instance;
if (result == UNINITIALIZED) {
synchronized (this) {
result = instance;
if (result == UNINITIALIZED) {
instance = result = factory.get();
}
}
}
return (T) result;
}
//...
}
再簡單不過了吧?第一次調用ScopedProvider
從factory(我們的例子中是UserModule_ProvideRepositoriesManagerFactory
)中獲取實例並像單例模式一樣存儲起來。我們的scoped provider只是UserComponentImpl
中的一個屬性,所以簡單說就是ScopedProvider
返回一個與依賴於Component的單例。
在這裡你可以查看 ScopedProvider 的所有的實現。
就是這樣。我們弄清楚了Dagger 2中Scope底層是怎麼工作的。現在我們知道,它們沒有以任何方式於Scope註解連接。自定義註解只是給了我們一個簡單的方式來進行編譯時代碼校驗和標記一個類是單例/非單例。所有的scope相關東西都是與Component的生命周期相關聯。
以上就是今天的全部內容。我希望從現在開始scopes會變得更加容易使用。感謝閱讀!
代碼:
以上描述的完整代碼可見Github repository。
作者
Head of Mobile Development @ Azimo
[Android]使用Dagger 2依賴註入 - DI介紹(翻譯):
http://www.cnblogs.com/tiantianbyconan/p/5092083.html
[Android]使用Dagger 2依賴註入 - API(翻譯):
http://www.cnblogs.com/tiantianbyconan/p/5092525.html
[Android]使用Dagger 2依賴註入 - 自定義Scope(翻譯):
http://www.cnblogs.com/tiantianbyconan/p/5095426.html
[Android]使用Dagger 2依賴註入 - 圖表創建的性能(翻譯):
http://www.cnblogs.com/tiantianbyconan/p/5098943.html
[Android]Dagger2Metrics - 測量DI圖表初始化的性能(翻譯):
http://www.cnblogs.com/tiantianbyconan/p/5193437.html