點贊關注,再也不迷路,你的支持對我意義重大!java
🔥 Hi,我是醜醜。本文 GitHub · Android-NoteBook 已收錄,這裏有 Android 進階成長路線筆記 & 博客,歡迎跟着彭醜醜一塊兒成長。(聯繫方式在 GitHub)android
這篇文章的內容會涉及如下前置 / 相關知識,貼心的我都幫你準備好了,請享用~git
依賴注入(Dependency Injection,簡稱 DI)其實並非一個很神祕的概念,每每在不經意地間咱們就使用了依賴注入。依賴注入應用了 「控制反轉(IoC)」 的原理,簡單來講就是在類的外部構造依賴項,使用構造器或者 setter 注入。github
提示: 你每每在不經意間使用了依賴注入的思想。markdown
使用依賴注入能夠爲咱們帶來什麼好處呢?app
當只有一個依賴項時,手動進行依賴注入很簡單,但隨着項目規模變大,手動注入會變得愈來愈複雜。而使用依賴注入框架,可讓依賴注入的過程更加簡便,另外,依賴注入框架每每還提供了管理依賴項的生命週期的功能。從實現上,依賴注入框架能夠歸爲兩類:框架
提示:依賴注入框架本質上不是提供了依賴注入的能力,而是採用了註解等方式讓依賴注入變得更加簡易。ide
在這裏面,Dagger2 和 Hilt 是咱們今天討論的主題。工具
Dagger2: Dagger 的名字取自有向無環圖(DAG,Directed acyclic graph),最初由 Square 組織開發,然後來的 Dagger2 和 Hilt 框架則由 Square 和 Google 共同開發維護。oop
Hilt: Hilt 是 Dagger2 的二次封裝,Hilt 本質上是對 Dagger 進行場景化。它爲 Android 平臺制定了一系列規則,大大簡化了 Dagger2 的使用。在 Dagger2 裏,你須要手動獲取依賴圖和執行注入操做,而在 Hilt 裏,注入會自動完成,由於 Hilt 會自動找到 Android 系統組件中那些最佳的注入位置。
下面,咱們分別來討論 Dagger2 和 Hilt 兩個框架。本來我不打算介紹太多 Dagger2 的內容(由於在 Android 裏咱們是直接使用 Hilt),考慮到二者的關係仍是以爲仍是有必要把 Dagger2 講清楚,才能真正理解 Hilt 幫咱們作了什麼。
提示: 我在學習 Dagger2 時,也閱讀了不少文章和官方文檔。有些做者會列舉出全部註解的用法,有些做者只介紹用法而忽略解釋自動生成的代碼。我也在尋求一種易於理解 / 接受的講法,最後我以爲先「基礎註解」再「複雜註解」,邊介紹用法邊解釋自動生成代碼的方式,或許是更容易理解的方式。期待獲得你的反饋~
在討論的過程當中,咱們經過一個簡單的例子來展開:假設咱們有一個用戶數據模塊,它依賴於兩個依賴項:
public class UserRepository {
private final UserLocalDataSource userLocalDataSource;
private final UserRemoteDataSource userRemoteDataSource;
public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
this.userLocalDataSource = userLocalDataSource;
this.userRemoteDataSource = userRemoteDataSource;
}
}
複製代碼
首先,你能夠選擇不使用依賴注入,那麼你可能就會在項目多處重複構建,缺點咱們在第一節都討論過了。
new UserRepository(new UserLocalDataSource(), new UserRemoveDataSource());
複製代碼
後來,有追求的你已經開始使用依賴注入,你寫了一個全局的工具方法:
public static UserRepository get() {
return new UserRepository(new UserLocalDataSource(), new UserRemoveDataSource());
}
複製代碼
這確實能知足需求,然而在真實項目中,模塊之間的依賴關係每每比這個例子要複雜得多。此時,若是常常手動編寫依賴注入的模板代碼,不只耗時耗力,也容易出錯。下面,咱們開始使用 Dagger2 這個幫手來替咱們編寫模板代碼。
@Component 和 @Inject 是 Dagger2 最基礎的兩個註解,僅使用這兩個註解就能夠實現最簡單的依賴注入。
@Component
public interface ApplicationComponent {
UserRepository userRepository();
}
複製代碼
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 註解修飾依賴項的構造方法,同時,它的依賴項 UserLocalDataSource 和 UserRemoteDataSource 也須要增長 @Inject 註解。
以上代碼在構建後會自動生成代碼:
DaggerApplicationComponent.java
一、實現 ApplicationComponent 接口
public final class DaggerApplicationComponent implements ApplicationComponent {
private DaggerApplicationComponent() {
}
二、建立依賴項實例
@Override
public UserRepository userRepository() {
return new UserRepository(new UserLocalDataSource(), new UserRemoteDataSource());
}
三、構建者模式
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();
}
}
}
複製代碼
能夠看到,最簡單的依賴注入模板代碼已經自動生成了。使用時,你只須要經過 ApplicationComponent 這個入口就能夠得到 UserReopsitory 實例:
ApplicationComponent component = DaggerApplicationComponent.create();
UserRepository userRepository = component.userRepository();
複製代碼
有些類不是使用構造器初始化的,例如 Android 框架類 Activity 和 Fragment 由系統實例化,此時就不能再使用 3.1 節 中使用的構造器注入,能夠改成字段注入,並手動調用方法請求注入。
構造器注入:(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 中使用時,須要注意組件的生命週期:
在 super.onCreate() 中的恢復階段,Activity 會附加綁定的 Fragment,這些 Fragment 可能須要訪問 Activity。爲保證數據一致性,應在調用 super.onCreate() 以前在 Activity 的 onCreate() 方法中注入 Dagger。
在使用 Fragment 時,應在 Fragment 的 onAttach() 方法中注入 Dagger,此操做能夠在調用 super.onAttach() 以前或以後完成。
@Singleton
public class UserRepository {
...
}
--------------------------------------------
@Component
@Singleton
public interface ApplicationComponent {
...
}
複製代碼
在 ApplicationComponent 和 UserRepository 上使用相同的做用域註解,代表二者處於同一個做用域週期。這意味着,同一個 Component 屢次提供該依賴項都是同一個實例。你能夠直接使用內置的 @Singleton,也可使用自定義註解:
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}
--------------------------------------------
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCustomScope {}
複製代碼
提示: 使用 @Singleton 或 @MyCustomScope,效果是徹底同樣的。
以上代碼在構建後會自動生成代碼:
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();
}
...
}
複製代碼
有幾個關於做用域註解的約束,你須要注意下:
提示: 關於子組件的概念,你能夠看 第 3.5 節。
只要你知足上面提到的約束規則,Dagger2 框架並不嚴格限制你定義的做用域語義。你能夠按照業務劃分做用域,也能夠按照生命週期劃分做用域。例如:
按照業務劃分:
@Singleton
@LoginScope
@RegisterScope
--------------------------------------------
按聲明週期劃分:
@Singleton
@ActivityScope
@ModuleScope
@FeatureScope
複製代碼
不過,按照生命週期劃分做用域是更加理想的作法,做用域不該該明確指明其實現目的。
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 不一樣的提供對象實例的方式。在 @Module 裏,@Provides 方法的返回值是依賴項實例,而參數是進一步依賴的對象。另外,你還須要在 @Component 參數中應用該模塊。
目前爲止,咱們構造的依賴關係圖以下所示:
子組件是繼承並擴展父組件的對象圖的組件,子組件中的對象就能夠依賴於父組件中提供的對象,可是父組件不能依賴於子組件依賴的對象(簡單的包含關係,對吧?)。
咱們繼續經過一個簡單的例子來展開:假設咱們有一個登陸模塊 LoginActivity,它依賴於 LoginModel。咱們的需求是定義一個子組件,它的聲明週期只在一次登陸流程中存在。在 第 3.2 節 提過,Activity 沒法使用構造器注入,因此 LoginActivity 咱們採用的是 @Inject 字段注入的語法:
@Subcomponent
public interface LoginComponent {
void inject(LoginActivity activity);
}
複製代碼
可是這樣定義的 LoginComponent 還不能真正稱爲某個組件的子組件,須要增長額外聲明:
@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);
}
複製代碼
在這裏,咱們須要定義一個新模塊 SubcomponentModule,同時須要在 LoginComponent 中定義子組件 Factory,以便 ApplicationComponent 知道如何建立 LoginComponent 的示例。
如今,LoginComponent 就算聲明完成了。爲了讓 LoginComponent 保持和 LoginActivity 相同的生命週期,你應該在 LoginActivity 內部建立 LoginComponent 實例,並持有引用:
public class LoginActivity extends Activity {
一、持有子組件引用,保證相同生命週期
LoginComponent loginComponent;
二、@Inject 字段注入
@Inject
LoginViewModel loginViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
三、建立子組件實例
loginComponent = ((MyApplication) getApplicationContext())
.appComponent.loginComponent().create();
四、注入
loginComponent.inject(this);
...
}
}
複製代碼
執行到步驟 4 ,loginViewModel 字段就初始化完成了。這裏有一個須要特別注意的點,你思考這個問題:若是你在 LoginActivity 中的一個 Fragment 重複注入 LoginViewModel,它是一個對象嗎?
@Subcomponent
public interface LoginComponent {
@Subcomponent.Factory
interface Factory {
LoginComponent create();
}
void inject001(LoginActivity loginActivity);
void inject002(LoginUsernameFragment fragment);
}
複製代碼
確定是不一樣對象的,由於咱們尚未使用 第 3.3 節 提到的 @Singleton / @Scope 做用域註解。如今咱們增長做用域註解:
@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;
}
}
複製代碼
目前爲止,咱們構造的依賴關係圖以下所示:
當一個項目應用了 Dagger2 或者其它依賴注入框架,那麼在必定程度上它的各個組件之間是處於一種鬆耦合的狀態,此時進行單元測試顯得遊刃有餘。
在 Dagger2 項目上你能夠選擇在不一樣級別上注入模擬依賴項:
你能夠定義一個 FakeLoginViewModel,而後替換到 LoginActivity:
public class LoginActivity extends Activity {
一、持有子組件引用,保證相同生命週期
LoginComponent loginComponent;
二、@Inject 字段注入
@Inject
FakeLoginViewModel loginViewModel;
}
複製代碼
你可爲爲正式版和測試版定義兩個組件:ApplicationComponent 和 TestApplicationComponent:
@Singleton
@Component(modules = {FakeNetworkModule.class, SubcomponentsModule.class})
public interface TestApplicationComponent extends ApplicationComponent {
}
複製代碼
總結一下咱們提到的註解:
註解 | 描述 |
---|---|
@Component | 建立一個 Dagger 容器,做爲獲取依賴項的入口 |
@Inject | 指示 Dagger 如何實例化一個對象 |
@Singleton / @Scope | 做用域,能夠約束依賴項的做用域週期 |
@Module + @Providers | 指示 Dagger 如何實例化一個對象,但不是以構造器的方式 |
@Subcomponent | 聲明子組件,使用子組件的概念能夠定義更加細緻的做用域 |
創做不易,你的「三連」是醜醜最大的動力,咱們下次見!