
點(diǎn)贊關(guān)注,不再迷路,你的支持對(duì)我意義重大!
?? Hi,我是丑丑。本文 「Android 路線」| 導(dǎo)讀 —— 從零到無窮大 已收錄,這里有 Android 進(jìn)階成長(zhǎng)路線筆記 & 博客,歡迎跟著彭丑丑一起成長(zhǎng)。(聯(lián)系方式在 GitHub)
前言
- 依賴注入是項(xiàng)目組件解耦中非常重要的一個(gè)手段,Dagger2 和 Hilt 是在 Android 中最主要的依賴注入框架;
- 在這篇文章里,我將總結(jié) Dagger2 的使用方法,如果能幫上忙,請(qǐng)務(wù)必點(diǎn)贊加關(guān)注,這真的對(duì)我非常重要。
目錄

前置知識(shí)
這篇文章的內(nèi)容會(huì)涉及以下前置 / 相關(guān)知識(shí),貼心的我都幫你準(zhǔn)備好了,請(qǐng)享用~
- APT 【點(diǎn)贊催更】
- Java | 注解(含 Kotlin)
1. 為什么要進(jìn)行依賴注入
依賴注入(Dependency Injection,簡(jiǎn)稱 DI)其實(shí)并不是一個(gè)很神秘的概念,往往在不經(jīng)意地間我們就使用了依賴注入。依賴注入應(yīng)用了 “控制反轉(zhuǎn)(IoC)” 的原理,簡(jiǎn)單來說就是在類的外部構(gòu)造依賴項(xiàng),使用構(gòu)造器或者 setter 注入。
提示: 你往往在不經(jīng)意間使用了依賴注入的思想。

使用依賴注入可以為我們帶來什么好處呢?
- 重用組件: 因?yàn)槲覀冊(cè)陬愅獠繕?gòu)造依賴項(xiàng);
- 組件解耦: 當(dāng)我們需要修改某個(gè)組件的實(shí)現(xiàn)時(shí),不需要在項(xiàng)目中進(jìn)行大量變更;
- 易測(cè)試: 我們可以向依賴方注入依賴項(xiàng)的模擬實(shí)現(xiàn),這使得依賴方的測(cè)試更加容易;
- 生命周期透明: 依賴方不感知依賴項(xiàng)創(chuàng)建 / 銷毀的生命周期,這些可以交給依賴注入框架管理。
2. Android 依賴注入框架
當(dāng)只有一個(gè)依賴項(xiàng)時(shí),手動(dòng)進(jìn)行依賴注入很簡(jiǎn)單,但隨著項(xiàng)目規(guī)模變大,手動(dòng)注入會(huì)變得越來越復(fù)雜。而使用依賴注入框架,可以讓依賴注入的過程更加簡(jiǎn)便,另外,依賴注入框架往往還提供了管理依賴項(xiàng)的生命周期的功能。從實(shí)現(xiàn)上,依賴注入框架可以歸為兩類:
- 1、基于反射的動(dòng)態(tài)方案: Guice、Dagger;
- 2、基于編譯時(shí)注解的靜態(tài)方案(性能更高): Dagger2、Hilt、ButterKnife。
提示:依賴注入框架本質(zhì)上不是提供了依賴注入的能力,而是采用了注解等方式讓依賴注入變得更加簡(jiǎn)易。
在這里面,Dagger2 和 Hilt 是我們今天討論的主題。
Dagger2: Dagger 的名字取自有向無環(huán)圖(DAG,Directed acyclic graph),最初由 Square 組織開發(fā),而后來的 Dagger2 和 Hilt 框架則由 Square 和 Google 共同開發(fā)維護(hù)。
Hilt: Hilt 是 Dagger2 的二次封裝,Hilt 本質(zhì)上是對(duì) Dagger 進(jìn)行場(chǎng)景化。它為 Android 平臺(tái)制定了一系列規(guī)則,大大簡(jiǎn)化了 Dagger2 的使用。在 Dagger2 里,你需要手動(dòng)獲取依賴圖和執(zhí)行注入操作,而在 Hilt 里,注入會(huì)自動(dòng)完成,因?yàn)?Hilt 會(huì)自動(dòng)找到 Android 系統(tǒng)組件中那些最佳的注入位置。
下面,我們分別來討論 Dagger2 和 Hilt 兩個(gè)框架。原本我不打算介紹太多 Dagger2 的內(nèi)容(因?yàn)樵?Android 里我們是直接使用 Hilt),考慮到兩者的關(guān)系還是覺得還是有必要把 Dagger2 講清楚,才能真正理解 Hilt 幫我們做了什么。
3. Dagger2 使用教程
提示: 我在學(xué)習(xí) Dagger2 時(shí),也閱讀了很多文章和官方文檔。有些作者會(huì)列舉出所有注解的用法,有些作者只介紹用法而忽略解釋自動(dòng)生成的代碼。我也在尋求一種易于理解 / 接受的講法,最后我覺得先「基礎(chǔ)注解」再「復(fù)雜注解」,邊介紹用法邊解釋自動(dòng)生成代碼的方式,或許是更容易理解的方式。期待得到你的反饋~
在討論的過程中,我們通過一個(gè)簡(jiǎn)單的例子來展開:假設(shè)我們有一個(gè)用戶數(shù)據(jù)模塊,它依賴于兩個(gè)依賴項(xiàng):
public class UserRepository {
private final UserLocalDataSource userLocalDataSource;
private final UserRemoteDataSource userRemoteDataSource;
public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
this.userLocalDataSource = userLocalDataSource;
this.userRemoteDataSource = userRemoteDataSource;
}
}

