視專案大小而定,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:
如何使用
@Inject
註解的建構函式建立UserRepository
執行個體。依附元件為:
UserLocalDataSource
和UserRemoteDataSource
。
現在,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 建立依附元件圖表,當中包含三個類別 (UserRepository
、UserLocalDatasource
和 UserRemoteDataSource
) 之間,只有一個進入點的關聯:取得 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)
有時候,您需要在容器中具有依附元件的專屬執行個體。基於以下原因,您可能會想這麼做:
您希望有此類型做為依附元件的其他類型共用同一個執行個體,例如在使用相同
LoginUserData
在登入流程中有多個ViewModel
物件。建立物件的費用高昂,而您不會想在每次宣告依附元件時建立新的執行個體 (例如 JSON 剖析器)。
在此範例中,您可能希望在圖表中使用 UserRepository
的專屬執行個體,因此每次要求 UserRepository
時,您都會得到相同的執行個體。這在範例中非常實用,因為現實中的應用程式擁有較為複雜的應用程式圖表,您可能會有多個 ViewModel
物件取決於 UserRepository
,並且您可能在需要提供 UserRepository
時,不想建立 UserLocalDataSource
和 UserRemoteDataSource
的新執行個體。
在手動依附元件插入中,您可以將相同的 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。