博客 / 詳情

返回

Hilt 擴展 | MAD Skills

本文是 MAD Skills 系列 中有關 Hilt 的第四篇文章!在本文中,我們將探討如何編寫自定義的 Hilt 擴展。如果您需瞭解本系列前三篇文章,請查閲:

  • Hilt 介紹
  • Hilt 測試最佳實踐
  • Hilt 工作原理

如果您更喜歡通過視頻瞭解此內容,可以點擊 此處 查看。

案例: WorkManager 擴展

Hilt 擴展是一個生成代碼的庫,常通過註解處理器實現。生成的代碼作為構成 Hilt 依賴項注入關係圖的模塊或入口點。

Jetpack 中 WorkManager 的集成庫就是一個擴展的例子。WorkManager 擴展幫助我們減少向 worker 提供依賴項時所需的模板代碼及配置。該庫由兩部分組成,分別為 androidx.hilt:hilt-work 和 androidx.hilt:hilt-compiler。第一部分包含 HiltWorker 註解以及一些運行時的輔助類,第二部分是一個註解處理器,根據第一部分中註解提供的信息生成模塊。

擴展的使用非常簡單,僅需在您的 worker 上添加 @HiltWorker 註解:

@HiltWorker
public class ExampleWorker extends Worker {
   // ...
}

擴展編譯器會生成一個添加了 @Module 註解的類:

@Generated("androidx.hilt.AndroidXHiltProcessor")
@Module
@InstallIn(SingletonComponent.class)
@OriginatingElement(
    topLevelClass = ExampleWorker.class
)
public interface ExampleWorker_HiltModule {
    @Binds
    @IntoMap
    @StringKey("my.app.ExmapleWorker")
    WorkerAssistedFactory<? extends ListenableWorker> bind(
            ExampleWorker_AssistedFactory factory);
}

該模塊為 worker 定義了一個可以訪問 HiltWorkerFactory 的綁定。然後,配置 WorkerManager 使用該 factory,從而使 worker 的依賴項注入可用。

Hilt 聚合

啓用擴展的一個關鍵機制是 Hilt 能夠從類路徑中發現模塊和入口點。這被稱為聚合,因為模塊和入口點被聚合到帶有 @HiltAndroidApp 註解的 Application 中。

由於 Hilt 具有聚合能力,任何通過添加 @InstallIn 註解生成 @Module 及 @EntryPoint 的工具都可以被 Hilt 發現,並在編譯期成為 Hilt DI 圖中的一部分。這使得擴展可以輕鬆地以插件形式集成到 Hilt,無需開發者處理任何額外工作。

註解處理器

生成代碼的常規途徑是使用註解處理器。源文件轉換為 class 文件之前,註解處理器會在編譯器中運行。當資源帶有處理器所聲明的已支持的註解時,處理器會進行處理。處理器可以生成進一步需要被處理的方法,因此編譯器會不斷循環運行註解處理器,直到沒有新的內容產生。一旦所有的環節都完成,編譯器才會將源文件轉換為 class 文件。

△ 註解處理示意圖

△ 註解處理示意圖

由於循環機制,處理器可以相互作用。這非常重要,因為這使得 Hilt 的註解處理器可以處理由其他處理器生成的 @Module 或 @EntryPoint 類。這也意味着您的擴展也可以建立在其他人編寫的擴展之上!

WorkManager extension processor 根據帶有 @HiltWorker 註解的類生成代碼,同時驗證註解用法並使用 JavaPoet 等庫生成代碼。

Hilt 擴展註解

Hilt API 中有兩個重要的註解: @GeneratesRootInput 和 @OriginatingElement。擴展應該使用這些註解才能與 Hilt 正確集成。

擴展應該使用 @GeneratesRootInput 來啓用代碼生成的註解。這讓 Hilt 註解處理器知道它應該在生成組件之前完成擴展註解處理器的工作。例如,@HiltWorker 註解本身是被 @GeneratesRootInput 註解修飾的:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
@GeneratesRootInput
public @interface HiltWorker {
}

