Jetpack架構組件學習(5)——Hilt 註入框架使用

来源:https://www.cnblogs.com/stars-one/p/18364360
-Advertisement-
Play Games

原文: Jetpack架構組件學習(5)——Hilt 註入框架使用-Stars-One的雜貨小窩 本篇需要有Kotlin基礎知識,否則可能閱讀本篇會有所困難! 介紹說明 實際上,郭霖那篇文章已經講得比較明白了(具體參考鏈接都貼在下文了),這裡簡單總結下: 如果按照之前我們的MVC寫法,我們可以直接在 ...


原文: Jetpack架構組件學習(5)——Hilt 註入框架使用-Stars-One的雜貨小窩

本篇需要有Kotlin基礎知識,否則可能閱讀本篇會有所困難!

介紹說明

實際上,郭霖那篇文章已經講得比較明白了(具體參考鏈接都貼在下文了),這裡簡單總結下:

如果按照之前我們的MVC寫法,我們可以直接在activity中發起網路請求,但發起網路請求我們需要調用一個Api對象的具體方法,而Api對象只能在activity中進行創建

這裡activity和api對象實際上就是耦合關係,從客觀上講,我們activity不應該去負責創建一個api對象

所以使用註入框架,相當於有了個中間人幫activity處理,至於是中間人直接找到api對象,或者是中間人進行創建api對象,activity都不關心,activity只知道去找中間人就能幫得到一個api對象

優點

學習之後,目前感覺到的優點:

  1. 可能大型項目,多module那種比較適合
  2. MMVM/MVI架構的app也比較適合
  3. 註入介面,方便不同邏輯實現

可能就是介面註入可能有些用處,比如上面例子,假設我們網路框架剛開始用的是okhttp,但後期可能又會變更其他框架,我們可以考慮封裝一個通用介面,然後使用依賴註入,後期更換其他網路框架只需要實現介面的對應方法即可

依賴註入比較針對是MVVM/MVI架構的app,傳統mvc結構,我直接一個單例object,也可以解決問題,好像也沒啥必要?

網上大多數說的都是解耦,方便後續測試,問題是我都不怎麼寫測試用例,實在無法感受到具體好處就是

總之,目前學習這個只是因為很多開源項目都開始用上了,學了這個發現大概才能看得懂哈哈,也順手做下記錄了

基本使用

1.依賴引入

註: 下麵我使用的是ksl的gradle腳本

項目的build.kts文件

buildscript {
    ...
    dependencies {
        ...
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.48.1'
    }
}

在app模塊里的build.kts加上插件和依賴

plugins {
    id("com.android.application")
    id("kotlin-kapt") // kotlin-kapt 插件
    id("dagger.hilt.android.plugin") // Hilt 插件
}

dependencies {	
	implementation("com.google.dagger:hilt-android:2.48.1")
	kapt("com.google.dagger:hilt-compiler:2.48.1")
}

//還有記得有下麵此數據配置,不過一般都預設有
android{	
	compileOptions {
		sourceCompatibility = JavaVersion.VERSION_1_8
		targetCompatibility = JavaVersion.VERSION_1_8
	}
}

我這裡直接新版本as創建的新項目,已經使用了toml+ksl的方式,貼下圖參考下:

toml:

build.gradle.kt

app里的build.gradle.kt

這裡有個坑:

就是ksp和kapt一起使用會導致編譯失敗,得設置插件不傳遞,及上面的build.gradle.kt截圖

2.application上加註解

@HiltAndroidApp
class MyApplication:Application() {
    override fun onCreate() {
        super.onCreate()
		//...
    }
}

註意在清單文件中使用MyApplication對象哦!

<application
	android:name=".MyApplication"
	//省略其他...
/>

3.註入對象

class MyApi @Inject constructor(){
	fun sendApi(){
		
	}
}


@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

	/**
	 * 這裡不能是private,且是懶載入的方式
	 */
	@Inject
	lateinit var api: MyApi
		
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

補充說明

我們註意到上面出現了3個註解

  • @HiltAndroidApp 在application上使用
  • @AndroidEntryPoint 在activity等類上使用此註解,下麵有補充說明
  • @Inject 用來註入對象及標識需要註入實體

其中@HiltAndroidApp是在application中使用的,而

hilt有以下入口點:

  • Application
  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver

