Android 推荐应用架构建议将代码划分为多个类,以从分离关注点这一原则(其中,层次结构的每个类都具有一项已定义的职责)中受益。这就需要将更多更小的类连接在一起,以实现彼此之间的依赖关系。
各个类之间的依赖关系可以表示为图表,其中每个类都连接到其所依赖的类。所有类及其依赖关系的表示法便构成了应用图表。在图 1 中,您可以看到应用图表的抽象呈现。
当 A 类 (ViewModel
) 依赖于 B 类 (Repository
) 时,有一条从 A 指向 B 的直线表示该依赖关系。
依赖项注入有助于建立这些连接,并使您可以更换实现以进行测试。例如,在测试依赖于代码库的 ViewModel
时,您可以通过伪造或模拟传递 Repository
的不同实现,以测试不同的情形。
手动依赖项注入的基础知识
本部分介绍如何在实际 Android 应用场景中应用手动依赖项注入。本部分详细介绍了如何开始在应用中使用依赖项注入的迭代方法。该方法会不断改进,直至达到与 Dagger 自动为您生成的场景相似的程度。如需详细了解 Dagger,请参阅 Dagger 基础知识。
将流程视为应用中与某项功能相对应的一组屏幕。登录、注册和签出都是流程的示例。
在介绍典型 Android 应用的登录流程时,LoginActivity
依赖于 LoginViewModel
,而后者又依赖于 UserRepository
。然后,UserRepository
依赖于 UserLocalDataSource
和
UserRemoteDataSource
,而后者依赖于 Retrofit
服务。
LoginActivity
是登录流程的入口点,用户与 activity 进行交互。因此,LoginActivity
需要创建 LoginViewModel
及其所有依赖项。
该流程的 Repository
和 DataSource
类如下所示:
Kotlin
class UserRepository( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... } class UserLocalDataSource { ... } class UserRemoteDataSource( private val loginService: LoginRetrofitService ) { ... }
Java
class UserLocalDataSource { public UserLocalDataSource() { } ... } class UserRemoteDataSource { private final Retrofit retrofit; public UserRemoteDataSource(Retrofit retrofit) { this.retrofit = retrofit; } ... } class UserRepository { private final UserLocalDataSource userLocalDataSource; private final UserRemoteDataSource userRemoteDataSource; public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) { this.userLocalDataSource = userLocalDataSource; this.userRemoteDataSource = userRemoteDataSource; } ... }
LoginActivity
如下所示:
Kotlin
class LoginActivity: Activity() { private lateinit var loginViewModel: LoginViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // In order to satisfy the dependencies of LoginViewModel, you have to also // satisfy the dependencies of all of its dependencies recursively. // First, create retrofit which is the dependency of UserRemoteDataSource val retrofit = Retrofit.Builder() .baseUrl("https://example.com") .build() .create(LoginService::class.java) // Then, satisfy the dependencies of UserRepository val remoteDataSource = UserRemoteDataSource(retrofit) val localDataSource = UserLocalDataSource() // Now you can create an instance of UserRepository that LoginViewModel needs val userRepository = UserRepository(localDataSource, remoteDataSource) // Lastly, create an instance of LoginViewModel with userRepository loginViewModel = LoginViewModel(userRepository) } }
Java
public class MainActivity extends Activity { private LoginViewModel loginViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // In order to satisfy the dependencies of LoginViewModel, you have to also // satisfy the dependencies of all of its dependencies recursively. // First, create retrofit which is the dependency of UserRemoteDataSource Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://example.com") .build() .create(LoginService.class); // Then, satisfy the dependencies of UserRepository UserRemoteDataSource remoteDataSource = new UserRemoteDataSource(retrofit); UserLocalDataSource localDataSource = new UserLocalDataSource(); // Now you can create an instance of UserRepository that LoginViewModel needs UserRepository userRepository = new UserRepository(localDataSource, remoteDataSource); // Lastly, create an instance of LoginViewModel with userRepository loginViewModel = new LoginViewModel(userRepository); } }
这种方法存在以下问题:
有大量样板代码。如需在代码的另一部分中创建另一个
LoginViewModel
实例,则需要使用重复代码。必须按顺序声明依赖项。必须在
LoginViewModel
之前实例化UserRepository
才能创建它。很难重复使用对象。如需在多项功能中重复使用
UserRepository
,必须使其遵循单例模式。单例模式使测试变得更加困难,因为所有测试共享相同的单例实例。
使用容器管理依赖项
如需解决重复使用对象的问题,您可以创建自己的依赖项容器类,用于获取依赖项。此容器提供的所有实例可以是公共实例。在该示例中,由于您仅需要 UserRepository
的一个实例,您可以将其依赖项设为私有,并且可以在将来需要提供依赖项时将其公开:
Kotlin
// Container of objects shared across the whole app class AppContainer { // Since you want to expose userRepository out of the container, you need to satisfy // its dependencies as you did before private val retrofit = Retrofit.Builder() .baseUrl("https://example.com") .build() .create(LoginService::class.java) private val remoteDataSource = UserRemoteDataSource(retrofit) private val localDataSource = UserLocalDataSource() // userRepository is not private; it'll be exposed val userRepository = UserRepository(localDataSource, remoteDataSource) }
Java
// Container of objects shared across the whole app public class AppContainer { // Since you want to expose userRepository out of the container, you need to satisfy // its dependencies as you did before private Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://example.com") .build() .create(LoginService.class); private UserRemoteDataSource remoteDataSource = new UserRemoteDataSource(retrofit); private UserLocalDataSource localDataSource = new UserLocalDataSource(); // userRepository is not private; it'll be exposed public UserRepository userRepository = new UserRepository(localDataSource, remoteDataSource); }
由于这些依赖项在整个应用中使用,因此需要将它们放置在所有 activity 都可以使用的通用位置:Application
类。创建一个包含 AppContainer
实例的自定义 Application
类。
Kotlin
// Custom Application class that needs to be specified // in the AndroidManifest.xml file class MyApplication : Application() { // Instance of AppContainer that will be used by all the Activities of the app val appContainer = AppContainer() }
Java
// Custom Application class that needs to be specified // in the AndroidManifest.xml file public class MyApplication extends Application { // Instance of AppContainer that will be used by all the Activities of the app public AppContainer appContainer = new AppContainer(); }
现在,您可以从应用中获取 AppContainer
的实例并获取共享 UserRepository
实例:
Kotlin
class LoginActivity: Activity() { private lateinit var loginViewModel: LoginViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Gets userRepository from the instance of AppContainer in Application val appContainer = (application as MyApplication).appContainer loginViewModel = LoginViewModel(appContainer.userRepository) } }
Java
public class MainActivity extends Activity { private LoginViewModel loginViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Gets userRepository from the instance of AppContainer in Application AppContainer appContainer = ((MyApplication) getApplication()).appContainer; loginViewModel = new LoginViewModel(appContainer.userRepository); } }
这样一来,您就没有单例 UserRepository
。相反,您可以在所有 activity 中共享 AppContainer
,其包含图表中的对象并创建其他类可以使用的对象实例。
如果需要在应用的更多位置使用 LoginViewModel
,则具有一个可创建 LoginViewModel
实例的集中位置是有必要的。您可以将 LoginViewModel
的创建移至容器,并为该类型的新对象提供工厂。LoginViewModelFactory
的代码如下所示:
Kotlin
// Definition of a Factory interface with a function to create objects of a type interface Factory<T> { fun create(): T } // Factory for LoginViewModel. // Since LoginViewModel depends on UserRepository, in order to create instances of // LoginViewModel, you need an instance of UserRepository that you pass as a parameter. class LoginViewModelFactory(private val userRepository: UserRepository) : Factory{ override fun create(): LoginViewModel { return LoginViewModel(userRepository) } }
Java
// Definition of a Factory interface with a function to create objects of a type public interface Factory<T> { T create(); } // Factory for LoginViewModel. // Since LoginViewModel depends on UserRepository, in order to create instances of // LoginViewModel, you need an instance of UserRepository that you pass as a parameter. class LoginViewModelFactory implements Factory{ private final UserRepository userRepository; public LoginViewModelFactory(UserRepository userRepository) { this.userRepository = userRepository; } @Override public LoginViewModel create() { return new LoginViewModel(userRepository); } }
您可以在 AppContainer
中添加 LoginViewModelFactory
并让 LoginActivity
使用它:
Kotlin
// AppContainer can now provide instances of LoginViewModel with LoginViewModelFactory class AppContainer { ... val userRepository = UserRepository(localDataSource, remoteDataSource) val loginViewModelFactory = LoginViewModelFactory(userRepository) } class LoginActivity: Activity() { private lateinit var loginViewModel: LoginViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Gets LoginViewModelFactory from the application instance of AppContainer // to create a new LoginViewModel instance val appContainer = (application as MyApplication).appContainer loginViewModel = appContainer.loginViewModelFactory.create() } }
Java
// AppContainer can now provide instances of LoginViewModel with LoginViewModelFactory public class AppContainer { ... public UserRepository userRepository = new UserRepository(localDataSource, remoteDataSource); public LoginViewModelFactory loginViewModelFactory = new LoginViewModelFactory(userRepository); } public class MainActivity extends Activity { private LoginViewModel loginViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Gets LoginViewModelFactory from the application instance of AppContainer // to create a new LoginViewModel instance AppContainer appContainer = ((MyApplication) getApplication()).appContainer; loginViewModel = appContainer.loginViewModelFactory.create(); } }
此方法比前一种方法更好,但仍需考虑一些挑战:
您必须自行管理
AppContainer
,手动为所有依赖项创建实例。仍然有大量样板代码。您需要手动创建工厂或参数,具体取决于是否要重复使用某个对象。
管理应用流程中的依赖项
如需在项目中添加更多功能,AppContainer
会变得非常复杂。当应用变大并且可以引入不同功能流程时,还会出现更多问题:
当您具有不同的流程时,您可能希望对象仅位于该流程的作用域内。例如,在创建
LoginUserData
时(可能包含仅在登录流程中使用的用户名和密码),您不希望保留来自其他用户的旧登录流程中的数据。您需要为每个新流程创建一个新实例。您可以通过在AppContainer
内部创建FlowContainer
对象实现这一目标,如下面的代码示例所示。对应用图表和流程容器进行优化可能也非常困难。您需要注意删除不需要的实例,具体取决于您所处的流程。
假设您的登录流程由一个 activity (LoginActivity
) 和多个 fragment(LoginUsernameFragment
和 LoginPasswordFragment
)组成。这些视图需要:
访问需要共享的同一
LoginUserData
实例,直至登录流程完成。当该流程再次开始时,创建一个新的
LoginUserData
实例。
您可以使用登录流程容器实现这一目标。此容器需要在登录流程开始时创建,并在流程结束时将其从内存中移除。
我们将 LoginContainer
添加到示例代码中。您希望能够在应用中创建多个 LoginContainer
实例,因此,请不要将其设为单例,而应使其成为具有登录流程需要从 AppContainer
中获取的依赖项的类。
Kotlin
class LoginContainer(val userRepository: UserRepository) { val loginData = LoginUserData() val loginViewModelFactory = LoginViewModelFactory(userRepository) } // AppContainer contains LoginContainer now class AppContainer { ... val userRepository = UserRepository(localDataSource, remoteDataSource) // LoginContainer will be null when the user is NOT in the login flow var loginContainer: LoginContainer? = null }
Java
// Container with Login-specific dependencies class LoginContainer { private final UserRepository userRepository; public LoginContainer(UserRepository userRepository) { this.userRepository = userRepository; loginViewModelFactory = new LoginViewModelFactory(userRepository); } public LoginUserData loginData = new LoginUserData(); public LoginViewModelFactory loginViewModelFactory; } // AppContainer contains LoginContainer now public class AppContainer { ... public UserRepository userRepository = new UserRepository(localDataSource, remoteDataSource); // LoginContainer will be null when the user is NOT in the login flow public LoginContainer loginContainer; }
拥有某个流程专用的容器后,必须决定何时创建和删除容器实例。由于您的登录流程在 activity (LoginActivity
) 中是独立的,因此该 activity 是管理该容器生命周期的 activity。LoginActivity
可以在 onCreate()
中创建实例并在 onDestroy()
中将其删除。
Kotlin
class LoginActivity: Activity() { private lateinit var loginViewModel: LoginViewModel private lateinit var loginData: LoginUserData private lateinit var appContainer: AppContainer override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) appContainer = (application as MyApplication).appContainer // Login flow has started. Populate loginContainer in AppContainer appContainer.loginContainer = LoginContainer(appContainer.userRepository) loginViewModel = appContainer.loginContainer.loginViewModelFactory.create() loginData = appContainer.loginContainer.loginData } override fun onDestroy() { // Login flow is finishing // Removing the instance of loginContainer in the AppContainer appContainer.loginContainer = null super.onDestroy() } }
Java
public class LoginActivity extends Activity { private LoginViewModel loginViewModel; private LoginData loginData; private AppContainer appContainer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); appContainer = ((MyApplication) getApplication()).appContainer; // Login flow has started. Populate loginContainer in AppContainer appContainer.loginContainer = new LoginContainer(appContainer.userRepository); loginViewModel = appContainer.loginContainer.loginViewModelFactory.create(); loginData = appContainer.loginContainer.loginData; } @Override protected void onDestroy() { // Login flow is finishing // Removing the instance of loginContainer in the AppContainer appContainer.loginContainer = null; super.onDestroy(); } }
与 LoginActivity
一样,登录 fragment 可以从 AppContainer
访问 LoginContainer
并使用共享的 LoginUserData
实例。
因为在这种情况下,您需要处理视图生命周期逻辑,因此使用生命周期观察较为合理。
总结
依赖项注入对于创建可扩展且可测试的 Android 应用而言是一项适合的技术。将容器作为在应用的不同部分共享各个类实例的一种方式,以及使用工厂创建各个类实例的集中位置。
当应用变大时,您会发现您编写了大量样板代码(例如工厂),这可能容易出错。您还必须自行管理容器的作用域和生命周期,优化并舍弃不再需要的容器以释放内存。如果操作不当,可能会导致应用出现微小错误和内存泄露。
在 Dagger 部分中,您将学习如何使用 Dagger 自动执行该过程,并生成与手动编写相同的代码。