所生成的帶有 @Module、@EntryPoint 以及 @InstallIn 註解的類都需要添加 @OriginatingElement 註解,該註解的輸入參數是觸發模塊或入口點生成的頂層類。這就是 Hilt 判斷生成的模塊和入口點是否在本地測試的依據。例如,在 Hilt 測試中定義了一個添加 @HiltWorker 註解的內部類,模塊的初始元素就是測試值。

測試案例如下:

@HiltAndroidTest
class SampleTest {
    @HiltWorker
    class TestWorker extends Worker {
        // …
    }
}

生成的模塊包含 @OriginatingElement 註解:

@Module
@InstallIn(SingletonComponent.class)
@OriginatingElement(
    topLevelClass = SampleTest.class
)
public interface SampleTest_TestWorker__HiltModule {
    // …
}

心得

Hilt 擴展支持多種可能性,以下是創建擴展的一些心得:

項目中的通用模式

如果您的項目中有創建模塊或入口點的通用模式,那麼它們很大概率可以通過使用 Hilt 擴展實現自動化。舉個例子,如果每一個實現特定接口的類都必須創建一個具有多綁定的模塊,那麼可以創建一個擴展,只需在實現類上添加註解即可生成多重綁定模塊。

支持非標準成員注入

對於那些 Framework 中已經支持帶有實例化能力的成員注入類型,我們需要創建一個 @EntryPoint。如果有多種類型需要被成員注入,那麼自動創建入口點的擴展會很有用。例如,需要通過 ServiceLoader 發現服務實現的庫負責實例化發現的服務。為了將依賴項注入到服務實現中,必須創建一個 @EntryPoint。通過使用 Hilt 擴展,可以使用在實現類上添加註解完成自動生成入口點。擴展可以進一步生成代碼以使用入口點,例如由服務實現擴展的基類。這類似於 @AndroidEntryPoint 為 Activity 創建 @EntryPoint,並創建使用生成的入口點在 Activity 中執行成員注入的基類。

鏡像綁定

有時需要使用不同的限定符來鏡像或重新聲明綁定。當存在自定義組件時,這可能更常見。為了避免丟失重新聲明的綁定,可以創建 Hilt 擴展以自動生成其他鏡像綁定的模塊。例如,考慮包含不同依賴項實現的應用中 "付費" 和 "免費" 訂閲的情況。然後,每一層都有兩個不同的自定義組件,這樣您就可以確定依賴關係的作用域。當添加一個通用的未限定作用域的綁定時,定義綁定的模塊可以在其 @InstallIn 中包含兩個組件,也可以加載在父組件中,通常是單例組件。但是當綁定被限定作用域時,模塊必須被複制,因為需要不同的限定符。實現一個擴展就可以生成兩個模塊,可以避免樣板代碼並確保不會遺漏通用綁定。

總結

Hilt 的擴展可以進一步增強代碼庫中的依賴項注入能力,因為它們可以實現與 Hilt 尚不支持的其他庫集成。總而言之,擴展通常由兩部分組成,包含擴展註解的運行時部分,以及生成 @Module 或 @EntryPoint 的代碼生成器 (通常是註解處理器)。擴展的運行時部分可能有額外的輔助類,這些輔助類使用聲明在生成的模塊或入口點中綁定。代碼生成器還可能生成與擴展相關的附加代碼,它們無需專門生成模塊和入口點。

擴展必須使用兩個註解才能與 Hilt 正確交互:

  • @GeneratesRootInput 添加在擴展註解上。
  • @OriginatingElement 由擴展添加在生成的模塊或入口點上。

最後,您可以查看 hilt-install-binding 項目,這是一個簡單擴展的示例,它展示了本文中提到的概念。

以上便是 MAD Skills 系列關於 Hilt 的全部內容,如需觀看視頻全集,請移步到 Hilt - MAD Skills 播放列表。感謝閲讀本文!

歡迎您 點擊這裏 向我們提交反饋,或分享您喜歡的內容、發現的問題。您的反饋對我們非常重要,感謝您的支持!

user avatar dreamlu 頭像 mjlong123 頭像 idisfkj 頭像
3 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.