依赖注入:Dagger 2 完整实战
Dagger 2 完整实战:核心注解 @Inject / @Provides / @Module / @Component、@Singleton 与自定义 Scope、@Qualifier 限定符、Component 依赖与 Subcomponent、AndroidInjector 以及测试中替换 Module。
学习目标
- 掌握 Dagger 2 核心注解的语义与用法
- 理解 @Singleton 与自定义 Scope 的作用域控制
- 能够使用 @Qualifier 区分同类型多个绑定
- 使用 AndroidInjector 简化 Activity / Fragment 注入
- 在测试中通过替换 Module 提供假依赖
本章定位:Dagger 2 是 Java + Views 项目中依赖注入(DI)的成熟方案,也是 Hilt 的底层基础。Kotlin 项目中 Google 推荐 Hilt(基于 Dagger 的封装),但纯 Java 项目无法使用 Hilt(Hilt 依赖 Kotlin 协程与 Kotlin 注解处理)。因此 Dagger 2 仍是 Java 存量项目 DI 的最佳实践。Kotlin 迁移时可在过渡期继续使用 Dagger 2,再逐步评估是否迁移到 Hilt。
与 Part 8-10 的对应关系:Kotlin + Compose 路线请使用 Hilt,参见 Part 8-10 Kotlin + Compose 相关章节。
学习目标
- 掌握 Dagger 2 核心注解
@Inject/@Provides/@Module/@Component的语义与用法; - 理解
@Singleton与自定义 Scope 的作用域控制; - 能够使用
@Qualifier区分同类型多个绑定; - 使用
AndroidInjector简化 Activity / Fragment 注入; - 在测试中通过替换 Module 提供假依赖。
依赖注入:Dagger 2 完整实战
为什么需要依赖注入
依赖注入(Dependency Injection, DI)的核心思想是 “调用方不创建依赖,由容器提供依赖”。在没有 DI 的代码里,类往往 new 出自己的依赖:
public class LoginPresenter {
private final LoginManager manager = new LoginManager(new HttpService());
}
这导致:
- 难以测试:替换
LoginManager或HttpService必须改源码; - 耦合度高:高层模块依赖低层模块的具体实现;
- 复用困难:依赖创建逻辑散落各处。
Dagger 2 是 Android 生态中最成熟的 编译期 DI 框架:通过注解处理器在编译期生成连接代码,没有运行时反射开销,错误在编译期暴露。
依赖配置
plugins {
id "com.android.application" version "8.5.0"
}
android {
compileSdk 34
// Java 8+ 必须开启
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation "androidx.appcompat:appcompat:1.7.0"
// Dagger 2 核心
implementation "com.google.dagger:dagger:2.51.1"
annotationProcessor "com.google.dagger:dagger-compiler:2.51.1"
// dagger.android(可选,用于 Activity/Fragment 注入)
implementation "com.google.dagger:dagger-android:2.51.1"
implementation "com.google.dagger:dagger-android-support:2.51.1"
annotationProcessor "com.google.dagger:dagger-android-processor:2.51.1"
}
核心注解
@Inject:标注注入点
@Inject 用在三处:
- 构造方法:告诉 Dagger 用这个构造方法创建实例(最常用);
- 字段:让 Dagger 在创建实例后给字段赋值;
- 方法:让 Dagger 在创建实例后调用方法注入参数。
public class LoginManager {
@Inject
public LoginManager(HttpService http, UserCache cache) {
// 构造方法注入,Dagger 会自动解析 HttpService 与 UserCache
}
}
public class LoginActivity extends AppCompatActivity {
@Inject LoginManager loginManager; // 字段注入
}
构造方法注入是首选,便于单元测试时直接 new 实例。
@Module + @Provides:无法构造注入时的提供者
接口、第三方类、Context 等无法在构造方法上加 @Inject,需用 @Module 类中的 @Provides 方法提供:
@Module
public class NetworkModule {
@Provides
public HttpService provideHttpService(OkHttpClient client) {
return new HttpServiceImpl(client);
}
@Provides
public OkHttpClient provideOkHttpClient() {
return new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.build();
}
}
@Provides 方法的参数同样由 Dagger 解析,形成依赖图。
@Component:组装依赖图
@Component 把多个 @Module 装配成一个可执行依赖图,并暴露入口方法:
@Component(modules = {NetworkModule.class, LoginModule.class})
public interface AppComponent {
// 方式 1:直接返回顶级依赖
LoginManager loginManager();
// 方式 2:注入字段(用于无法构造注入的类,如 Activity)
void inject(LoginActivity activity);
}
编译时 Dagger 生成 DaggerAppComponent,使用方式:
AppComponent component = DaggerAppComponent.builder()
.networkModule(new NetworkModule())
.build();
LoginManager manager = component.loginManager();
完整最小示例
public class HttpService { } // 接口/类
public class UserCache { } // 简单 POJO
public class LoginManager { // 业务类
@Inject
public LoginManager(HttpService http, UserCache cache) {
// ...
}
}
@Module
public class LoginModule {
@Provides
public HttpService provideHttpService() { return new HttpService(); }
@Provides
public UserCache provideUserCache() { return new UserCache(); }
}
@Component(modules = LoginModule.class)
public interface AppComponent {
LoginManager loginManager();
}
// 调用
AppComponent c = DaggerAppComponent.create();
LoginManager m = c.loginManager();
@Singleton 与自定义 Scope
@Singleton
@Singleton 是 Dagger 内置的作用域,标注 @Provides 方法或 @Inject 构造方法后,该实例在同一个 Component 内 只创建一次:
@Module
public class NetworkModule {
@Provides
@Singleton
public OkHttpClient provideOkHttpClient() {
return new OkHttpClient.Builder().build();
}
}
@Component(modules = NetworkModule.class)
@Singleton // Component 必须也声明对应 Scope,否则编译报错
public interface AppComponent {
OkHttpClient client();
}
注意 @Singleton 仅在 当前 Component 实例范围 内单例。如果每次都 DaggerAppComponent.create(),会得到不同的“单例”。通常把 AppComponent 放在 Application 中复用。
自定义 Scope
@Singleton 本质只是一个自定义 Scope,可以创建自己的 Scope 来表达更细粒度的生命周期:
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope { }
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface FragmentScope { }
@ActivityScope
@Subcomponent(modules = LoginModule.class)
public interface LoginComponent {
void inject(LoginActivity activity);
}
@Module(subcomponents = LoginComponent.class)
public abstract class LoginActivityBinding { }
约定俗成:Scope 名称与 Component 生命周期对应(ActivityScope 对应 Activity 子组件,FragmentScope 对应 Fragment 子组件)。
@Qualifier:区分同类型多个绑定
当同一类型有多个绑定(如多个 String 配置项、生产 vs 调试 OkHttpClient),用 @Qualifier 注解区分:
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiKey { }
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface DebugHttp { }
@Module
public class NetworkModule {
@Provides
@ApiKey
public String provideApiKey() { return "secret-key"; }
@Provides
@DebugHttp
public OkHttpClient provideDebugClient() {
return new OkHttpClient.Builder()
.addInterceptor(new HttpLoggingInterceptor().setLevel(Level.BODY))
.build();
}
}
public class ApiService {
private final String apiKey;
@Inject
public ApiService(@ApiKey String apiKey) { this.apiKey = apiKey; }
}
如果忘了限定符,Dagger 在编译期直接报错 “Multiple … bindings”,避免运行时混淆。
也可以用 Dagger 内置的 @Named("xxx"),但自定义注解有编译期类型检查与重命名友好性。
Component 依赖与 Subcomponent
Component Dependencies
通过 dependencies 让一个 Component 复用另一个 Component 已暴露的依赖:
@Component(modules = {AppModule.class})
@Singleton
public interface AppComponent {
UserCache userCache(); // 必须显式暴露,否则下游拿不到
}
@Component(modules = {LoginModule.class}, dependencies = AppComponent.class)
@ActivityScope
public interface LoginComponent {
void inject(LoginActivity activity);
}
下游 Component 调用方式:
AppComponent app = ...;
LoginComponent login = DaggerLoginComponent.builder()
.appComponent(app)
.build();
Subcomponent
Subcomponent 是更紧密的子图,能直接访问父 Component 所有依赖,无需显式暴露:
@ActivityScope
@Subcomponent(modules = LoginModule.class)
public interface LoginComponent {
void inject(LoginActivity activity);
@Subcomponent.Builder
interface Builder {
LoginComponent build();
}
}
@Module(subcomponents = LoginComponent.class)
public abstract class LoginActivityModule {
@Provides
@IntoMap
@ActivityKey(LoginActivity.class)
abstract AndroidInjector.Factory<? extends Activity>
bindLoginFactory(LoginComponent.Builder builder);
}
子组件适合父子生命周期强包含的场景(如 Activity 隶属于 App)。Component dependencies 适合两个独立图复用关系。
AndroidInjector:dagger.android 简化
直接为每个 Activity 写 inject() 与 Component 是繁琐的,dagger.android 通过 @ContributesAndroidInjector 自动生成对应 Subcomponent:
public class App extends Application implements HasAndroidInjector {
@Inject DispatchingAndroidInjector<Object> androidInjector;
@Override
public AndroidInjector<Object> androidInjector() {
return androidInjector;
}
@Override
public void onCreate() {
super.onCreate();
DaggerAppComponent.create().inject(this);
}
}
@Singleton
@Component(modules = {AppModule.class, AndroidInjectionModule.class})
public interface AppComponent {
void inject(App app);
}
@Module
public abstract class ActivityBindingModule {
@ActivityScope
@ContributesAndroidInjector(modules = LoginModule.class)
abstract LoginActivity contributeLoginActivity();
}
public class LoginActivity extends AppCompatActivity {
@Inject LoginManager loginManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this); // 注入字段
super.onCreate(savedInstanceState);
// ...
}
}
AndroidInjectionModule 来自 dagger.android,提供 DispatchingAndroidInjector 的支撑代码。
测试中替换 Module
依赖注入最大的好处之一是 可测试性。在测试时通过提供一个测试 Module 来替换真实依赖:
生产 Module
@Module
public class NetworkModule {
@Provides
@Singleton
public ApiService provideApiService(OkHttpClient client) {
return new RealApiService(client);
}
@Provides
@Singleton
public OkHttpClient provideOkHttpClient() {
return new OkHttpClient.Builder().build();
}
}
@Component(modules = NetworkModule.class)
@Singleton
public interface AppComponent {
ApiService apiService();
void inject(LoginPresenter presenter);
}
测试 Module
@Module
public class TestNetworkModule {
@Provides
@Singleton
public ApiService provideApiService() {
return new FakeApiService(); // 返回固定假数据
}
@Provides
@Singleton
public OkHttpClient provideOkHttpClient() {
return new OkHttpClient(); // 不需要真实网络
}
}
@Component(modules = TestNetworkModule.class)
@Singleton
public interface TestAppComponent extends AppComponent {
// 注意:测试 Component 必须用相同签名复写所有用到的入口
ApiService apiService();
void inject(LoginPresenter presenter);
}
单元测试
public class LoginPresenterTest {
private TestAppComponent component;
@Before
public void setUp() {
component = DaggerTestAppComponent.create();
}
@Test
public void loginSuccess_callsShowSuccess() {
LoginPresenter p = new LoginPresenter();
component.inject(p);
p.login("admin", "123456");
// 断言逻辑(FakeApiService 永远返回成功)
}
}
关键约束:测试 Module 必须与生产 Module 提供完全相同的依赖键(同类型 + 同 Qualifier),否则编译期报错。这也是 Dagger 编译期检查的价值之一。
常见坑与最佳实践
- Component 忘记加 Scope:
@Provides @Singleton的依赖,对应 Component 必须也标@Singleton,否则编译报错。 - Subcomponent 没在父 Module 声明:Subcomponent 必须通过父 Component 的某个
@Module(subcomponents = ...)声明才能被 Dagger 识别。 - 在 Application 中每次 create 新 Component:会让
@Singleton失效,应在Application字段里缓存一个实例。 - 直接暴露 Activity 子组件:Activity 注入应通过
AndroidInjection.inject(this),不要在 AppComponent 上手动inject(LoginActivity)。 - 用 @Named 字符串易拼错:自定义
@Qualifier注解比@Named("debug")更安全,编译期可发现拼写错误。 - 测试 Component 与生产 Component 签名不一致:测试 Component 必须复写所有需要的入口方法签名。
- 循环依赖:A 构造依赖 B,B 构造依赖 A,Dagger 在编译期直接报错,需要拆解为字段注入或引入 Provider。
- Module 方法写成 private:
@Provides方法必须可被生成代码访问,不能是 private,否则编译失败。
章节小结
本章完整覆盖了 Dagger 2 的核心注解、@Singleton 与自定义 Scope、@Qualifier 限定符、Component 依赖与 Subcomponent 两种组装方式、AndroidInjector 简化 Activity/Fragment 注入,以及测试中替换 Module 提供假依赖。Dagger 2 的最大价值在于编译期检查与零运行时开销,配合合理的 Module 拆分能让测试替换非常自然。
下一章预告
下一章将进入异步编程领域,使用 RxJava 3:基础类型(Observable / Flowable / Single / Maybe / Completable)、常用操作符(map / flatMap / filter / concatMap)、线程调度(subscribeOn / observeOn)、背压策略、与 Android Lifecycle 互操作(RxJavaAndroid)、内存泄漏与 dispose / CompositeDisposable。