91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Hilt自定義與跨壁壘的方法是什么

發布時間:2023-04-10 16:43:13 來源:億速云 閱讀:163 作者:iii 欄目:開發技術

本篇內容介紹了“Hilt自定義與跨壁壘的方法是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

    跨越 IOC容器的壁壘

    使用依賴注入(DI)時,我們需要它對 實例依賴關系生命周期 進行管理,因此DI框架會構建一個容器,用于實現這些功能。這個容器我們慣稱為IOC容器。

    在容器中,會按照我們制定的規則:

    • 創建實例

    • 訪問實例

    • 注入依賴

    • 管理生命周期

    但容器外也有訪問容器內部的需求,顯然這里存在一道虛擬的 邊界、壁壘。這種需求分為兩類:

    • 依賴注入客觀需要的入口

    • 系統中存在合理出現的、非DI框架管理的實例,但它不希望破壞其他實例對象的 生命周期作用域唯一性,即它的依賴希望交由DI框架管理

    但請注意,IOC容器內部也存在著 邊界、壁壘,這和它管理實例的機制有關,在Hilt(包括Dagger)中,最大顆粒度的內部壁壘是 Component

    即便從外部突破IOC容器的壁壘,也只能進入某個特定的Component

    使用EntryPoint跨越IOC容器壁壘

    在Hilt中,我們可以很方便地

    • 使用接口定義 進入點(EntryPoint),并使用 @EntryPoint 注解使其生效;

    • @InstallIn 注解指明訪問的Component;

    • 并利用 EntryPoints 完成訪問,突破容器壁壘

    下面的代碼展示了如何定義:

    UserComponent是自定義的Component,在下文中會詳細展開

    @EntryPoint
    @InstallIn(UserComponent::class)
    interface UserEntryPoint {
        fun provideUserVO(): UserVO
    }

    下面的代碼展示了如何獲取進入點,注意,您需要先獲得對應的Component實例。

    對于Hilt內建的Component,均有其獲取方法,而自定義的Component,需從外界發起生命周期控制,同樣會預留實例訪問路徑

    fun manualGet(): UserEntryPoint {
        return EntryPoints.get(
            UserComponentManager.instance.generatedComponent(),
            UserEntryPoint::class.java
        )
    }

    當獲取進入點后,即可使用預定義的API,訪問容器內的對象實例。

    自定義Scope、Component

    部分業務場景中,Hilt內建的Scope和Component并不能完美支持,此時我們需要進行自定義。

    為了下文能夠更順利的展開,我們再花一定的筆墨對 ScopeComponentModule 的含義進行澄清。

    Scope、Component、Module的真實含義

    前文提到兩點:

    • DI框架需要 創建實例訪問實例注入依賴管理生命周期

    • IOC容器內部也存在著 邊界、壁壘,這和它管理實例的機制有關,在Hilt(包括Dagger)中,最大顆粒度的內部壁壘是 Component

    不難理解:

    • 實例之間,也會存在依賴關系;

    • DI框架需要管理內部實例的生命周期;

    • 需要進行依賴注入的客戶,本身也存在生命周期,它的依賴對象,應該結合實際需求被合理控制生命周期,避免生命周期泄漏

    因此,出現了 范圍、作用域Scope 的概念,它包含兩個維度:實例的生命周期范圍;實例之間的訪問界限。

    并且DI框架通過Component控制內部對象的生命周期。

    舉一個例子描述,以Activity為例,Activity需要進行依賴注入,并且我們不希望Activity自身需要的依賴出現生命周期泄漏,于是按照Activity的生命周期特點定義了:

    • ActivityRetainedScoped ActivityRetainedComponent,不受reCreate 影響

    • ActivityScopedActivityComponent,橫豎屏切換等配置變化引起reCreate 開始新生命周期

    并據此對 依賴對象實例 實施 生命周期訪問范圍 控制

    可以記住以下三點結論:

    • Activity實例按照 預定Scope對應的生命周期范圍 創建、管理Component,訪問Component中的實例;

    • Component內的實例可以互相訪問,實例的生命周期和Component一致;

    • Activity實例(需要依賴注入的客戶)和 Component中的實例 可以訪問 父Component中的實例,父Component的生命周期完全包含子Component的生命周期

    內建的Scope、Component關系參考:

    Hilt自定義與跨壁壘的方法是什么

    而Module指導DI框架 創建實例選用實例進行注入

    值得注意的是,Hilt(以及Dagger)可以通過 @Inject 注解類構造函數指導 創建實例,此方式創建的實例的生命周期跟隨宿主,與 通過Module方式 進行對比,存在生命周期管理粒度上的差異。

    自定義

    至此,已不難理解:因為有實際的生命周期范圍管理需求,才會自定義。

    為了方便行文以及編寫演示代碼,我們舉一個常見的例子:用戶登錄的生命周期。

    一般的APP在設計中,用戶登錄后會持久化TOKEN,下次APP啟動后驗證TOKEN真實性和時效性,通過驗證后用戶仍保持登錄狀態,直到TOKEN超時、登出。當APP退出時,可以等效認為用戶登錄生命周期結束。

    顯然,用戶登錄的生命周期完全涵蓋在APP生命周期(Singleton Scope)中,但略小于APP生命周期;和Activity生命周期無明顯關聯。

    定義Scope

    import javax.inject.Scope
    @Scope
    annotation class UserScope

    就是這么簡單。

    定義Component

    定義Component時,需要指明父Component和對應的Scope:

    import dagger.hilt.DefineComponent
    @DefineComponent(parent = SingletonComponent::class)
    @UserScope
    interface UserComponent {
    }

    Hilt需要以Builder構建Component,不僅如此,一般構建Component時存在初始信息,例如:ActivityComponent需要提供Activity實例。

    通常設計中,用戶Component存在 用戶基本信息、TOKEN 等初始信息

    data class User(val name: String, val token: String) {
    }

    此時,我們可以在Builder中完成初始信息的注入:

    import dagger.BindsInstance
    import dagger.hilt.DefineComponent
    @DefineComponent.Builder
    interface Builder {
        fun feedUser(@BindsInstance user: User?): Builder
        fun build(): UserComponent
    }

    我們以 @BindsInstance 注解標識需要注入的初始信息,注意合理控制其可空性,在后續的使用中,可空性需保持一致

    注意:方法名并不重要,采用習慣性命名即可,我習慣于將向容器喂入參數的API添加feed前綴

    當我們通過Hilt獲得Builder實例時,即可控制Component的創建(即生命周期開始)

    使用Manager管理Component

    不難想象,Component的管理基本為模板代碼,Hilt中提供了模板和接口類:

    如果您想避免模板代碼編寫,可以定義擴展模塊,使用APT、KCP、KSP生成

    此處展示非線程安全的簡單使用Demo

    @Singleton
    class UserComponentManager @Inject constructor(
        private val builder: UserComponent.Builder
    ) : GeneratedComponentManager<UserComponent> {
        companion object {
            lateinit var instance: UserComponentManager
        }
        private var userComponent = builder
            .feedUser(null)
            .build()
        fun onLogin(user: User) {
            userComponent = builder.feedUser(user).build()
        }
        fun onLogout() {
            userComponent = builder.feedUser(null).build()
        }
        override fun generatedComponent(): UserComponent {
            return userComponent
        }
    }

    您也可以定義如下的線程安全的Manager,并使用 ComponentSupplier 提供實例

    class CustomComponentManager(
        private val componentCreator: ComponentSupplier
    ) : GeneratedComponentManager<Any> {
        @Volatile
        private var component: Any? = null
        private val componentLock = Any()
        override fun generatedComponent(): Any {
            if (component == null) {
                synchronized(componentLock) {
                    if (component == null) {
                        component = componentCreator.get()
                    }
                }
            }
            return component!!
        }
    }

    您可以根據實際需求選擇最適宜的方法進行管理,不再贅述。

    在生命周期范圍更小的Component中使用

    至此,我們已經完成了自定義Scope、Component的主要工作,通過Manager即可控制生命周期。

    如果想在生命周期范圍更小的Component中訪問 UserComponent中的對象實例,您需要謹記前文提到的三條結論。

    該需求很合理,但下面的例子并不足夠典型

    此時,您需要通過一個合理的Component實現訪問,例如在Activity中需要注入相關實例時。 因為 ActivityRetainedComponentUserComponent 不存在父子關系,Scope沒有交集,所以 需要找到共同的父Component進行幫助,并通過EntryPoint突破壁壘

    前文中,我們將 UserComponentManager 劃入 SingletonComponent, 他是兩種的共同父Component,此時可以這樣處理:

    @Module
    @InstallIn(ActivityRetainedComponent::class)
    object AppModule {
        @Provides
        fun provideUserVO(manager: UserComponentManager):UserVO {
            return UserEntryPoint.manualGet(manager.generatedComponent()).provideUserVO()
        }
    }

    解決獨立library的依賴初始化問題

    此問題屬于常見案例,通過研究它的解決方案,我們可以更深刻地理解前文內容,做到吃透。

    當處理主工程時,沒有代碼隔離,我們可以很輕易的修改Application的代碼,因此很多問題難以暴露。

    例如,我們可以在Application中通過注解標明依賴 (滿足Singleton Scope前提) ,DI框架會幫助我們進行注入,在注入后可以編寫邏輯代碼,將對象賦值給全局變量,便可以 "方便" 的使用。

    為方便下文表述,我們稱之 "方案1"

    顯然,這是有異味的代碼,雖然它有效且方便。

    因此,我們選取一些場景來說明該做法的弊端:

    • 場景1:創建獨立Library,其中使用Hilt作為DI框架,Library中存在自定義Component,需要初始化管理入口

    • 場景2:項目采用了組件化,該Library按照渠道包需求,渠道包A集成、渠道包B不集成

    • 場景3:項目采用了Uni-App、React-Native等技術,該Library中存在實例由反射方式創建、不受Hilt管理,無法借助Hilt自動注入依賴

    以上場景并不相互孤立

    在場景1中,我們仍然可以通過 方案1 完成需求,但在場景2中便不再可行。

    常規的組件化、插件化,都會完成代碼隔離&使用抽象,因此無法在主工程的Application中使用目標類。通過定制字節碼工具曲線救國,則屬實是大炮打蚊子、屎盆子鑲金邊

    使用hilt的聚合能力解決問題

    在 MAD Skills 系列文章的最后一篇中,簡單提及了Hilt的聚合能力,它至少包含以下兩個層面:

    • 即便一個已經編譯為aar的庫,在被集成后,Hilt依舊能夠掃描該庫中Hilt相關的內容,進行依賴圖聚合

    • Hilt生成的代碼,依舊存在著注解,這些注解可以被注解處理器、字節碼工具識別、并進一步處理。可以是Hilt內建的處理器或您自定義的擴展處理器

    依據第一個層面,我們可以制定一個約定:

    子Library按照抽象接口提供Library初始化實例,主工程的Application通過DI框架獲取后進行初始化

    我們將其稱為方案2

    例如,在Library中定義如下初始化類:

    class LibInitializer @Inject constructor(
        private val userComponentManager: UserComponentManager
    ) : Function1<Application, Any> {
        override fun invoke(app: Application): Any {
            UserComponentManager.instance = userComponentManager
            return Unit
        }
    }

    不難發現,他是方案1的變種,將依賴獲取從Application中挪到了LibInitializer中

    并約定綁定實例&集合注入, 依舊在Library中編碼 :

    @InstallIn(SingletonComponent::class)
    @Module
    abstract class AppModuleBinds {
        @Binds
        @IntoSet
        abstract fun provideLibInitializer(bind: LibInitializer): Function1<Application, Any>
    }

    在主工程的Application中:

    @HiltAndroidApp
    class App : Application() {
        @Inject
        lateinit var initializers: Set<@JvmSuppressWildcards Function1<Application, Any>>
        override fun onCreate() {
            super.onCreate()
            initializers.forEach {
                it(this)
            }
        }
    }

    如此即可滿足場景1、場景2的需求。

    但仔細思考一下,這種做法太 "強硬" 了,不僅要求主工程的Application進行配合,而且需要小心的處理初始化代碼的分配。

    在場景3中,這些技術均有相適應的插件初始化入口;組件化插件化項目中,也具有類似的設計。隨集成方式的不同,很可能造成 初始化邏輯遺漏或者重復

    注意:重復初始化可能造成潛在的Scope泄漏,滋生bug。

    聚合能力+EntryPoint

    前文中,我們已經討論了使用EntryPoint突破IOC容器的壁壘,也體驗了Hilt的聚合能力。而 SingletonComponent 作為內建Component,同樣可以使用EntryPoint突破容器壁壘。

    如果您對Hilt的源碼或其設計有一定程度的了解,應當清楚:

    內建Component均有對應的ComponentHolder,而SingletonComponent對應的Holder即為Application。

    通過 Holder實例和 EntryPointAccessors 可以獲得定義的 EntryPoint接口

    SingletonComponent 自定義EntryPoint后,即可擺脫Hilt自定注入的傳遞鏈而通過邏輯編碼獲取實例。

    @EntryPoint
    @InstallIn(SingletonComponent::class)
    interface UserComponentEntryPoint {
        companion object {
            fun manualGet(context: Context): UserComponentEntryPoint {
                return EntryPointAccessors.fromApplication(
                    context, UserComponentEntryPoint::class.java
                )
            }
        }
        fun provideBuilder(): UserComponent.Builder
        fun provideManager():UserComponentManager
    }

    通過這一方式,我們只需要獲得Context即可突破壁壘訪問容器內部實例,Hilt不再約束Library的初始化方式。

    至此,您可以在原先的Library初始化模塊中,按需自由的添加邏輯!

    注意:Builder由Hilt生成實現,無法干預其生命周期,故每次調用時生成新的實例,從一般的編碼需求,獲取Manager實例即可。您可以在WorkShop項目中獲得驗證

    問題衍生

    在場景3中,我們繼續進行衍生:

    Library作為動態插件,并不直接集成,而是通過插件化技術,動態集成啟用功能。又該如何處理呢?

    在MAD Skills系列文章的第四篇中,簡單提及了Hilt的擴展能力。考慮到篇幅以及AAB(Dynamic Feature)、插件化的背景,我們將在下一篇文章中對該問題展開解決方案的討論。

    “Hilt自定義與跨壁壘的方法是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    化德县| 昌黎县| 如东县| 云和县| 京山县| 锡林郭勒盟| 华蓥市| 陕西省| 新兴县| 武冈市| 泰顺县| 正蓝旗| 马关县| 双流县| 余姚市| 华坪县| 江阴市| 金塔县| 阜新市| 璧山县| 晋城| 双牌县| 肃北| 淮滨县| 赞皇县| 木兰县| 卫辉市| 邛崃市| 华宁县| 洛南县| 龙里县| 开江县| 南岸区| 资阳市| 夹江县| 恩平市| 进贤县| 漠河县| 囊谦县| 镇巴县| 内乡县|