[Android]使用Dagger 2依賴注入 - 自定義Scope(翻譯)

使用Dagger 2依賴注入 - 自定義Scope

這章是展示使用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

作者

Miroslaw Stanek

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

來自: http://www.cnblogs.com/tiantianbyconan/p/5095426.html

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!