我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Hilt自定义与跨壁垒的方法是什么

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Hilt自定义与跨壁垒的方法是什么

本篇内容介绍了“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@Scopeannotation class UserScope

    就是这么简单。

    定义Component

    定义Component时,需要指明父Component和对应的Scope:

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

    Hilt需要以Builder构建Component,不仅如此,一般构建Component时存在初始信息,例如:ActivityComponent需要提供Activity实例。

    通常设计中,用户Component存在 用户基本信息、TOKEN 等初始信息

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

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

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

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

    注意:方法名并不重要,采用习惯性命名即可,我习惯于将向容器喂入参数的API添加feed前缀

    当我们通过Hilt获得Builder实例时,即可控制Component的创建(即生命周期开始)

    使用Manager管理Component

    不难想象,Component的管理基本为模板代码,Hilt中提供了模板和接口类:

    如果您想避免模板代码编写,可以定义扩展模块,使用APT、KCP、KSP生成

    此处展示非线程安全的简单使用Demo

    @Singletonclass 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)@Moduleabstract class AppModuleBinds {    @Binds    @IntoSet    abstract fun provideLibInitializer(bind: LibInitializer): Function1<Application, Any>}

    在主工程的Application中:

    @HiltAndroidAppclass 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自定义与跨壁垒的方法是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

    免责声明:

    ① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

    ② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

    Hilt自定义与跨壁垒的方法是什么

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档

    猜你喜欢

    Hilt自定义与跨壁垒的方法是什么

    本篇内容介绍了“Hilt自定义与跨壁垒的方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!跨越 IOC容器的壁垒使用依赖注入(DI)时
    2023-07-06

    一文吃透Hilt自定义与跨壁垒

    这篇文章主要介绍了Hilt自定义与跨壁垒的实现示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-15

    android自定义dialog的方法是什么

    Android中自定义Dialog的方法有以下几种:1. 创建一个继承自Dialog类的自定义对话框:- 创建一个新的类,继承自Dialog类。- 在构造方法中设置对话框的样式和布局,以及其他需要自定义的属性。- 可以通过重写onCreat
    2023-09-21

    vue3自定义指令的方法是什么

    这篇文章主要介绍“vue3自定义指令的方法是什么”,在日常操作中,相信很多人在vue3自定义指令的方法是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”vue3自定义指令的方法是什么”的疑惑有所帮助!接下来
    2023-07-04

    mysql自定义字段的方法是什么

    在MySQL中,可以通过以下两种方法来自定义字段:1. 使用计算字段:- 计算字段是使用表中已有字段计算出来的结果。可以使用算术运算符、字符串函数、日期函数等对已有字段进行操作,并将结果作为计算字段返回。- 计算字段在查询时动态计算,不会在
    2023-09-25

    Java自定义异常的方法是什么

    本文小编为大家详细介绍“Java自定义异常的方法是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java自定义异常的方法是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。一、异常分为哪几种异常分为两种,分
    2023-07-06

    mysql自定义函数的方法是什么

    在MySQL中,可以使用以下步骤来创建自定义函数:1. 创建函数:使用`CREATE FUNCTION`语句来创建函数,并指定函数的名称、参数列表和返回值的数据类型。例如:```sqlCREATE FUNCTION 函数名(参数列表) RE
    2023-09-11

    winform自定义控件的方法是什么

    要创建一个自定义控件,你需要按照以下步骤进行操作:创建一个新的类并继承自System.Windows.Forms.Control类或者更具体的控件(如Button、Label等)。在类中添加需要的属性、方法和事件。重写基类的一些方法,比
    winform自定义控件的方法是什么
    2024-04-09

    linux自定义信号的方法是什么

    在Linux中,可以使用以下方法来自定义信号:1. 使用`signal()`函数:可以使用`signal()`函数来设置信号处理程序。该函数的原型如下:```cvoid (*signal(int signum, void (*handler
    2023-08-12

    android自定义控件的方法是什么

    Android自定义控件的方法有以下几种:1. 继承系统控件:可以通过继承系统控件来进行扩展和定制。通过重写控件的绘制方法,修改控件的样式、行为或添加新的功能。2. 组合已有控件:可以通过将多个现有的控件组合在一起,形成一个新的复合控件。可
    2023-08-16

    qt自定义表格的方法是什么

    在Qt中,可以使用QTableView类来创建和操作表格。下面是一些自定义表格的方法:1. 设置表格模型:使用QStandardItemModel类或自定义的QAbstractTableModel类创建表格模型,并使用setModel()方
    2023-09-29

    mfc自定义控件的方法是什么

    MFC(Microsoft Foundation Class)是一种用于开发Windows桌面应用程序的C++框架。MFC提供了一组类和函数,用于简化Windows应用程序的开发。要创建自定义控件,可以按照以下步骤进行:1. 创建一个继承自
    2023-09-07

    pgsql自定义排序的方法是什么

    在 PostgreSQL 中,可以通过创建自定义排序规则来实现自定义排序。以下是实现自定义排序的一般方法:创建自定义排序规则函数:首先,您需要创建一个函数来定义您的自定义排序规则。这个函数会接收两个参数,比较它们并返回一个整数值,表示它们的
    pgsql自定义排序的方法是什么
    2024-04-09

    vue3自定义指令方法是什么

    这篇文章主要讲解了“vue3自定义指令方法是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“vue3自定义指令方法是什么”吧!一、注册自定义指令以下实例都是实现一个输入框自动获取焦点的自定
    2023-06-21

    Java方法的定义与调用是什么

    这篇文章主要介绍“Java方法的定义与调用是什么”,在日常操作中,相信很多人在Java方法的定义与调用是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java方法的定义与调用是什么”的疑惑有所帮助!接下来
    2023-06-29

    Flutter构建自定义Widgets的方法是什么

    这篇文章主要讲解了“Flutter构建自定义Widgets的方法是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Flutter构建自定义Widgets的方法是什么”吧!一.组合widge
    2023-06-26

    Android自定义PhotoView使用的方法是什么

    这篇“Android自定义PhotoView使用的方法是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Android自定
    2023-07-05

    flink自定义触发器的方法是什么

    在 Flink 中自定义触发器需要实现 Trigger 接口,该接口定义如下:public interface Trigger extends Serializable {// 初始化触发器voi
    flink自定义触发器的方法是什么
    2024-03-07

    node.js自定义实现EventEmitter的方法是什么

    这篇文章主要讲解了“node.js自定义实现EventEmitter的方法是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“node.js自定义实现EventEmitter的方法是什么”吧
    2023-07-04

    编程热搜

    • Python 学习之路 - Python
      一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
      Python 学习之路 - Python
    • chatgpt的中文全称是什么
      chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
      chatgpt的中文全称是什么
    • C/C++中extern函数使用详解
    • C/C++可变参数的使用
      可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
      C/C++可变参数的使用
    • css样式文件该放在哪里
    • php中数组下标必须是连续的吗
    • Python 3 教程
      Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
      Python 3 教程
    • Python pip包管理
      一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
      Python pip包管理
    • ubuntu如何重新编译内核
    • 改善Java代码之慎用java动态编译

    目录