也就是說,我們使用依賴註入功能只能在這幾個類中

對於application,我們使用@HiltAndroidApp註解,這步是必須的,否則依賴註入不會生效!

而另外的@AndroidEntryPoint註解,在哪裡你需要使用到@Inject註入對象,則需要將當前類標明上註解@AndroidEntryPoint,(如上個步驟中的代碼示例)

進階使用

1.帶參實體註入

給之前的MyApi添加個新的構造參數

class MyApi @Inject constructor(val client:Client){
    fun sendApi(){

    }
}

class Client @Inject constructor(){
    fun config() {
        
    }
}

總結: 需要依賴註入的實體,如果有其他參數,則保證其他參數實體也是有依賴註入即可

上面的MyApi,也可以寫成下麵這樣:

class MyApi @Inject constructor(){
	@Inject
    lateinit var client: Client
	
    fun sendApi(){

    }
}

2.介面類型註入

import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import javax.inject.Inject


interface ClientInterface{
    fun config()
}

class MyClient @Inject constructor():ClientInterface{
    override fun config() {
        Log.d("ttt", "myclient config ")
    }
}

class MyApi @Inject constructor(){

    @Inject
    lateinit var clientInterface: ClientInterface

    fun sendApi(){
        clientInterface.config()
        Log.d("ttt", "send api")
    }
}

@Module
@InstallIn(ActivityComponent::class)
abstract class ClientModule {
    @Binds
    abstract fun createClient(myClient: MyClient): ClientInterface

}

上面定義一個ClientInterface介面,我們要註入一個此介面實現類MyClient

得多加一個ClientModule類,依賴註入的時候會通過此類中的對應方法createClient來註入

這裡類和方法名都是可以隨意,不過方法里的參數就是你要註入的介面實現類,返回則是介面類,還要註意該類是抽象類!

關於@InstallIn註解,在下麵章節會再次講解,這裡先跳過,先這樣使用即可

PS:當然這裡可以也可以不是介面類型,改成抽象類應該也是可以的!

3.相同類型不同實例註入

在上面介面類型註入的代碼上加入一個新的類進行註入

class MyTwoClient @Inject constructor():ClientInterface{
    override fun config() {
        Log.d("ttt", "mytwoclient config ")
    }
}

@Module
@InstallIn(ActivityComponent::class)
abstract class ClientModule {
    @BindMyClient
    @Binds
    abstract fun createClient(myClient: MyClient): ClientInterface


	/**
	 * 註意這個方法名不能與上面createClient相同,否則編譯會失敗!
	 */
    @BindMyTwoClient
    @Binds
    abstract fun createTwoClient(myClient: MyTwoClient): ClientInterface
}

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindMyClient

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindMyTwoClient

修改要註入對象的地方:

class MyApi @Inject constructor(){

	/**
	 * 這裡使用@BindMyTwoClient來標明我們要註入MyTwoClient實例
	 */
    @BindMyTwoClient
    @Inject
    lateinit var clientInterface: ClientInterface

    fun sendApi(){
        clientInterface.config()
        Log.d("ttt", "send api")
    }
}

4.外部第三方實體註入

這裡說的第三方,指的是第三方庫,由於庫里基本封裝好了,代碼修改不像上面那麼自由,可能還沒有構造方法,那我們應該如何實現註入?

這裡是使用@Provides註解來實現

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import javax.inject.Inject
import javax.inject.Qualifier

class MyApi @Inject constructor(){

	/**
	 * 指定MyClient,依賴註入最終會調用後面createNClient()方法生成對象
	 */
    @Inject
    lateinit var clientInterface: MyClient

    fun sendApi(){
        Log.d("ttt", "send api")
    }
}

class MyClient {
    fun config() {
        Log.d("ttt", "myclient config ")
    }
}

@Module
@InstallIn(ActivityComponent::class)
class ClientModule {
    @Provides
    fun createNClient(): MyClient{
		//這裡方便延時,我直接通過創建一個實例了
        return MyClient()
    }
}

和上面介面類型註入不同,需要註意以下幾點:

  1. ClientModule這個類不是抽象類了
  2. @Provides註解的那個方法,也不是抽象方法,且不用@Inject註解標明

或者方法還能加個參數(依賴其他類):