首先,你可以選擇不使用依賴注入,那么你可能就會(huì)在項(xiàng)目多處重復(fù)構(gòu)建,缺點(diǎn)我們?cè)诘谝还?jié)都討論過了。
new UserRepository(new UserLocalDataSource(), new UserRemoveDataSource());
后來,有追求的你已經(jīng)開始使用依賴注入,你寫了一個(gè)全局的工具方法:
public static UserRepository get() {
return new UserRepository(new UserLocalDataSource(), new UserRemoveDataSource());
}
這確實(shí)能滿足需求,然而在真實(shí)項(xiàng)目中,模塊之間的依賴關(guān)系往往比這個(gè)例子要復(fù)雜得多。此時(shí),如果經(jīng)常手動(dòng)編寫依賴注入的模板代碼,不僅耗時(shí)耗力,也容易出錯(cuò)。下面,我們開始使用 Dagger2 這個(gè)幫手來替我們編寫模板代碼。
3.1 @Component + @Inject
@Component 和 @Inject 是 Dagger2 最基礎(chǔ)的兩個(gè)注解,僅使用這兩個(gè)注解就可以實(shí)現(xiàn)最簡(jiǎn)單的依賴注入。
- @Component:創(chuàng)建一個(gè) Dagger 容器,作為獲取依賴項(xiàng)的入口
@Component
public interface ApplicationComponent {
UserRepository userRepository();
}
- @Inject:指示 Dagger 如何實(shí)例化一個(gè)對(duì)象
public class UserRepository {
private final UserLocalDataSource userLocalDataSource;
private final UserRemoteDataSource userRemoteDataSource;
@Inject
public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
this.userLocalDataSource = userLocalDataSource;
this.userRemoteDataSource = userRemoteDataSource;
}
}
--------------------------------------------
public class UserLocalDataSource {
@Inject
public UserLocalDataSource() {
}
}
--------------------------------------------
public class UserRemoveDataSource {
@Inject
public UserRemoveDataSource() {
}
}
你需要用 @Inject 注解修飾依賴項(xiàng)的構(gòu)造方法,同時(shí),它的依賴項(xiàng) UserLocalDataSource 和 UserRemoteDataSource 也需要增加 @Inject 注解。
以上代碼在構(gòu)建后會(huì)自動(dòng)生成代碼:
DaggerApplicationComponent.java
1、實(shí)現(xiàn) ApplicationComponent 接口
public final class DaggerApplicationComponent implements ApplicationComponent {
private DaggerApplicationComponent() {
}
2、創(chuàng)建依賴項(xiàng)實(shí)例
@Override
public UserRepository userRepository() {
return new UserRepository(new UserLocalDataSource(), new UserRemoteDataSource());
}
3、構(gòu)建者模式
public static Builder builder() {
return new Builder();
}
public static ApplicationComponent create() {
return new Builder().build();
}
public static final class Builder {
private Builder() {
}
public ApplicationComponent build() {
return new DaggerApplicationComponent();
}
}
}
可以看到,最簡(jiǎn)單的依賴注入模板代碼已經(jīng)自動(dòng)生成了。使用時(shí),你只需要通過 ApplicationComponent 這個(gè)入口就可以獲得 UserReopsitory 實(shí)例:
ApplicationComponent component = DaggerApplicationComponent.create();
UserRepository userRepository = component.userRepository();
3.2 @Inject 字段注入
有些類不是使用構(gòu)造器初始化的,例如 Android 框架類 Activity 和 Fragment 由系統(tǒng)實(shí)例化,此時(shí)就不能再使用 3.1 節(jié) 中使用的構(gòu)造器注入,可以改為字段注入,并手動(dòng)調(diào)用方法請(qǐng)求注入。
構(gòu)造器注入:(X)
public class MyActivity {
@Inject
public MyActivity(LoginViewModel viewModel){
...
}
}
--------------------------------------------
字段注入:
class MainActivity : AppCompatActivity() {
@Inject
lateinit var viewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
DaggerApplicationComponent.create().inject001(this)
super.onCreate(savedInstanceState)
...
}
}
public class LoginViewModel {
private final UserRepository userRepository;
@Inject
public LoginViewModel(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
在 Activity 或 Fragment 中使用時(shí),需要注意組件的生命周期:
在 super.onCreate() 中的恢復(fù)階段,Activity 會(huì)附加綁定的 Fragment,這些 Fragment 可能需要訪問 Activity。為保證數(shù)據(jù)一致性,應(yīng)在調(diào)用 super.onCreate() 之前在 Activity 的 onCreate() 方法中注入 Dagger。
在使用 Fragment 時(shí),應(yīng)在 Fragment 的 onAttach() 方法中注入 Dagger,此操作可以在調(diào)用 super.onAttach() 之前或之后完成。
3.3 @Singleton / @Scope
- @Singleton / @Scope:聲明作用域,可以約束依賴項(xiàng)的作用域周期
@Singleton
public class UserRepository {
...
}
--------------------------------------------
@Component
@Singleton
public interface ApplicationComponent {
...
}
在 ApplicationComponent 和 UserRepository 上使用相同的作用域注解,表明兩者處于同一個(gè)作用域周期。這意味著,同一個(gè) Component 多次提供該依賴項(xiàng)都是同一個(gè)實(shí)例。你可以直接使用內(nèi)置的 @Singleton,也可以使用自定義注解:
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}
--------------------------------------------
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCustomScope {}
提示: 使用 @Singleton 或 @MyCustomScope,效果是完全一樣的。
以上代碼在構(gòu)建后會(huì)自動(dòng)生成代碼:
public final class DaggerApplicationComponent implements ApplicationComponent {
private Provider<UserRepository> userRepositoryProvider;
private DaggerApplicationComponent() {
initialize();
}
private void initialize() {
this.userRepositoryProvider = DoubleCheck.provider(UserRepository_Factory.create(UserLocalDataSource_Factory.create(), UserRemoteDataSource_Factory.create()));
}
@Override
public UserRepository userRepository() {
return userRepositoryProvider.get();
}
...
}
作用域注解約束
有幾個(gè)關(guān)于作用域注解的約束,你需要注意下:
- 如果某個(gè)組件有作用域注解,那么該組件只能給提供帶有該注解的類或者不帶任何作用域注解的類;
- 子組件不能使用和某個(gè)父組件的相同的作用域注解。
提示: 關(guān)于子組件的概念,你可以看 第 3.5 節(jié)。
作用域注解規(guī)范
只要你滿足上面提到的約束規(guī)則,Dagger2 框架并不嚴(yán)格限制你定義的作用域語義。你可以按照業(yè)務(wù)劃分作用域,也可以按照生命周期劃分作用域。例如:
按照業(yè)務(wù)劃分:
@Singleton
@LoginScope
@RegisterScope
--------------------------------------------
按聲明周期劃分:
@Singleton
@ActivityScope
@ModuleScope
@FeatureScope
不過,按照生命周期劃分作用域是更加理想的做法,作用域不應(yīng)該明確指明其實(shí)現(xiàn)目的。
3.4 @Module + @Providers
- @Module + @Providers:指示 Dagger 如何實(shí)例化一個(gè)對(duì)象,但不是以構(gòu)造器的方式
public class UserRemoteDataSource {
private final LoginRetrofitService loginRetrofitService;
@Inject
public UserRemoteDataSource(LoginRetrofitService loginRetrofitService) {
this.loginRetrofitService = loginRetrofitService;
}
}
--------------------------------------------
@Module
public class NetworkModule {
@Provides
public LoginRetrofitService provide001(OkHttpClient client) {
return new Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(LoginService.class);
}
}
--------------------------------------------
@Singleton
@Component(modules = NetworkModule.class)
public interface ApplicationComponent {
UserRepository userRepository();
void inject001(MainActivity activity);
}
@Module 模塊提供了一種與 @Inject 不同的提供對(duì)象實(shí)例的方式。在 @Module 里,@Provides 方法的返回值是依賴項(xiàng)實(shí)例,而參數(shù)是進(jìn)一步依賴的對(duì)象。另外,你還需要在 @Component 參數(shù)中應(yīng)用該模塊。
目前為止,我們構(gòu)造的依賴關(guān)系圖如下所示:

3.5 @Subcomponent
- @Subcomponent:聲明子組件,使用子組件的概念可以定義更加細(xì)致的作用域
子組件是繼承并擴(kuò)展父組件的對(duì)象圖的組件,子組件中的對(duì)象就可以依賴于父組件中提供的對(duì)象,但是父組件不能依賴于子組件依賴的對(duì)象(簡(jiǎn)單的包含關(guān)系,對(duì)吧?)。
我們繼續(xù)通過一個(gè)簡(jiǎn)單的例子來展開:假設(shè)我們有一個(gè)登錄模塊 LoginActivity,它依賴于 LoginModel。我們的需求是定義一個(gè)子組件,它的聲明周期只在一次登錄流程中存在。在 第 3.2 節(jié) 提過,Activity 無法使用構(gòu)造器注入,所以 LoginActivity 我們采用的是 @Inject 字段注入的語法:
@Subcomponent
public interface LoginComponent {
void inject(LoginActivity activity);
}
但是這樣定義的 LoginComponent 還不能真正稱為某個(gè)組件的子組件,需要增加額外聲明:
@Module(subcomponents = LoginComponent.class)
public class SubComponentsModule {
}
--------------------------------------------
@Component(modules = {NetworkModule.class,SubComponentsModule.class})
@Singleton
public interface ApplicationComponent {
UserRepository userRepository();
LoginComponent.Factory loginComponent();
}
--------------------------------------------
@Subcomponent
public interface LoginComponent {
@Subcomponent.Factory
interface Factory{
LoginComponent create();
}
void inject001(LoginActivity activity);
}
在這里,我們需要定義一個(gè)新模塊 SubcomponentModule,同時(shí)需要在 LoginComponent 中定義子組件 Factory,以便 ApplicationComponent 知道如何創(chuàng)建 LoginComponent 的示例。
現(xiàn)在,LoginComponent 就算聲明完成了。為了讓 LoginComponent 保持和 LoginActivity 相同的生命周期,你應(yīng)該在 LoginActivity 內(nèi)部創(chuàng)建 LoginComponent 實(shí)例,并持有引用:
public class LoginActivity extends Activity {
1、持有子組件引用,保證相同生命周期
LoginComponent loginComponent;
2、@Inject 字段注入
@Inject
LoginViewModel loginViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
3、創(chuàng)建子組件實(shí)例
loginComponent = ((MyApplication) getApplicationContext())
.appComponent.loginComponent().create();
4、注入
loginComponent.inject(this);
...
}
}
執(zhí)行到步驟 4 ,loginViewModel 字段就初始化完成了。這里有一個(gè)需要特別注意的點(diǎn),你思考這個(gè)問題:如果你在 LoginActivity 中的一個(gè) Fragment 重復(fù)注入 LoginViewModel,它是一個(gè)對(duì)象嗎?
@Subcomponent
public interface LoginComponent {
@Subcomponent.Factory
interface Factory {
LoginComponent create();
}
void inject001(LoginActivity loginActivity);
void inject002(LoginUsernameFragment fragment);
}
肯定是不同對(duì)象的,因?yàn)槲覀冞€沒有使用 第 3.3 節(jié) 提到的 @Singleton / @Scope 作用域注解?,F(xiàn)在我們?cè)黾幼饔糜蜃⒔猓?/p>
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {}
@ActivityScope
@Subcomponent
public interface LoginComponent { ... }
@ActivityScope
public class LoginViewModel {
private final UserRepository userRepository;
@Inject
public LoginViewModel(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
目前為止,我們構(gòu)造的依賴關(guān)系圖如下所示:

4. 在 Dagger2 的基礎(chǔ)上進(jìn)行單元測(cè)試
當(dāng)一個(gè)項(xiàng)目應(yīng)用了 Dagger2 或者其它依賴注入框架,那么在一定程度上它的各個(gè)組件之間是處于一種松耦合的狀態(tài),此時(shí)進(jìn)行單元測(cè)試顯得游刃有余。
在 Dagger2 項(xiàng)目上你可以選擇在不同級(jí)別上注入模擬依賴項(xiàng):
4.1 對(duì)象級(jí)別:
你可以定義一個(gè) FakeLoginViewModel,然后替換到 LoginActivity:
public class LoginActivity extends Activity {
1、持有子組件引用,保證相同生命周期
LoginComponent loginComponent;
2、@Inject 字段注入
@Inject
FakeLoginViewModel loginViewModel;
}
4.2 組件級(jí)別
你可為為正式版和測(cè)試版定義兩個(gè)組件:ApplicationComponent 和 TestApplicationComponent:
@Singleton
@Component(modules = {FakeNetworkModule.class, SubcomponentsModule.class})
public interface TestApplicationComponent extends ApplicationComponent {
}
5. 總結(jié)
總結(jié)一下我們提到的注解:
| 注解 | 描述 |
|---|---|
| @Component | 創(chuàng)建一個(gè) Dagger 容器,作為獲取依賴項(xiàng)的入口 |
| @Inject | 指示 Dagger 如何實(shí)例化一個(gè)對(duì)象 |
| @Singleton / @Scope | 作用域,可以約束依賴項(xiàng)的作用域周期 |
| @Module + @Providers | 指示 Dagger 如何實(shí)例化一個(gè)對(duì)象,但不是以構(gòu)造器的方式 |
| @Subcomponent | 聲明子組件,使用子組件的概念可以定義更加細(xì)致的作用域 |
參考資料
- 【Dagger · 官網(wǎng)】【Hilt · 官網(wǎng)】【Dagger2 · Github】
- 《Android 中的依賴項(xiàng)注入》系列文檔 —— Android Developers(必看)
- 《Tasting Dagger 2 on Android.》 —— Fernando Cejas 著
- 《從 Dagger 到 Hilt,谷歌為何執(zhí)著于讓我們用依賴注入?》 —— 扔物線 著
- 《Jetpack 新成員,一篇文章帶你玩轉(zhuǎn) Hilt 和依賴注入》 —— 郭霖 著
- 《Android Studio 4.1 的 Dagger 導(dǎo)航更新》 —— Android Developers
- 《在 Kotlin 中使用 Dagger 會(huì)遇到的陷阱和優(yōu)化方法》 —— Android Developers
創(chuàng)作不易,你的「三連」是丑丑最大的動(dòng)力,我們下次見!
