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

来源:http://www.cnblogs.com/tiantianbyconan/archive/2016/01/02/5095426.html
-Advertisement-
Play Games

<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):UserComponentSplashActivityComponent。因為它們都是AppComponent的subcomponents,所以它們兩者都可以訪問AppModuleGithubApiModule創建的實例。

這些方法的命名法則是:返回類型是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:RepositoriesListActivityComponentRepositoryDetailsActivityComponent

@UserScope
@Subcomponent(
        modules = {
                UserModule.class
        }
)
public interface UserComponent {
    RepositoriesListActivityComponent plus(RepositoriesListActivityModule repositoriesListActivityModule);

    RepositoryDetailsActivityComponent plus(RepositoryDetailsActivityModule repositoryDetailsActivityModule);
}

並且更重要的是所有scope的東西都發生在這裡。所有UserComponent中從AppComponent繼承過來的仍然shi是單例的(是 Applicaton scope)。但是UserModuleUserComponent的那部分)創建的對象將會是“局部單例”,它的生命周期跟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實例,它應該使用@UserScopeScope。我們來檢驗這個方法哪裡被調用(第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_ProvideRepositoriesManagerFactoryUserComponentImpl中被使用 - 我們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


[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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 效果如圖目前旋轉和銳化的界面還沒做。編輯模塊包含圖片裁剪、旋轉、銳化。一、點擊美化圖片首頁【編輯】,進入具體操作界面 FWFunctionViewController *vc = [[FWFunctionViewController alloc] initWithImage:self.imag...
  • [轉載請註明出處]sdk是別人的,我只是下載來集成一下.smssdk下載網站:http://www.mob.com/(也有其他很多網站有類似SDK,譬如https://www.juhe.cn/等等,可以自行百度,我在這裡就演示一下MOB官網的)此網站號稱smssdk免費,可是進去一看...........
  • //// ViewController.m// 計算器//屏幕的寬和高#define SCREEN_W self.view.frame.size.width#define SCREEN_H self.view.frame.size.height#import "ViewController.h"@i...
  • Button講解:一、在我們實際的使用button的時候經常會對button不同狀態會有不同的顯示,在講解Button前,首先對drawable下麵的statelistdrawable的相關知識講一下,StateListDrawable在一中drawable下麵的一種資源文件,它的關鍵節點selec...
  • EditText的講解一、《實例一》:用戶登錄 屬性說明:很多屬性和TextView相同就不做過多的解釋,這裡就對這兩個屬性進行說明:android:hint="預設提示文本" android:textColorHint="#95A1AA",第一...
  • .a文件是靜態文件,有多個.o文件組合而成的,在ios項目開發中,當引用第三方庫的時候,時不時的會碰到諸如庫衝突、庫包含了某些禁用的API等問題,而這些庫往往都被打包成了靜態庫文件(即.a文件)來使用。這時就需要我們能夠去對Objectfile進行一些必要的處理調整。如檢索信息,移除衝突的庫等。首先...
  • 首先是今天想測試下應用,沒有問題的話就進行下一步的操作來著,結果遇到這個問題,The connection to adb is down, and a severe error has occured.網上找的一些解決方法:1、在Eclipse中選擇菜單“help->Checkforupdate”,...
  • 問題如圖:一般都是系統未啟動或者未安裝該服務。1.使用sc命令查詢是否存在IpOverUsbSvc服務Cmd執行Sc queryIpOverUsbSvc結果如下,如果可以找到服務,state為closed,可以執行 Sc startIpOverUsbSvc2.若服務不存在,使用SC命令創建服務:sc...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...