@Module
@InstallIn(ActivityComponent::class)
class ClientModule {
    @Provides
    fun createNClient(): MyClient{
        return MyClient()
    }
    
	/**
	 * myClient這個也會被自動註入上面我們的那個返回數據
	 */
    @Provides
    fun createNewApi(myClient: MyClient): MyNewApi{
        return MyNewApi(myClient)
    }
    
}

class MyNewApi(myClient: MyClient)

組件和組件作用域

介紹

上面有個@InstallIn,翻譯就是安裝到的意思

@InstallIn(ActivityComponent::class): 就是把這個模塊安裝到 Activity 組件當中;

如果我們在Service中使用@Inject註入,則編譯時就會提示出錯,原因是ActivityComponent已經限定只能在activity里使用

當然,除了ActivityComponent這個組件,我們還有其他的組件可用,如下表

Android 類 組件 作用域
Application SingletonComponent @Singleton
Activity ActivityRetainedComponent @ActivityRetainedScoped
ViewModel ViewModelComponent @ViewModelScoped
Activity ActivityComponent @ActivityScoped
Fragment FragmentComponent @FragmentScoped
View ViewComponent @ViewScoped
帶有 @WithFragmentBindings 註解的 View ViewWithFragmentComponent @ViewScoped
Service ServiceComponent @ServiceScoped
  • @Singleton 被它修飾的構造函數或是函數,返回的始終是同一個實例
  • @ActivityRetainedScoped 被它修飾的構造函數或是函數,在Activity的重建前後返回同一實例
  • @ActivityScoped 被它修飾的構造函數或是函數,在同一個Activity對象里,返回的都是同一實例
  • @ViewModelScoped 被它修飾的構造函數或是函數,與ViewModel規則一致

組件的生命周期

生成的組件 創建時機 銷毀時機
SingletonComponent Application#onCreate() Application 已銷毀
ActivityRetainedComponent Activity#onCreate() Activity#onDestroy()
ViewModelComponent ViewModel 已創建 ViewModel 已銷毀
ActivityComponent Activity#onCreate() Activity#onDestroy()
FragmentComponent Fragment#onAttach() Fragment#onDestroy()
ViewComponent View#super() View 已銷毀
ViewWithFragmentComponent View#super() View 已銷毀
ServiceComponent Service#onCreate() Service#onDestroy()

依賴註入實現單例

一般情況下,我們的api全局應該是單例模式,所以上面的可以改成下麵代碼:

@Module
@InstallIn(SingletonComponent::class)
class ClientModule {

    @Singleton
    @Provides
    fun createNClient(): MyClient{
        return MyClient()
    }
}

上面的@Singleton這個是不可省略的,省略了相當於你使用的預設的組件,相當於每次註入都是新創建實例了!

然後需要註意的是,下麵幾個錯誤的寫法:

@Module
@InstallIn(SingletonComponent::class)
class ClientModule {

    @ActivityScoped //錯誤,與當前組件的作用域不一致
    @Provides
    fun createNClient(): MyClient{
        return MyClient()
    }
}

@Module
@InstallIn(ActivityComponent::class)
class ClientModule {

    @Singleton //錯誤,與當前組件的作用域不一致
    @Provides
    fun createNClient(): MyClient{
        return MyClient()
    }
}

組件作用域除了在module里使用,還可以修飾構造函數

@ActivityScoped
class Hardware @Inject constructor(){
    fun printName() {
        println("I'm fish")
    }
}

表示Hardware在同個Activity,只會有一個實例

組件的層次

組件有層次的使用,比如上面的全局的api,我們可以在其他地方組件作用域進行註入或者Activity,fragment中使用註入,如下代碼:

@Module
@InstallIn(SingletonComponent::class)
class ClientModule {

    @Singleton
    @Provides
    fun createNClient(): MyClient{
        return MyClient()
    }
}

@ActivityScoped
class MyNewApi @Inject constructor(myClient: MyClient)

具體的關係結構層次如下圖所示:

註入application或Activity

當我們構造函數需要傳遞application或Activity的時候,可以使用@ApplicationContext@ActivityContext 限定符。

如下麵代碼:

class AnalyticsServiceImpl @Inject constructor(
  @ApplicationContext context: Context
) : AnalyticsService { ... }

// The Application binding is available without qualifiers.
class AnalyticsServiceImpl @Inject constructor(
  application: Application
) : AnalyticsService { ... }

