一、控制反转与依赖注入的概念 控制反转(IoC, Inversion of Control)是一种设计原则,最通俗的理解是:不要让类自己创建它所依赖的对象,而是由外部(容器/框架)注入进来 。依赖注入(DI, Dependency Injection)是 IoC 的一种具体实现方式。
在传统写法中,类自己控制依赖的创建:
public class NewsRepository { private NewsApi newsApi; public NewsRepository () { this .newsApi = new Retrofit .Builder() .baseUrl("https://api.example.com" ) .build() .create(NewsApi.class); } }
使用依赖注入后:
public class NewsRepository { private final NewsApi newsApi; @Inject public NewsRepository (NewsApi newsApi) { this .newsApi = newsApi; } }
依赖注入的三个核心好处:
可测试性 :可以在测试中传入 mock 对象。
松耦合 :NewsRepository 不需要知道 NewsApi 是如何创建的。
可替换性 :更换实现只需修改注入配置,无需改动依赖方代码。
二、三种注入方式 2.1 构造函数注入(推荐方式) public class UserViewModel { private final UserRepository repository; @Inject public UserViewModel (UserRepository repository) { this .repository = repository; } }
构造函数注入的优势是:(1) 依赖在对象创建时就确定,不会出现 NPE;(2) 可以将依赖声明为 final;(3) 便于发现循环依赖——如果 A 依赖 B 且 B 依赖 A,编译时会检测到。
2.2 字段注入 public class MainActivity extends AppCompatActivity { @Inject UserViewModel viewModel; @Override protected void onCreate (Bundle savedInstanceState) { ((MyApplication) getApplication()).getAppComponent().inject(this ); super .onCreate(savedInstanceState); } }
字段注入的缺点:(1) 依赖不可 final;(2) 容易出现忘记触发注入的情况;(3) 在单元测试中难以替换依赖。Android 中主要用于 Activity/Fragment 等不能控制构造函数创建的对象。
2.3 方法注入 public class MainActivity extends AppCompatActivity { private UserViewModel viewModel; @Inject public void setViewModel (UserViewModel viewModel) { this .viewModel = viewModel; } }
方法注入使用较少,主要用于向后兼容(已经有 setter)或需要对注入的依赖做额外处理。
三、编译期 DI:Dagger/Hilt 原理 Dagger(https://github.com/google/dagger)是 Android 生态中最主流的编译期 DI 框架,由 Google 维护。其核心原理是通过 APT(Annotation Processing Tool)在编译期生成工厂类,完全避免运行时的反射开销。
3.1 核心概念 Component(组件) :依赖注入的容器和桥梁。定义哪些模块提供什么依赖,注入给哪些目标。
@Component(modules = {NetworkModule.class, DatabaseModule.class}) @Singleton public interface AppComponent { NewsApi getNewsApi () ; UserDao getUserDao () ; void inject (MainActivity activity) ; }
Module(模块) :提供对象创建逻辑的地方,尤其是对第三方库(Retrofit、Room)的实例化。
@Module public class NetworkModule { @Provides @Singleton public NewsApi provideNewsApi () { return new Retrofit .Builder() .baseUrl("https://api.example.com" ) .addConverterFactory(GsonConverterFactory.create()) .build() .create(NewsApi.class); } }
Scope(作用域) :控制依赖的生命周期。@Singleton 是内置的作用域,此外可自定义:
@Scope @Retention(RetentionPolicy.RUNTIME) public @interface ActivityScope {}@Scope @Retention(RetentionPolicy.RUNTIME) public @interface FragmentScope {}
作用域确保:同一个 Component 实例中,被同一作用域标记的依赖只会创建一次。
Qualifier(限定符) :区分同类型的多个依赖实例。
@Qualifier @Retention(RetentionPolicy.RUNTIME) public @interface LoggingInterceptor {}@Qualifier @Retention(RetentionPolicy.RUNTIME) public @interface AuthInterceptor {}@Module public class NetworkModule { @Provides @LoggingInterceptor public Interceptor provideLoggingInterceptor () { return new HttpLoggingInterceptor (); } @Provides @AuthInterceptor public Interceptor provideAuthInterceptor () { return chain -> chain.proceed( chain.request().newBuilder() .addHeader("Authorization" , "Bearer " + token) .build() ); } }
3.2 Component 的实现原理:生成代码解析 Dagger 为每个 @Component 注解的接口生成一个 DaggerXXXComponent 实现类。以下是实际的生成代码解析:
public final class DaggerAppComponent implements AppComponent { private final NetworkModule networkModule; private DaggerAppComponent (NetworkModule networkModuleParam) { this .networkModule = networkModuleParam; } public static Builder builder () { return new Builder (); } @Override public NewsApi getNewsApi () { return NetworkModule_ProvideNewsApiFactory.provideNewsApi(networkModule); } @Override public void inject (MainActivity activity) { injectMainActivity(activity); } private MainActivity injectMainActivity (MainActivity instance) { MainActivity_MembersInjector.injectViewModel(instance, new UserViewModel (DataModule_ProvideNewsApiFactory.provideNewsApi(networkModule))); return instance; } public static final class Builder { private NetworkModule networkModule; public Builder networkModule (NetworkModule module ) { this .networkModule = Objects.requireNonNull(module ); return this ; } public AppComponent build () { if (networkModule == null ) { this .networkModule = new NetworkModule (); } return new DaggerAppComponent (networkModule); } } }
Dagger 生成的每个 @Provides 方法对应一个 Factory 类:
public final class NetworkModule_ProvideNewsApiFactory implements Factory <NewsApi> { private final NetworkModule module ; public NetworkModule_ProvideNewsApiFactory (NetworkModule module ) { this .module = module ; } @Override public NewsApi get () { return provideNewsApi(module ); } public static NewsApi provideNewsApi (NetworkModule instance) { return Preconditions.checkNotNull( instance.provideNewsApi(), "Cannot return null from a non-nullable @Provides method" ); } public static NetworkModule_ProvideNewsApiFactory create (NetworkModule module ) { return new NetworkModule_ProvideNewsApiFactory (module ); } }
3.3 Subcomponent(子组件) Subcomponent 用于将 Component 的生命周期与 Activity/Fragment 绑定:
@ActivityScope @Subcomponent(modules = {ActivityModule.class}) public interface ActivityComponent { void inject (MainActivity activity) ; @Subcomponent .Factory interface Factory { ActivityComponent create (@BindsInstance Context context) ; } }
Subcomponent 继承了父 Component 的所有依赖,同时有自己的作用域和生命周期。当 Activity 销毁时,Subcomponent 也被释放,其管理的依赖随之被 GC。
3.4 Binds 与 Provides 的区别 @Module public abstract class DatabaseModule { @Binds abstract UserDataSource bindUserDataSource (LocalUserDataSource impl) ; @Provides @Singleton public AppDatabase provideAppDatabase (Context context) { return Room.databaseBuilder(context, AppDatabase.class, "app.db" ).build(); } }
四、运行时 DI:Koin Koin(https://github.com/InsertKoinIO/koin)是 Kotlin 编写的轻量级 DI 框架,基于 Kotlin DSL 和函数式编程,无需 APT。
val appModule = module { single { Retrofit.Builder().baseUrl("https://api.example.com" ).build().create(NewsApi::class .java) } single { UserRepository(get ()) } } val viewModelModule = module { viewModel { UserViewModel(get ()) } } class MyApp : Application () { override fun onCreate () { super .onCreate() startKoin { androidContext(this @MyApp ) modules(appModule, viewModelModule) } } } class MainActivity : AppCompatActivity () { private val viewModel: UserViewModel by viewModel() }
Koin 通过 Kotlin 的 inline + reified 实现类型解析,在运行时通过映射表查找依赖。其优点是配置简洁,无需 kapt(编译速度更快);缺点是依赖在运行时解析,编译期无法发现依赖未绑定的错误。
五、Dagger vs Koin vs Hilt
特性
Dagger
Koin
Hilt
原理
编译期 APT
运行时反射
编译期 APT(封装 Dagger)
编译速度
慢(kapt)
快(无 kapt)
慢但封装简单
配置复杂度
高
低
低(自动化)
错误发现
编译期
运行时
编译期
Kotlin 支持
一般
原生
优良
性能
无反射开销
有轻微运行时开销
无反射开销
生产推荐
中大型项目
中小型/快速原型
官方推荐
六、面试常问题目 Q1: Dagger 的 @Component 和 @Subcomponent 有什么区别?
@Component 是独立的依赖容器,有自己的作用域和生命周期,通常对应 Application 级别。@Subcomponent 是 Component 的子容器,继承父 Component 的所有依赖,但有自己的作用域(如 @ActivityScope),生命周期与其宿主(如 Activity)绑定。当 Activity 销毁时,Subcomponent 被释放,其管理的 @ActivityScope 依赖也随之释放,防止内存泄漏。
Q2: Dagger 如何解决循环依赖问题?
当 A 的构造函数依赖 B,B 的构造函数依赖 A 时,形成循环依赖。Dagger 会在编译期检测到这种循环并报错 “Found a dependency cycle”。解决方法是:将其中一个依赖改为 Provider<T> 或 Lazy<T> 延迟注入,或者引入第三个类 C 打破循环,或者将其中一个注入方式从构造函数注入改为字段注入。
Q3: @Binds 和 @Provides 有什么不同?为什么 @Binds 方法必须是 abstract 的?
@Binds 用于接口到实现的绑定,方法必须是 abstract 的且只有一个参数(实现类型),Dagger 直接知道”当请求接口时,返回这个实现”即可,无需方法体。@Provides 用于需要 new 对象或进行复杂创建逻辑的场景,方法必须有方法体。@Binds 比 @Provides 更高效,因为 Dagger 不需要调用方法体,直接使用实现类的构造函数。
Q4: Hilt 相对 Dagger 做了哪些简化?
Hilt 自动生成了 Component 层级(SingletonComponent → ViewModelComponent → ActivityComponent → FragmentComponent 等),无需手动定义 @Subcomponent;自动注入 Android 类(@AndroidEntryPoint 标记 Activity/Fragment 等),无需手动调用 inject();预定义 @ApplicationContext 和 @ActivityContext 限定符;集成 Jetpack ViewModel,一行 @HiltViewModel 即可。
参考源码路径:
Dagger:https://github.com/google/dagger
Dagger APT 核心:dagger-compiler/src/main/java/dagger/internal/codegen/ComponentProcessor.java
Hilt:https://dagger.dev/hilt/
Koin:https://github.com/InsertKoinIO/koin
Guice(Google 的运行时 DI):https://github.com/google/guice