Dagger 基本概念

視專案大小而定,Android 應用程式中的手動依附元件插入或服務定位器可能有問題。您可以透過使用 Dagger 管理依附元件,以在其向上擴充時,限制專案的複雜度。

Dagger 會自動產生程式碼,模仿您在其他情況自行編寫的程式碼。由於程式碼是在編譯時間產生,因此可以比其他反射型解決方案 (例如 Guice) 更易於追蹤且效能更高。

使用 Dagger 的優點

Dagger 讓您不必編寫繁瑣且容易出錯的樣板程式碼,方法如下:

  • 產生手動 DI 區段中手動導入的 AppContainer 程式碼 (應用程式圖表)。

  • 針對應用程式圖表中可用的類別建立工廠。這就是內部依附元件的達成方式。

  • 決定要重複使用依附元件,還是透過使用「範圍」來建立新的執行個體。

  • 按照上一節的登入流程,使用 Dagger「子元件」為特定流程的建立容器。這麼做會在不再需要記憶體中的物件時將其釋出,藉此提升應用程式效能。

只要宣告類別的依附元件,並使用註解指明如何滿足這些條件,Dagger 就會在建構期間自動執行上述所有操作。Dagger 會產生與手動編寫程式碼類似的程式碼。Dagger 在內部會建立物件圖表,以參照提供類別執行個體的方式。針對圖表中的每個類別,Dagger 會產生一個工廠類型類別,並使用該類別在內部取得該類型的執行個體。

在建構期間,Dagger 逐步說明您的程式碼,並:

  • 建構及驗證依附元件圖表,確保下列事項:

    • 可以滿足每個物件的依附元件,因此沒有執行階段例外狀況。
    • 沒有任何依附元件週期,因此沒有無限迴圈。
  • 產生在執行階段使用的類別,用於建立實際物件及其依附元件。

Dagger 的簡易用途之一:產生工廠

為示範如何使用 Dagger,讓我們為 UserRepository 類別建立簡易的工廠,如下圖所示:

定義 UserRepository,如下所示:

Kotlin

class UserRepository(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

Java

public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }

    ...
}

UserRepository 建構函式中加入 @Inject 註解,讓 Dagger 瞭解如何建立 UserRepository

Kotlin

// @Inject lets Dagger know how to create instances of this object
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

Java

public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    // @Inject lets Dagger know how to create instances of this object
    @Inject
    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }
}

在上述程式碼片段中,您會告訴 Dagger:

  1. 如何使用 @Inject 註解的建構函式建立 UserRepository 執行個體。

  2. 依附元件為:UserLocalDataSourceUserRemoteDataSource

現在,Dagger 知道如何建立 UserRepository 的例項,但不知道如何建立其依附元件。如果您也為其他類別註解,Dagger 知道如何建立這些類別:

Kotlin

// @Inject lets Dagger know how to create instances of these objects
class UserLocalDataSource @Inject constructor() { ... }
class UserRemoteDataSource @Inject constructor() { ... }

Java

public class UserLocalDataSource {
    @Inject
    public UserLocalDataSource() { }
}

public class UserRemoteDataSource {
    @Inject
    public UserRemoteDataSource() { }
}

Dagger 元件

Dagger 可在您的專案中建立依附元件圖表,用來在必要時尋找依附元件。如要讓 Dagger 執行這項操作,您必須建立介面,並使用 @Component 加上註解。Dagger 會建立容器,做法與手動依附元件插入一樣。

@Component 介面中,您可以定義函式,用於傳回所需類別的執行個體 (例如 UserRepository)。@Component 會指示 Dagger 產生容器,並滿足其公開類型的所有必要依附元件。這就是所謂的「Dagger 元件」;其中包含一個圖表,內含 Dagger 如何提供物件及其相關依附元件。

Kotlin

// @Component makes Dagger create a graph of dependencies
@Component
interface ApplicationGraph {
    // The return type  of functions inside the component interface is
    // what can be provided from the container
    fun repository(): UserRepository
}

Java

// @Component makes Dagger create a graph of dependencies
@Component
public interface ApplicationGraph {
    // The return type  of functions inside the component interface is
    // what can be consumed from the graph
    UserRepository userRepository();
}

建構專案時,Dagger 會為您產生 ApplicationGraph 介面的實作:DaggerApplicationGraph。透過註解處理工具,Dagger 建立依附元件圖表,當中包含三個類別 (UserRepositoryUserLocalDatasourceUserRemoteDataSource) 之間,只有一個進入點的關聯:取得 UserRepository 執行個體。您可以按照下列方式使用:

Kotlin

// Create an instance of the application graph
val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()
// Grab an instance of UserRepository from the application graph
val userRepository: UserRepository = applicationGraph.repository()