class AnalyticsAdapter @Inject constructor(
  @ActivityContext context: Context
) { ... }

// The Activity binding is available without qualifiers.
class AnalyticsAdapter @Inject constructor(
  activity: FragmentActivity
) { ... }

特殊用法

似乎是自定義入口類,然後給application實現一個擴展方法

@Module
@InstallIn(SingletonComponent::class)
object PlayServiceModule {
fun Application.playerController(): PlayerController {
return accessEntryPoint<PlayerControllerEntryPoint>().playerController()
}

@EntryPoint
@InstallIn(SingletonComponent::class)
interface PlayerControllerEntryPoint {
fun playerController(): PlayerController
}

}

與ViewModel聯用

為ViewModel添加 @HiltViewModel 註解,併在 ViewModel 對象的構造函數中使用 @Inject 註解

@HiltViewModel
class ExampleViewModel @Inject constructor(
  private val savedStateHandle: SavedStateHandle,
  private val repository: ExampleRepository
) : ViewModel() {
  ...
}

然後,帶有 @AndroidEntryPoint 註解的 activity 或 fragment 可以使用 ViewModelProvider 或 by viewModels() KTX 擴展照常獲取 ViewModel 實例:

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
  private val exampleViewModel: ExampleViewModel by viewModels()
  ...
}

參考


提問之前,請先看提問須知 點擊右側圖標發起提問 聯繫我 或者加入QQ群一起學習 Stars-One安卓學習交流群 TornadoFx學習交流群:1071184701
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 摘要:當多個引擎/節點同時訪問和修改數據時,如何保證數據在各個引擎/節點之間的一致性成為了一項挑戰。本文將深入探討MySQL集群在保持數據一致性的解決方案。 本文分享自華為雲社區《【華為雲MySQL技術專欄】MySQL 8.0事務提交原理解析!》,作者:GaussDB資料庫。 1. 概述 MySQL ...
  • Flink CDC 於 2021 年 11 月 15 日發佈了最新版本 2.1,該版本通過引入內置 Debezium 組件,增加了對 Oracle 的支持。 Flink下載地址 https://flink.apache.org/downloads/ 其他必需的jar包(cdc、jdbc、mysq和o ...
  • 數字化轉型提速中!傳統農牧食品行業也尋求搭上數字化轉型的快車,通過物聯網、大數據、人工智慧等現代信息技術,實現生產、加工、流通等環節的智能化和自動化,提高生產效率、優化資源配置、提升產品質量,並滿足消費者對食品安全和可追溯性的需求。 在數字化浪潮的推動下,鐵騎力士集團作為一家歷史悠久的農牧食品企業, ...
  • 作者 | 月影幽篁 在當前數據驅動的業務環境中,快速且高效的數據處理能力至關重要。Apache SeaTunnel以其卓越的性能和靈活性,成為數據工程師和開發者的首選工具之一。本文將介紹如何在集群環境中搭建Apache SeaTunnel 2.3.5版本的 Zeta-Server,並概述其使用方法。 ...
  • 本文是翻譯MySQL InnoDB Cluster – how to manage a split-brain situation[1]這篇文章,如有翻譯不妥或不對的地方,敬請諒解與指正。請尊重原創和翻譯勞動成果,轉載的時候請註明出處。謝謝! 每次我展示MySQL InnoDB Cluster時,在 ...
  • 案例介紹 環境介紹 操作系統: Red Hat Enterprise Linux release 8.10 (Ootpa)資料庫版本: Oracle 19.23.0.0.0 上周五,系統管理員需要給Linux升級補丁,UAT環境下的一套DG,資料庫沒有正常關閉的情況下,操作系統升級補丁後強制rebo ...
  • 在數據技術不斷演進的背景下,雲資料庫的崛起和雲原生資料庫的普及標志著資料庫技術的顯著變革。從最初的自建資料庫模式到如今的雲原生資料庫,企業在數據管理上的選擇變得更加豐富和靈活。雲資料庫不僅僅是對傳統資料庫技術的一個遷移,更是對其進行了一次全面的升級和優化。 ...
  • ElasticSearch服務提供對索引操作、文檔操作、分詞操作等多種介面。介面的查詢通常採用DSL的形式,也可採用SQL形式進行查詢。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...