在Android应用中引入Dagger2之后,最常见的困局不是不会写注解,而是组件划分混乱导致依赖图失控。Activity作为系统实例化的组件,无法通过构造函数注入,只能借助字段注入或AndroidInjector机制。如果组件构建方式不合理,要么全局单例塞进Activity生命周期造成泄漏,要么每次创建重复对象拖慢启动。透彻理解组件作用域与注入时机,是搭建可维护依赖框架的前提。

Dagger2组件构建的核心思路:划分作用域与生命周期
组件是Dagger2中依赖图的入口,通过@Component注解定义。一个组件可以依赖模块提供实例,也可以通过dependencies或@Subcomponent关联其他组件。在Android中通常至少需要一个Application级别的组件,例如AppComponent,用来提供全局单例服务,如网络客户端、数据库、SharedPreferences等。这些对象生命周期与应用一致,用@Singleton标注,能够保证整个应用运行期间只创建一次。
Activity级别的依赖不能直接放在AppComponent中,因为那样会变成应用级单例,Activity销毁后对象仍被持有,造成内存泄漏。正确做法是定义ActivityComponent,作为AppComponent的子组件,或者使用独立的组件并通过依赖关系共享。子组件继承父组件的依赖,还能定义Activity特有的作用域注解,例如@PerActivity,保证同一Activity内注入的对象在Activity生命周期内复用。需要给子组件添加模块提供Activity相关依赖,比如Presenter或ViewModel工厂。
@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
ApiService apiService();
DatabaseHelper databaseHelper();
ActivityComponent.Builder activityComponentBuilder();
}
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface PerActivity {}
@PerActivity
@Subcomponent(modules = ActivityModule.class)
public interface ActivityComponent {
void inject(MainActivity activity);
@Subcomponent.Builder
interface Builder {
@BindsInstance
Builder activity(Activity activity);
ActivityComponent build();
}
}
@Module
public class ActivityModule {
@Provides
@PerActivity
MainPresenter provideMainPresenter(ApiService apiService, Activity activity) {
return new MainPresenter(activity, apiService);
}
}
这种分层结构的好处非常直接:全局服务不会被Activity持有引用,Activity销毁后子组件随之释放,减少内存泄漏风险。自定义@Scope本身是元注解,不包含实际逻辑,但Dagger2通过它保证在组件实例存活期间只创建一个带该作用域的依赖。例如MainPresenter在同一个Activity内注入多次,也只会返回同一个实例。另外,@Subcomponent让子组件可以继承父组件已经暴露的依赖,不需要反复声明,降低了组件之间的耦合。
与@Subcomponent不同,@Component(dependencies = ...)要求父组件显式暴露对象,子组件构建时手动传入依赖。这种方式虽然灵活,但在Activity场景下会增加不少样板代码,而且每次创建都要手动维护父组件引用。相比之下,@Subcomponent通过父组件的工厂方法创建子组件,生命周期更加清晰,更适合Activity这种频繁创建和销毁的组件。
Activity注入的两种方式:字段注入与构造函数注入的选择
构造函数注入是Dagger2最推荐的方式,但Activity由系统通过反射创建,无法在构造阶段提供依赖,因此只能使用字段注入。字段注入的核心是在Activity中声明@Inject成员变量,然后在onCreate里调用component.inject(this)完成依赖赋值。这种方式简单直接,但每个Activity都要手动调用一次注入逻辑,样板代码较多。
public class MainActivity extends AppCompatActivity {
@Inject
MainPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((MyApplication) getApplication()).getAppComponent()
.activityComponentBuilder()
.activity(this)
.build()
.inject(this);
presenter.loadData();
}
}
手动调用inject虽然直白,但每个Activity都要重复获取Application、构建组件、调用注入,容易忘记。为了消除这些模板代码,Dagger2提供了AndroidInjection和AndroidInjector,结合@ContributesAndroidInjector可以自动完成注入。需要引入dagger-android与dagger-android-support依赖,在Application中实现HasAndroidInjector或HasActivityInjector接口,并通过DispatchingAndroidInjector统一分发。Activity只需在super.onCreate()之前调用AndroidInjection.inject(this)即可。
@Singleton
@Component(modules = {
AndroidInjectionModule.class,
AppModule.class,
ActivityBindingModule.class
})
public interface AppComponent {
void inject(MyApplication application);
}
@Module
public abstract class ActivityBindingModule {
@ContributesAndroidInjector(modules = MainActivityModule.class)
abstract MainActivity bindMainActivity();
}
public class MainActivity extends AppCompatActivity {
@Inject
MainPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
presenter.loadData();
}
}
两种方式各有适用场景。手动构建组件的优点是依赖图可见、调试直观,适合小型项目或刚接触Dagger2的开发者;缺点则是Activity必须了解组件构建细节,代码重复。AndroidInjector方案解耦了Activity与具体组件,Activity只依赖AndroidInjection静态调用,便于测试和模块化,同时减少了大量的注入模板。不过AndroidInjector多了一层抽象,编译错误或运行时的依赖缺失可能更难以定位。如果项目规模较大且Activity较多,推荐使用@ContributesAndroidInjector;如果项目简单,手动构建组件加字段注入完全够用。
最佳实践:避免常见错误与性能优化
作用域不匹配是Dagger2使用中最典型的错误之一。如果某个依赖被@Singleton标注,却由ActivityComponent提供,Dagger2编译时会直接报错,因为应用级作用域不能依赖短生命周期组件。反过来,如果Activity依赖被AppComponent提供,会变成全局单例,导致Activity被静态引用无法回收。必须严格根据生命周期选择作用域,不要图省事把不同层级的依赖混在同一个组件中。
另一个常见错误是在Activity中保存Component引用。把ActivityComponent存为Activity成员变量,会在Activity销毁后继续持有依赖图,可能导致Activity无法被垃圾回收。正确做法是每次注入使用父组件的工厂方法临时创建子组件,或者依赖AndroidInjector自动管理。这样可以保证子组件的生命周期与Activity一致,不会意外延长对象存活时间。
@Subcomponent(modules = ActivityModule.class)
@PerActivity
public interface ActivityComponent {
@Subcomponent.Builder
interface Builder {
@BindsInstance
Builder activity(Activity activity);
Builder intent(Intent intent);
ActivityComponent build();
}
void inject(MainActivity activity);
}
使用@BindsInstance可以避免在Module中编写构造参数传递逻辑,让组件构建过程更加简洁。例如Activity实例或Intent参数都可以通过@BindsInstance直接绑定到依赖图中,不需要额外的@Module提供方法。这样减少了模块类的数量,也让依赖来源更加明确。在Application中构建子组件时,直接调用activityComponentBuilder().activity(this).intent(getIntent()).build()即可完成注入。
性能优化方面,尽量把无状态依赖设计为无作用域,每次注入时新建实例,避免长时间持有。对于创建成本高的对象如Retrofit、OkHttpClient,使用@Singleton集中管理。还可以使用Lazy<T>或Provider<T>延迟加载,减少初始化开销。例如Presenter中依赖HeavyService时,可以注入Provider<HeavyService>,在真正使用时才获取。但要注意Provider方法不要在循环中频繁调用,避免创建重复实例。测试性方面,使用@Component.Builder可以替换真实模块,注入Mock依赖,让单元测试和UI测试都更加容易编写。
合理的组件构建和注入方式能让项目在功能膨胀时保持结构清晰,核心是理解作用域与生命周期的对应关系。Activity应当只依赖注入、不持有组件引用,并借助AndroidInjector减少样板代码。这些实践落地后,Dagger2才能真正帮助你管理依赖,而不是变成另一种负担。

