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
的另一个实例,就会出现代码重复。依赖项必须按顺序声明。您必须先实例化
UserRepository
,然后才能创建LoginViewModel
。对象重用困难。如果您想在多个功能中重用
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); } }
您可以将 LoginViewModelFactory
包含在 AppContainer
中,并让 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 负责管理该容器的生命周期。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 自动化此过程,并生成您原本需要手动编写的相同代码。