Hilt是什么?
Hilt是Android的依賴注入庫,可以減少在項目中執行手動依賴項注入的樣板代碼。執行手動依賴項注入需要手動構造每個類及其依賴項,并借助容器重復使用和管理依賴項。
和Degger有什么不同?
Hilt是在Degger的基礎上構建而成,更具場景化。Hilt通過為項目中的每個Android類提供容器并自動管理其生命周期,在簡化依賴注入使用的同時保留了Degger原有的強大功能。(類似于retrofit與okhttp的關系)
Hilt憑什么吸引我?
是數據共享和依賴管理,確切一點是生命周期內數據共享,是不是有點不知所云,沒關系咱看代碼
一個數據類:
@ActivityScoped
public class User {
private String name;
private String mood;
@Inject
public User() {
this("matthew", "百無聊賴");
}
public User(String name, String mood) {
this.name = name;
this.mood = mood;
}
......省略不重要的代碼
}
Activity
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var user: User
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
user.mood = "一切都好"
}
......省略不重要的代碼
}
一個自定義view繼承于AppCompatTextView ,它需要使用User對象的數據進行展示.它被放在了MainActivity的layout里
@AndroidEntryPoint
public class UserView extends androidx.appcompat.widget.AppCompatTextView {
@Inject
User user;
......省略不重要的代碼
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
setText(user.getName() + "現在的心情是" + user.getMood());
}
}
我們的使用場景是,需要在MainActivity里動態的去修改User里的值,然后UserView去展示修改過后的值。老司機門一定馬上就想到了,先在UserView添加一個接收User對象的方法,比如public void setUser(User user){...}
然后在activity里獲取userView的對象并調用userView.setUser函數把對象傳過去。這種方式我用了好多年了,但是接觸hilt之后瞬間感覺不優雅了。假設之后需求變了不需要這個User對象了呢?。。。。一頓刪除。 。。。。頭都大了吧
如果使用Hilt來依賴注入,我們只需要刪除注入的地方@Inject User user;
即可,就是這么簡單。
例子舉的還是有一點點不太理想
想在介紹一大堆枯燥的規則配置項之前先給大家講講怎么最簡單的實現上面的生命周期類共享數據(只講操作,為什么這么寫后面會講)
1、添加依賴項 (往下翻翻,不重復寫了)
2、給Applicaton添加@HiltAndroidApp注解
@HiltAndroidApp
public class ExampleApplication extends Application { ... }
3、準備需要注入的數據類,給無參構造函數添加@Inject
注解,給User類添加@ActivityScoped
注解
@ActivityScoped
public class User {
private String name;
private String mood;
@Inject
public User() {
this("matthew", "百無聊賴");
}
public User(String name, String mood) {
this.name = name;
this.mood = mood;
}
......省略不重要的代碼
}
4、需要時引入
給需要依賴注入的類添加@AndroidEntryPoint
注解
給對象聲明添加@Inject
注解
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var user: User
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
user.mood = "一切都好"
}
......省略不重要的代碼
}
@AndroidEntryPoint
public class UserView extends androidx.appcompat.widget.AppCompatTextView {
@Inject
User user;
......省略不重要的代碼
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
setText(user.getName() + "現在的心情是" + user.getMood());
}
}
至此就完成了!!!
Hilt怎么用
-
添加依賴項
首先,將 hilt-android-gradle-plugin 插件添加到項目的根級 build.gradle 文件中:
buildscript {
...
dependencies {
...
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
}
}
然后,應用 Gradle 插件并在 app/build.gradle 文件中添加以下依賴項:
...
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
android {
...
}
dependencies {
implementation "com.google.dagger:hilt-android:2.28-alpha"
kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}
Hilt 使用 Java 8 功能。如需在項目中啟用 Java 8,請將以下代碼添加到 app/build.gradle
文件中:
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
-
Hilt 目前支持以下 Android 類:
通過使用 @HiltAndroidApp
Application()
其它通過使用@AndroidEntryPoint
Activity
Fragment
View
Service
BroadcastReceiver
@HiltAndroidApp 會觸發 Hilt 的代碼生成操作,生成的代碼包括應用的一個基類,該基類充當應用級依賴項容器。
@HiltAndroidApp
public class ExampleApplication extends Application { ... }
在 Application 類中設置了 Hilt 且有了應用級組件后,Hilt 可以為帶有 @AndroidEntryPoint 注釋的其他 Android 類提供依賴項:
@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity { ... }
-
注入
如需從組件獲取依賴項,請使用 @Inject 注釋執行字段注入:
@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {
@Inject
AnalyticsAdapter analytics;
...
}
-
定義Hilt綁定
為了執行字段注入,Hilt需要知道如何從相應組件提供必要依賴項的實例,向Hilt提供綁定信息的一種方法是構造函數注入。在類的構造函數中使用@Inject
注釋以告知Hilt如何提供該類實例。
public class AnalyticsAdapter {
private final AnalyticsService service;
@Inject
AnalyticsAdapter(AnalyticsService service) {
this.service = service;
}
...
}
-
Hilt 模塊
上例中給出的構造函數需要一個參數,那么我們還需要告知Hilt如何獲得AnalyticsService的實例。很簡單啊!對AnalyticsService的構造函數添加@Inject
不就可以了。
但是有時候實例不能通過構造函數注入。發生這種情況可能有多種原因。例如,不能通過構造函數注入接口;或者不歸我們所有的類型,比如第三方庫;或者必須通過構造器構建的實例。
怎么辦?需要用到Hilt模塊,一種帶有@Module
注釋的類。它會告訴Hilt如何提供某些類型的實例。同時必須使用@InstallIn
為模塊添加注釋,告訴Hilt每個模塊將用在或安裝在那個Android類中。
-
使用 @Provides 注入實例
如果類不歸我們所有,或者需要使用構造器時,可以在Hilt模塊內創建一個函數添加 @Provides
注解
@Provides
注解會告訴Hilt以下信息
1、函數返回類型會告訴Hilt提供哪個類型的實例
2、函數參數或告訴Hilt相應類型的依賴項
3、函數體會告訴Hilt如何提供相應類型的實例
@Module
@InstallIn(ActivityComponent.class)
public class AnalyticsModule {
@Provides
@ActivityScoped //共享范圍
public static AnalyticsService provideAnalyticsService(
// Potential dependencies of this type
) {
return new Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(AnalyticsService.class);
}
}
-
使用 @Binds 注入接口實例
如果 AnalyticsService
是一個接口,則無法通過構造函數注入,而應向Hilt提供綁定信息,在Hilt模塊內創建一個帶有@Binds
注釋的抽象函數。
帶有注釋的函數會向Hilt提供以下信息
1、函數返回類型會告訴Hilt需要提供哪個接口的實例
2、函數參數會告訴Hilt要提供哪種實現
public interface AnalyticsService {
void analyticsMethods();
}
// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
public class AnalyticsServiceImpl implements AnalyticsService {
...
@Inject
AnalyticsServiceImpl(...) {
...
}
}
@Module
@InstallIn(ActivityComponent.class)
public abstract class AnalyticsModule {
@Binds
public abstract AnalyticsService bindAnalyticsService(
AnalyticsServiceImpl analyticsServiceImpl
);
}
-
Hilt組件
可以從中執行字段注入的每個Android類都有一個與之關聯的Hilt組件。可以在@InstallIn
中引入該組件,每個Hilt負責將其綁定注入相應的Android類。
Hilt 組件 | 注入器面向的對象 |
---|---|
ApplicationComponent | Application |
ActivityRetainedComponent | ViewModel |
ActivityComponent | Activity |
FragmentComponent | Fragment |
ViewComponent | View |
ViewWithFragmentComponent | 帶有 @WithFragmentBindings 注釋的 View |
ServiceComponent | Service |
那么比如使用@InstallIn(ActivityComponent.class)
將module注入到activity之后,activity下的view能否獲得依賴呢?具體規則是什么?
-
組件層級結構
將模塊安裝到組件后,其綁定就可以用做該組件內其它綁定的依賴項,也可以用作組件層級中該組件下的任何子組件中其它綁定的依賴項。就是向下使用關系
注意:默認情況下,如果您在視圖中執行字段注入,ViewComponent 可以使用 ActivityComponent 中定義的綁定。如果您還需要使用 FragmentComponent 中定義的綁定并且視圖是 Fragment 的一部分,應將 @WithFragmentBindings 注釋和 @AndroidEntryPoint 一起使用。
-
組件的聲明周期
Hilt會根據Android類的生命周期自動創建和銷毀組件類的實例
生成的組件 | 創建時機 | 銷毀時機 |
---|---|---|
ApplicationComponent | Application#onCreate() | Application#onDestroy() |
ActivityRetainedComponent | Activity#onCreate() | Activity#onDestroy() |
ActivityComponent | Activity#onCreate() | Activity#onDestroy() |
FragmentComponent | Fragment#onAttach() | Fragment#onDestroy() |
ViewComponent | View#super() | 視圖銷毀時 |
ViewWithFragmentComponent | View#super() | 視圖銷毀時 |
ServiceComponent | Service#onCreate() | Service#onDestroy() |
注意:ActivityRetainedComponent 在配置更改后仍然存在,因此它在第一次調用 Activity#onCreate() 時創建,在最后一次調用 Activity#onDestroy() 時銷毀。
-
作用域
如果使用@ActivityScoped
將AnalyticsService的作用域限定為ActivityComponent
,Hilt會在Activity的整個周期內提供AnalyticsService的同一實例
@Provides
@ActivityScoped //共享范圍
public static AnalyticsService provideAnalyticsService(
// Potential dependencies of this type
) {
return new Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(AnalyticsService.class);
}
Android 類 | 生成的組件 | 作用域 |
---|---|---|
Application | ApplicationComponent | @Singleton |
View Model | ActivityRetainedComponent | @ActivityRetainedScope |
Activity | ActivityComponent | @ActivityScoped |
Fragment | FragmentComponent | @FragmentScoped |
View | ViewComponent | @ViewScoped |
帶有 @WithFragmentBindings 注釋的 View | ViewWithFragmentComponent | @ViewScoped |
Service | ServiceComponent | @ServiceScoped |
-
Hilt中的預定義限定符
Hilt提供了一些自定義的限定符,例如開發中可能需要來自應用或者Activity的context對象。因此 Hilt 提供了 @ApplicationContext
和 @ActivityContext
限定符。
public class AnalyticsAdapter {
private final Context context;
private final AnalyticsService service;
@Inject
AnalyticsAdapter(
@ActivityContext Context context,
AnalyticsService service
) {
this.context = context;
this.service = service;
}
}
-
為同一類型提供多個綁定
我將這個放在了最后,為什么?因為按照我的使用習慣,這個用到的情況比較少。
比如現在需要獲取同一個接口的不同實例。上面講到了使用 @Binds 注入接口實例,但是現在需要多個不同的實例,該怎么辦?返回值類型都是一樣的啊!!!
首先,要定義用于@Binds
或@Provides
方法添加注釋的限定符
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
private @interface AuthInterceptorOkHttpClient {}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
private @interface OtherInterceptorOkHttpClient {}
然后,Hilt需要知道如何提供與每個限定符對應的類型的實例。下面的兩個方法具有相同的返回值類型,但是限定符將它們標記為兩個不同的綁定。
@Module
@InstallIn(ActivityComponent.class)
public class NetworkModule {
@AuthInterceptorOkHttpClient
@Provides
public static OkHttpClient provideAuthInterceptorOkHttpClient(
AuthInterceptor authInterceptor
) {
return new OkHttpClient.Builder()
.addInterceptor(authInterceptor)
.build();
}
@OtherInterceptorOkHttpClient
@Provides
public static OkHttpClient provideOtherInterceptorOkHttpClient(
OtherInterceptor otherInterceptor
) {
return new OkHttpClient.Builder()
.addInterceptor(otherInterceptor)
.build();
}
}
在使用過程中使用相應的限定符為字段或參數添加注釋來注入所需的特定類型:
// As a dependency of another class.
@Module
@InstallIn(ActivityComponent.class)
public class AnalyticsModule {
@Provides
public static AnalyticsService provideAnalyticsService(
@AuthInterceptorOkHttpClient OkHttpClient okHttpClient
) {
return new Retrofit.Builder()
.baseUrl("https://example.com")
.client(okHttpClient)
.build()
.create(AnalyticsService.class);
}
}
// As a dependency of a constructor-injected class.
public class ExampleServiceImpl ... {
private final OkHttpClient okHttpClient;
@Inject
ExampleServiceImpl(@AuthInterceptorOkHttpClient OkHttpClient okHttpClient) {
this.okHttpClient = okHttpClient;
}
}
// At field injection.
@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {
@AuthInterceptorOkHttpClient
@Inject
OkHttpClient okHttpClient;
...
}