Java

// Create an instance of the application graph
ApplicationGraph applicationGraph = DaggerApplicationGraph.create();

// Grab an instance of UserRepository from the application graph
UserRepository userRepository = applicationGraph.userRepository();

Dagger 會在每次要求時建立新的 UserRepository 執行個體。

Kotlin

val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()

val userRepository: UserRepository = applicationGraph.repository()
val userRepository2: UserRepository = applicationGraph.repository()

assert(userRepository != userRepository2)

Java

ApplicationGraph applicationGraph = DaggerApplicationGraph.create();

UserRepository userRepository = applicationGraph.userRepository();
UserRepository userRepository2 = applicationGraph.userRepository();

assert(userRepository != userRepository2)

有時候,您需要在容器中具有依附元件的專屬執行個體。基於以下原因,您可能會想這麼做:

  1. 您希望有此類型做為依附元件的其他類型共用同一個執行個體,例如在使用相同 LoginUserData 在登入流程中有多個 ViewModel 物件。

  2. 建立物件的費用高昂,而您不會想在每次宣告依附元件時建立新的執行個體 (例如 JSON 剖析器)。

在此範例中,您可能希望在圖表中使用 UserRepository 的專屬執行個體,因此每次要求 UserRepository 時,您都會得到相同的執行個體。這在範例中非常實用,因為現實中的應用程式擁有較為複雜的應用程式圖表,您可能會有多個 ViewModel 物件取決於 UserRepository,並且您可能在需要提供 UserRepository 時,不想建立 UserLocalDataSourceUserRemoteDataSource 的新執行個體。

在手動依附元件插入中,您可以將相同的 UserRepository 執行個體傳遞至 ViewModel 類別的建構函式,即可執行此操作;但在 Dagger 中,由於您並未手動編寫該程式碼,因此您必須讓 Dagger 使用相同的執行個體。您可以使用「範圍註解」來完成這項操作。

使用 Dagger 限定範圍

您可以使用範圍註解,將物件的生命週期限制為元件的生命週期。這表示每當需要提供該類型執行個體時,系統就會使用相同的依附元件執行個體。

ApplicationGraph 中要求存放區時,如要取得 UserRepository 的專屬執行個體,請在 @Component 介面和 UserRepository 使用相同的範圍註解。您可以使用 Dagger 使用的 javax.inject 套件隨附的 @Singleton 註解:

Kotlin

// Scope annotations on a @Component interface informs Dagger that classes annotated
// with this annotation (i.e. @Singleton) are bound to the life of the graph and so
// the same instance of that type is provided every time the type is requested.
@Singleton
@Component
interface ApplicationGraph {
    fun repository(): UserRepository
}

// Scope this class to a component using @Singleton scope (i.e. ApplicationGraph)
@Singleton
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

Java

// Scope annotations on a @Component interface informs Dagger that classes annotated
// with this annotation (i.e. @Singleton) are scoped to the graph and the same
// instance of that type is provided every time the type is requested.
@Singleton
@Component
public interface ApplicationGraph {
    UserRepository userRepository();
}

// Scope this class to a component using @Singleton scope (i.e. ApplicationGraph)
@Singleton
public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    @Inject
    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }
}

或者,您也可以建立及使用自訂範圍註解。您可以按照下列方式建立範圍註解:

Kotlin

// Creates MyCustomScope
@Scope
@MustBeDocumented
@Retention(value = AnnotationRetention.RUNTIME)
annotation class MyCustomScope

Java

// Creates MyCustomScope
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCustomScope {}

然後,您就可以像以前一樣使用:

Kotlin

@MyCustomScope
@Component
interface ApplicationGraph {
    fun repository(): UserRepository
}

@MyCustomScope
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val service: UserService
) { ... }

Java

@MyCustomScope
@Component
public interface ApplicationGraph {
    UserRepository userRepository();
}

@MyCustomScope
public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    @Inject
    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }
}

在這兩種情況下,系統會提供相同範圍的物件,用來為 @Component 介面加註。因此,每次呼叫 applicationGraph.repository() 時,都會得到相同的 UserRepository 執行個體。

Kotlin

val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()

val userRepository: UserRepository = applicationGraph.repository()
val userRepository2: UserRepository = applicationGraph.repository()

assert(userRepository == userRepository2)

Java

ApplicationGraph applicationGraph = DaggerApplicationGraph.create();

UserRepository userRepository = applicationGraph.userRepository();
UserRepository userRepository2 = applicationGraph.userRepository();

assert(userRepository == userRepository2)

結語

在更複雜的情境中,您應瞭解 Dagger 的優點以及運作方式的基本概念。

下一頁中,您將瞭解如何在 Android 應用程式中新增 Dagger。