当前位置: 首页 > news >正文

Android DI框架-Hilt

到底该如何理解<依赖注入>

模版代码:食之无味,弃之可惜

public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);TextView mTextView=(TextView) findViewById(R.id.mTextView);mTextView.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {}});String params1= getIntent().getString("params1")String params2= getIntent().getString("params2")}
}

findViewByIdgetIntent().getXXX()这样的重复代码,即没有技术含量,还动不动就是几十行。


注解+APT+反射

为了解决上述的问题,越来越多的依赖注入框架应运而生,其中具有代表性的,当属:ButterKnife View注入,ARouter Intent 参数自动提取注入。

public class MainActivity extends Activity {@BindView(R.id.text)public TextView textView;@Autowiredpublic model model@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);ButterKnife.inject(this);//运行时view注入Arouter.getInstance().inject(this)//运行时参数注入}@Click(R.id.text)void buttonClick() {}
}

ButterKnife原理:
  1. 编译时按照命名规则生成相应实现类,编织好findViewById的代码
  2. 运行时根据MainActivity的类名找到编译生成的实现类,反射构建实例,并调用inject方法触发findViewById
//ButterKnife自动findViewById
class ButterKnife_MainActivity<MainActivity> implements Inject{@Overridepublic void inject(MainActivity activity){activity.textView =activity.findViewById(R.id.text)}
}
ARouter原理:大致相同,详情参考:ARouter简明扼要原理分析 :
//arouter的参数自动提取
public class MainActivity$$ARouter$$Autowired implements ISyringe {@Overridepublic void inject(Object target) {MainActivity substitute = (MainActivity)target;substitute.goodsId = substitute.getIntent().getStringExtra("id");substitute.goodsModel = substitute.getIntent().getParcelableExtra("model");}
}

IOC框架的类型

view注入: Xutils,ButterKnife

参数注入: Arouter

对象注入:koin,Dagger2,Hilt


IOC、DI、APT傻傻分不清楚

什么是IOC(Inversion of Control) 控制反转?

是一种思想,并不是特指一种技术实现,目的是要解决Java 开发领域,对象的创建以及管理的问题,是一种软件设计思想。

  • 控制 :指的是对象创建(实例化、管理)的权力。(在哪里创建,由‘’谁”创建)
  • 反转 :控制权交给外部环境(Spring 框架、IoC 容器
  • 传统的开发方式 :当一个类里面需要用到很多个成员变量时。传统的写法,这些成员变量,都需要new出来!
  • 使用 IOC 思想的开发方式 :IOC的原则是:NO,我们不要new,这样耦合度太高(入参改变,所有引用都要改),而是通过 IOC 容器(Hilt,Dagger2 框架) 来帮助我们实例化对象并赋值。
Java中常见IOC解决方案
  • 解决方案一: 配置xml文件,里面标明哪个类,里面用了哪些成员变量,等待加载这个类的时候,我帮你注入(new)进去(Spring 服务器开发常用ioc方案)
  • 解决方案二: 用注解在需要注入的成员变量上面加个注解,例如@Inject,编译时生成相关实现类,运行时动态注入。在android上常用的ioc实现案,就是annotition +abtractProcessor,简称APT(Annotation Processing Tool)

什么是DI(Dependency Injection)依赖注入?

其实它们是同一个概念的不同角度描述,IOC是一种软件设计思想,DI是这种软件设计思想的一个具体的实现,相比于控制反转,依赖注入更容易理解。

简单来说,依赖注入指的是对象是通过外部注入的方式完成创建。


IOC分析
IOC的优势
  • 对象之间的耦合度或者说依赖程度降低
//1. 传统做法需要ILoginService = new LoginServiceImpl(),增加了对LoginServiceImpl的引用,
//倘若那天LoginServiceImpl不再是唯一实现类,那么所有引用的地方都需要相应改变//2. 如果有100个地方都直接new创建LoginServiceImpl对象,
//如果入参改变了,那么这100个地方都需要相应改变。//3.使用DI则没有这个问题,因为对象的创建将被统一管理了,统一实现了。
class MainActivty:AppcompatActivity{@Inject public ILoginService loginService;
}class LoginServiceImpl(): ILoginService{
}
  • 对象实例的创建变的易于管理,很容易就可以实现一个全局,或局部共享单例
class CustomFragment:Fragment{//假如fragment需要访问Activity中的成员变量//共享对象的自动注入,在同一个作用域内(activity生命周期内,application生命周期内),得到的都是同一个实例对象,不需要传来传去@Inject user:Userfun  display(){//传统做法  ×强转×,或者set 也就是在CustomFragment定义setUser,在Activity中把user塞进来((MainActivity)context).user.name((SecondActivity)context).user.name}
}

适合场景:模板代码创建实例对象,全局或局部对象共享

IOC的缺点
  • 代码可读性差,不知道对象在哪里被创建?实例创建的时机在哪里?入参是什么?
  • 增加新人学习成本(Dagger2很优秀,但仅仅是使用,就劝退了很多人)
  • 加速触及65535方法数
是否有必要引入IOC?

不是必须。能发挥多大作用取决于你的项目体量复杂度,如果简单的小项目用它适得其反,学习成本和收益不匹配,出错不容易排查。

Hilt 救世主?

2019 dev submit大会,公开表示dagger2并没有真正解决大家真正遇到的问题,反而学习成本,入门门槛较高,现在已经停止开发新功能了。取而代之的是基于dagger2开发的Hilt组件,大大的降低了学习成本和上手复杂度。

  • Jetpack推荐的DI库,降低 Android 开发者使用依赖注入框架的上手成本,减少了在项目中进行手动依赖(简化使用姿势,dagger2需要大量配置,Hilt不需要)
  • Hilt内部有一套作用域的概念,只能在指定的作用域中使用这个对象,并且提供声明式对象生命周期的管理方式。原本dagger2是不能帮我们管理对象的使用范围,和生命周期,但是在hilt里面,这些问题都不存在了。

DI框架,在国内可能并不是很广泛,但在国外却很受欢迎。根据Google统计Google Play上架的应用,前10K的应用70%都是用到了DI框架,Google更是在自己的Youtube上使用了Hilt 数据来自2019-2020阶段

Hilt 基本用法

快速接入

项目根目录 build.gradle

plugins {id 'com.google.dagger.hilt.android' version '2.44.2' apply false
}
或 
buildscript {repositories {// other repositories...mavenCentral()}dependencies {// other plugins...classpath 'com.google.dagger:hilt-android-gradle-plugin:2.44.2'}
}

每个Module模块 build.gradle:

plugins {id 'com.android.application'id 'org.jetbrains.kotlin.android'//Hiltid 'kotlin-kapt'id 'com.google.dagger.hilt.android'
}android {compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}kotlinOptions {jvmTarget = '1.8'}
}dependencies {implementation 'com.google.dagger:hilt-android:2.44.2'kapt 'com.google.dagger:hilt-compiler:2.44.2'
}
Step.1 配置应用程序
/** Hilt在编译阶段会生成顶层的components容器,* 提供全局的application context。* 为application提供对象注入的能力*/
@HiltAndroidApp
class HiltApplication : Application() {}

Step.2 配置需要依赖注入的类
/*
声明该类是一个可以注入的入口类。 仅支持在以下类型中进行依赖注入:
this supports appcompatActivity, androidx.fragment, views, services, and broadcast-receivers.
不支持Content Provider,不支持你自定义且不属于以上类型的类*/
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {//声明该对象需要被动态注入@Injectlateinit var emptyView: EmptyViewoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)//Hilt.inject(this)不需要//编译时生成Hilt_HomeActivity,在super前面实例化该类,进行成员变量赋值}
}

@AndroidEntryPoint 声明该类是一个可以注入的入口类

仅支持在以下类型中进行依赖注入
在这里插入图片描述
不支持Content Provider,不支持你自定义且不属于以上类型的类


Step.3 创建注入对象
Step.3.1 @Providers 创建对象
// 声明该模块中创建的对象的生命周期 可以有多种生命周期
// doubt: 直接使用最高的不就行了?长生命周期的创建,势必会违反Hilt设计生命周期和作用范围的初衷
@InstallIn(value = [ActivityComponent::class])
@Module//声明这是一个模块,类似分组概念,里面的方法必须是public static
object HomeModule {//Kotlin 必须是单例/Java public static@Provides      //直接在方法给出具体的实现代码@ActivityScoped //声明该对象的作用域,在Activity生命周期内共享实例fun newEmptyView(@ActivityContext context: Context): EmptyView {//@ActivityContext//声明context的类型,此处声明为Activity类型的context,//除此之外还可使用@ApplicationContext,声明为Application contextval emptyView = EmptyView(context)emptyView.refresh()return emptyView}
}
Step.3.2 @binds接口注入
@InstallIn(FragmentComponent::class)//不可以在activity中使用
@Module
abstract class LoginServiceModule{//1. 函数返回类型告诉 Hilt 提供了哪个接口的实例//2. 函数参数告诉 Hilt 提供哪个实现//3. 没有指定作用域scope,则每次都会生成新的实例对象@Binds//@FragmentScoped //hint @FragmentScoped与FragmentComponent::class 必须一一对应abstract fun bindLoginService(impl:LoginServiceImpl):ILoginService?
}
  • @InstallIn(value = [FragmentComponent::class]) 必须与@FragmentScoped对应
    在这里插入图片描述
  • @Binds:需要在方法参数里面明确写明接口的实现类。
  • @Provides:不需要在方法参数里面明确指明接口的实现类,但需要给出具体的实现
  • 两者不能同时出现在一个module里面,因为Provides需要定义在object修饰的类,而Binds需要定义在abstract修饰的类。
@Qualifier 限定符

自定义限定符@Qualifier,提供同一接口,不同的实现。更多参考:深入浅出,一篇文章让你学会Dagger2使用

@InstallIn(FragmentComponent::class)
@Module
object MainModule {@Qualifier@Retention(AnnotationRetention.RUNTIME)annotation class GeneralLoginServiceImpl@Qualifier@Retention(AnnotationRetention.RUNTIME)annotation class VipLoginServiceImpl@GeneralLoginServiceImpl@Providesfun provideGeneralLoginServiceImpl(): ILoginService {//note 这么写没问题,或是外界generalLoginService.password = 123456 用这种方式赋值属性//     但与在构造方法中传入参数相比,这样就少了 传入的强制性和直观性return LoginServiceImpl("General")}@VipLoginServiceImpl@Providesfun provideVipLoginServiceImpl2(): ILoginService {return LoginServiceImpl("Vip")// 这样写是不行的 会报错}//    @VipLoginServiceImpl
//    @Provides
//    fun provideVipLoginServiceImpl2(account:String): ILoginService {
//        return LoginServiceImpl(account)// 这样写是不行的 可以。但改如何注入这个参数呢?会不会过于复杂呢 hint:参数包装进方法里
//    }@Providesfun vipLoginServiceAccount():String{return "Default"//这个是给 iLoginService使用的}
}@AndroidEntryPoint
class EmptyFragment : Fragment() {@Inject@JvmField//note 需要外界重新赋值这个加上这个注解var iLoginService: ILoginService? = null@Inject@MainModule.GeneralLoginServiceImpllateinit var generalLoginService: ILoginService@Inject@MainModule.VipLoginServiceImpllateinit var vipLoginService: ILoginServiceoverride fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {iLoginService!!.login()generalLoginService.login()vipLoginService.login()return inflater.inflate(R.layout.fragment_empty, container, false)}}
作用域 scope
  • 默认情况下,Hilt中的所有对象实例都是无作用域的。这意味着每次请求绑定时,Hilt都会创建一个新的绑定实例。@Scopes 的作用在指定作用域范围内(Application、Activity 等等) 提供相同的实例。
  • @InstallIn模块中声明实例生命周期的范围时,作用域的范围必须与component的作用域匹配。例如,@InstallIn(ActivityComponent.class)模块内的绑定只能用限制作用域@ActivityScoped
@Module
@InstallIn(ActivityComponent.class)
object MainModule{@Providers@ActivityScoped  //在Activity生命周期内实例唯一fun providerLoginService():ILoginService=LoginServiceImpl
}

关键注解示意

在这里插入图片描述

Hint的局限性

Hilt 支持最常见的 Android 类 进行依赖注入

  • Application、AppcompatActivity、androidx.Fragment、View、Service、BroadcastReceiver 等等,但是我们可能需要在 Hilt 不支持的类中执行依赖注入,在这种情况下可以使用 @EntryPoint 注解
  • 比如Hilt 不支持 ContentProvider,如果想在 ContentProvider 中让Hilt 完成对象的注入,你可以定义一个接口,并添加 @EntryPoint 注解,然后添加 @InstallIn 注解指定 对象的生命周期边界,代码如下所示。
class WorkContentProvider : ContentProvider() {override fun onCreate(): Boolean {val service = InitializerEntryPoint.resolve(context!!).injectWorkService()service.work()return true}}
@EntryPoint
@InstallIn(SingletonComponent::class)//ApplicationComponent::class 已经被SingletonComponent取代
interface InitializerEntryPoint {fun injectWorkService(): WorkServicecompanion object {fun resolve(context: Context): InitializerEntryPoint {//如果该模块的生命周期是application,在创建实例时需要使用fromApplication//对应的还有fromActivity,fromFragment,fromView//具体使用哪个方法需要和InstallIn指定的组件类型匹配return EntryPointAccessors.fromApplication(context,InitializerEntryPoint::class.java)}}
}
class WorkService @Inject constructor() {fun work() {Log.e("TAG", "WorkService  -- work ")}
}   

源码分析

字节码分析

点击Android Studio -> Build->Build Bundles/Apks->Build APK -> 分析生成的Apk,找到对应的Application,MainActivity,分析字节码:

## HiltApplication:.class public final Lorg/ggxz/kotlin/hiltsourcecode/HiltApplication;
.super Lorg/ggxz/kotlin/hiltsourcecode/Hilt_HiltApplication;
.source "HiltApplication.kt"
...
# virtual methods
.method public onCreate()V.registers 1.line 10invoke-super {p0}, Lorg/ggxz/kotlin/hiltsourcecode/Hilt_HiltApplication;->onCreate()V.line 11return-void
.end method## MainActivity:.class public final Lorg/ggxz/kotlin/hiltsourcecode/MainActivity;
.super Lorg/ggxz/kotlin/hiltsourcecode/Hilt_MainActivity;
.source "MainActivity.kt"
...
.method protected onCreate(Landroid/os/Bundle;)V.registers 3.param p1, "savedInstanceState"    # Landroid/os/Bundle;.line 15invoke-super {p0, p1}, Lorg/ggxz/kotlin/hiltsourcecode/Hilt_MainActivity;->onCreate(Landroid/os/Bundle;)V
...
Application对象注入
//1. 编译时生成Hilt_HiApplication,称之为依赖注入的入口类//负责创建applicationComponet组件对象
//2. 编译时把父类替换成Hilt_HiApplication
@HiltAndroidApp
class HiApplication :(Hilt_HiApplication) Application {override fun onCreate() {super.onCreate()-->//3.执行父类Hilt_HiApplication.onCreate() //4.创建与Application生命周期关联的HiltComponents_ApplicationC组件,//并把application的Context对象与之关联//5.调用injectHiApplication为Application注入对象}
}
MainActivity对象注入
//1. 编译时生成Hilt_MainActivity,称之为对象注入的入口类,//创建ActivityComponent对象
//2. 编译时把父类替换成Hilt_MainActivity
@AndroidEntryPoint
class MainActivity :(Hilt_MainActivity) AppcomponentActivity {override fun onCreate() {super.onCreate()-->//3.执行父类Hilt_MainActivity.onCreate() //4.创建与Activity生命周期关联的HiltComponents_ActivityC组件//5.调用injectHiMainActivity为MainActivity注入对象}
}
共同点
  • 都会创建以Hilt为前缀,类名(HiAppilication)为后缀的对象注入管理类
  • 都会被替换掉父类为 Hilt_HiApplication ,Hilt_MainActivity
  • 都会在onCreate()时创建对应的component组件HiltComponents_ApplicationC,HiltComponents_ActivityC
  • 都会调用injectxxx(MainActivity instance)开始注入对象

在这里插入图片描述

知其然,知其所以然-Hilt 源码分析

Hilt_Application

@Generated("dagger.hilt.android.processor.internal.androidentrypoint.ApplicationGenerator")
public abstract class Hilt_HiltApplication extends Application implements GeneratedComponentManager<Object> {private final ApplicationComponentManager componentManager = new ApplicationComponentManager(new ComponentSupplier() {@Overridepublic Object get() {return DaggerHiltApplication_HiltComponents_ApplicationC.builder().applicationContextModule(new ApplicationContextModule(Hilt_HiltApplication.this)).build();}});protected final ApplicationComponentManager componentManager() {return componentManager;}@Overridepublic final Object generatedComponent() {return componentManager().generatedComponent();}@CallSuper@Overridepublic void onCreate() {// This is a known unsafe cast, but is safe in the only correct use case:// HiltApplication extends Hilt_HiltApplication((HiltApplication_GeneratedInjector) generatedComponent()).injectHiltApplication(UnsafeCasts.<HiltApplication>unsafeCast(this));super.onCreate();}
}

super.onCreate()之前,调用injectHiltApplicationHiltApplication进行注入。通过Dagger2我们知道,对象的是通过Component。由上面代码所知,generatedComponent()最终会调用到
ApplicationComponentManager#get()。

private final ApplicationComponentManager componentManager = new ApplicationComponentManager(new ComponentSupplier() {@Overridepublic Object get() {return DaggerHiltApplication_HiltComponents_ApplicationC.builder().applicationContextModule(new ApplicationContextModule(Hilt_HiltApplication.this)).build();}});
  1. ApplicationContextModule是Hilt自动生成的类,其本身也是@Module类型,用来保存和对外提供Application/Context,当被注入的对象标注@ApplicationContext注解,Hilt会为其自动注入ApplicationContext参数。
  2. DaggerHiltApplication_HiltComponents_ApplicationC是ApplicationComponent类型。其本身是抽象类,.build()最终返回是它的子类DaggerHiltApplication_HiltComponents_ApplicationC。并将applicationContextModule作为参数传递了过去。
 public HiltApplication_HiltComponents.ApplicationC build() {Preconditions.checkBuilderRequirement(applicationContextModule, ApplicationContextModule.class);return new DaggerHiltApplication_HiltComponents_ApplicationC(applicationContextModule);}

DaggerHiltApplication_HiltComponents_ApplicationC

整体结构

public final class DaggerHiltApplication_HiltComponents_ApplicationC extends HiltApplication_HiltComponents.ApplicationC {}

DaggerHiltApplication_HiltComponents_ApplicationC继承自HiltApplication_HiltComponents.ApplicationC,结构如下:

  @Component(modules = {ApplicationContextModule.class,ActivityRetainedCBuilderModule.class,ServiceCBuilderModule.class,MainModule.class})@Singletonpublic abstract static class ApplicationC implements ApplicationComponent,HiltWrapper_ActivityRetainedComponentManager_LifecycleComponentBuilderEntryPoint,ServiceComponentManager.ServiceComponentBuilderEntryPoint,GeneratedComponent,HiltApplication_GeneratedInjector {}@Subcomponent(modules = {DefaultViewModelFactories.FragmentModule.class,ViewWithFragmentCBuilderModule.class,ViewModelFactoryModules.FragmentModule.class})
  • ApplicationC是ApplicationComponent子类型
  • 创建MainModule.class以注解参数的形式传递进来
  • ApplicationC是abstract static class类型
    由上面可知DaggerHiltApplication_HiltComponents_ApplicationC在HiltApplication中创建。所以和Application的生命周期一样长,同时在@Module类中标记@InstallIn(ApplicationComponent::class),@Component就就会和与之对应的ApplicationComponent相关联(同时也会关联一些默认Module),从而使得MainModule中对象也可以和Application的生命周期一样长。

了解了Module的生命周期,我们再来看下对象是如何创建,已经如何生成单例的。再次之前我们先概括下结合核心类的关系:
Hilt核心关系类图

单例创建
ublic final class DaggerHiltApplication_HiltComponents_ApplicationC extends HiltApplication_HiltComponents.ApplicationC {private final ApplicationContextModule applicationContextModule;private volatile Object iLoginService = new MemoizedSentinel();private DaggerHiltApplication_HiltComponents_ApplicationC(ApplicationContextModule applicationContextModuleParam) {this.applicationContextModule = applicationContextModuleParam;}public static Builder builder() {return new Builder();}private LoginServiceImpl getLoginServiceImpl() {return new LoginServiceImpl(ApplicationContextModule_ProvideContextFactory.provideContext(applicationContextModule));}private ILoginService getILoginService() {Object local = iLoginService;if (local instanceof MemoizedSentinel) {synchronized (local) {local = iLoginService;if (local instanceof MemoizedSentinel) {local = getLoginServiceImpl();iLoginService = DoubleCheck.reentrantCheck(iLoginService, local);}}}return (ILoginService) local;}...... 省略
}
  • iLoginService对象,就是需要注入生成的对象。通过getILoginService()方法通过双重验证。利用getLoginServiceImpl()来new创建对象。
  • 可以看到iLoginService是MemoizedSentinel()类型,这个类是final类,无法被继承,且内部无任何方法。作用就是用来创建单例对象。标记的作用
  • 由于iLoginService是在DaggerHiltApplication_HiltComponents_ApplicationC中创建,而DaggerHiltApplication_HiltComponents_ApplicationC是在Application中创建。所以iLoginService生命周期和Application一样长,同时利用getILoginService()单例方法只会创建一次。从而达到全局单例的效果

private volatile Object iLoginService = new MemoizedSentinel();
通过volatile和synchronized我们不难看出。Hilt依赖注入是支持多线程的。但是有条件的,注入的定义和Module中具体的对象需要在同一线程,因为getLoginServiceImpl()会同步返回结果。

那么如何在主线程定义对象,Module中通过子线程创建:

class MainActivity{@Inject@jvmFiledvar data:List<City>?=null}@module@InstallIn(ActivityComponent::Class)object MainModule{@Providersfun loadData():List<City>?={DbHelp.execute(Runnable{//加载数据库数据 How to do?})}}

对象注入

上面讲到对象的创建,以及Hilt是如何完成单例的。下面就分析下,在MainActivity中,Hilt是如何完成注入的。

Hilt_MainActivity
public abstract class Hilt_MainActivity extends AppCompatActivity implements GeneratedComponentManager<Object> {private volatile ActivityComponentManager componentManager;private final Object componentManagerLock = new Object();...省略   @CallSuper@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {inject();super.onCreate(savedInstanceState);}@Overridepublic final Object generatedComponent() {return componentManager().generatedComponent();}protected ActivityComponentManager createComponentManager() {return new ActivityComponentManager(this);}protected final ActivityComponentManager componentManager() {if (componentManager == null) {synchronized (componentManagerLock) {if (componentManager == null) {componentManager = createComponentManager();}}}return componentManager;}protected void inject() {((MainActivity_GeneratedInjector) generatedComponent()).injectMainActivity(UnsafeCasts.<MainActivity>unsafeCast(this));}@Overridepublic ViewModelProvider.Factory getDefaultViewModelProviderFactory() {...省略
}

Hilt_MainActivity的核心代码也不多。

  1. 首先在上面我们分析过,Hilt会讲MainActivity的父类,动态替换成Hilt_MainActivity。此时在onCreate()内,super.onCreate(savedInstanceState)之前调用inject()进行注入,所以并不是不需要注入的代码,而是Hilt帮我们完成了。
  2. 而要想完成注入,必须借助Component组件来完成injectMainActivity()。那么我们看下这个Component到底做了什么

generatedComponent()–>componentManager().generatedComponent()–>ActivityComponentManager#generatedComponent–>createComponent()
最终在createComponent()方法中调用.build();方法来创建对象。此时进入到:

@DefineComponent.Builder
public interface ActivityComponentBuilder {ActivityComponentBuilder activity(@BindsInstance Activity activity);ActivityComponent build();
}

这是个借口类,唯一的实现类就是ActivityCBuilder,在其内部的build()方法内返回ActivityCImpl对象。那么在ActivityCImpl中肯定存在injectMainActivity()方法:

@Overridepublic void injectMainActivity(MainActivity mainActivity) {injectMainActivity2(mainActivity);}private MainActivity injectMainActivity2(MainActivity instance) {MainActivity_MembersInjector.injectILoginService(instance, DaggerHiltApplication_HiltComponents_ApplicationC.this.getILoginService());return instance;}

内部两个参数分别为:MainActivity,ILoginService。MainActivity很好理解,我们要为这个类进行对象注入,ILoginService就是我们最终要依赖注入完成创建的对象。然后通过:MainActivity_MembersInjector#injectILoginService完成对MainActivity

public static void injectILoginService(MainActivity instance, ILoginService iLoginService) {
instance.iLoginService = iLoginService;
}
中定义iLoginService类型进行赋值。

而DaggerHiltApplication_HiltComponents_ApplicationC.this.getILoginService()用来创建注入对象。前面分析过,这是个单例创建对象的方法。所以iLoginService只会创建一次,且与Application生命周期一样长。

那么新的问题出现了。ActivityCImpl又是如何能访问的到DaggerHiltApplication_HiltComponents_ApplicationC#getILoginService()方法的呢?

层级嵌套

仔细观察DaggerHiltApplication_HiltComponents_ApplicationC的结构你会发现:
层级嵌套
请添加图片描述DaggerHiltApplication_HiltComponents_ApplicationC的结构是一层嵌套一层,长生命周期的Component,嵌套短生命周期的Component。长生命周期的Component称作短生命周期的父容器,这样设计的目的就是方便,短生命周期的Component可以调用长生命周期创建对象的方法,从而达到对象单例,方便的控制对象的生命周期。


@HiltApplication、@AndroidEntryPoint 注解

结果上面分析,可以更加深刻理解@HiltApplication的作用,理解为什么Application没有任何需要注入的对象,也需要添加@HiltApplication,不添加会报错原因:

  • 创建ApplicationContextModule来保存ApplicationContext,当对象参数添加@ApplicationContext时自动为其注入
  • 利用injectHiltApplication为Application进行对象注入
  • 创建DaggerHiltApplication_HiltComponents_ApplicationC顶层容器

扩展

为了加深理解,现进行其他场景的分析,修改MainModule.class代码:

//ApplicationComponent 改成ActivityComponent
@InstallIn(/*ApplicationComponent::class*/ ActivityComponent::class)
@Module
abstract class MainModule {@Binds
//    @Singleton @ActivityScoped//@Singleton 改成 @ActivityScopedabstract fun bindService(impl: LoginServiceImpl): ILoginService
}interface ILoginService {fun login()
}class LoginServiceImpl @Inject constructor(@ApplicationContext val context: Context) : ILoginService {override fun login() {Toast.makeText(context, "LoginServiceImpl", Toast.LENGTH_SHORT).show()}}

生成后的代码:

private final class ActivityCImpl extends HiltApplication_HiltComponents.ActivityC {private final Activity activity;private volatile Object iLoginService = new MemoizedSentinel();.....省略private LoginServiceImpl getLoginServiceImpl() {return new LoginServiceImpl(ApplicationContextModule_ProvideContextFactory.provideContext(DaggerHiltApplication_HiltComponents_ApplicationC.this.applicationContextModule));}private ILoginService getILoginService() {Object local = iLoginService;if (local instanceof MemoizedSentinel) {synchronized (local) {local = iLoginService;if (local instanceof MemoizedSentinel) {local = getLoginServiceImpl();iLoginService = DoubleCheck.reentrantCheck(iLoginService, local);}}}return (ILoginService) local;}.....省略}

可以看到,此时getILoginService()单例方法定义在ActivityCImpl类中,那么此时iLoginService对象的生命周期就和Activity一样长,那么在子容器:View和Fragment中就和实现对象共享,达到单例的目的。


再来看另外一种场景:

@InstallIn(ApplicationComponent::class /*ActivityComponent::class*/)
@Module
abstract class MainModule {@Binds
//    @Singleton//去掉abstract fun bindService(impl: LoginServiceImpl): ILoginService
}interface ILoginService {fun login()
}class LoginServiceImpl @Inject constructor(@ApplicationContext val context: Context) : ILoginService {override fun login() {Toast.makeText(context, "LoginServiceImpl", Toast.LENGTH_SHORT).show()}}

生成后的代码:

DaggerHiltApplication_HiltComponents_ApplicationC.classprivate LoginServiceImpl getLoginServiceImpl() {return new LoginServiceImpl(ApplicationContextModule_ProvideContextFactory.provideContext(applicationContextModule));}

可以看到getLoginServiceImpl()虽然定义在DaggerHiltApplication_HiltComponents_ApplicationC类中,但每次创建不在是单例,而是会new一个新的,那么此时这个对象的生命周期和Application生命周期一样长吗?,答案是不是的,此时会和对象所在页面生命周期一样长。(对象不是单例,就和所在Acitvity/Fragment生命周期一样长,是单例,就和定义的Component一样长)

总结与疑问

至此整个Hilt的原理分析到此为止,源码分析不可能面面俱到。除了源码学习之前,还能学习到特别的单例创建,生命周期嵌套设计,构造器模式等等,可以在编写自己的代码时,起到借鉴的作用。
总结不如提出几个有意思的问题:

  • 如何为主线程中的字段实现异步加载的依赖注入?
  • @HiltApplication/@AndroidEntryPoint可以去掉吗?为什么?
  • @Singleton和@ActivityScope能在同个Module共存吗?
  • ARouter、Dagger2、Hilt对比依赖注入的实现方式的怎么样的,优缺点各是什么?如何选择?

以上问题可以通过网络,或是本文的理解,找出答案。分享会上会给我个人的见解

相关文章:

Android DI框架-Hilt

到底该如何理解<依赖注入> 模版代码&#xff1a;食之无味&#xff0c;弃之可惜 public class MainActivity extends Activity {Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);TextView mTextView(TextView) findVi…...

基于寄生捕食优化的BP神经网络(分类应用) - 附代码

基于寄生捕食优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于寄生捕食优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.寄生捕食优化BP神经网络3.1 BP神经网络参数设置3.2 寄生捕食算法应用 4.测试结果…...

【Java常见的几种设计模式】

Java常见的几种设计模式 1. 单例模式&#xff08;Singleton Pattern&#xff09;2. 工厂模式&#xff08;Factory pattern&#xff09;3. 抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;4. 建造者模式&#xff08;Builder Pattern&#xff09;5. 原型模式&…...

jupyter崩溃进不去,报错module ‘mistune‘ has no attribute ‘BlockGrammar‘

是python包引起的问题 [E 2023-10-14 08:40:25.414 ServerApp] Uncaught exception GET /api/nbconvert?1697244025327 (127.0.0.1) HTTPServerRequest(protocol‘http’, host‘localhost:8090’, method‘GET’, uri‘/api/nbconvert?1697244025327’, version‘HTTP/1.1’…...

windows terminal鼠标右键打开

如果在官网上下载的是zip文件的 需要在注册表修改鼠标右键才能出来 注册表修改如下&#xff1a; 1.先windowsR&#xff0c;在命令框中输入regedit 打开注册表 2.在路径’计算机\HKEY_CLASSES_ROOT\directory\background\shell’下新建一个wt&#xff0c;wt下新建commond 这里…...

HTML5播放 M3U8的hls流地址

在HTML5页面上播放M3U8的hls流地址 <!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title>视频播放</title> <script src"https://cdn.jsdelivr.net/npm/hls.jslatest"></script> &…...

leetcode:101.对称二叉树

借用二叉树是否相同的代码改动左右孩子相等对应关系&#xff0c;即为是否对称。 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/bool isSameTree(struct TreeNode* p, struct Tr…...

UI自动化的适用场景,怎么做?

经常有人会问&#xff0c;什么样的项目才适合进行UI自动化测试呢&#xff1f;UI自动化测试相当于模拟手工测试&#xff0c;通过程序去操作页面上的控件。而在实际测试过程中&#xff0c;经常会遇到无法找到控件&#xff0c;或者因控件定义变更而带来的维护成本等问题。 哪些场…...

SpringFramewrok (1)

1、框架的概念与理解 在现实生活中&#xff0c;框架可以比喻为我们搭建房子的框架。 在框架的基础上&#xff0c;我们可以专注于我们自己的工作&#xff0c;而不用在意这些底层工作如何实现。 框架的优点包括以下几点&#xff1a; 1. 提高开发效率&#xff1a;框架提供了许多…...

电商独立站小程序开发方案

随着移动互联网的迅速发展&#xff0c;电商行业也逐渐向小程序平台转移。开发一款电商小程序对于拓展销售渠道、提高用户体验、增加用户忠诚度等方面都有着重要的意义。本文将围绕电商小程序的开发背景、需求分析、技术选型、开发流程、风险控制、商业模式和市场前景等方面进行…...

数据库安全运维是什么意思?数据库安全运维系统用哪家好?

我们大家都直到数据在某些情况下容易丢失或被破坏&#xff0c;攻击者可能通过对数据库进行破坏或勒索等手段获取利益。所以保障数据库安全至关重要。今天我们就来聊聊数据库安全运维是什么意思&#xff1f;数据库安全运维系统用哪家好&#xff1f; 数据库安全运维是什么意思&…...

小程序的console中出现:。。。不在以下 request 合法域名列表中,请参考文档:。。。的报错解决

报错效果&#xff1a; 其实这个报错不代表自己的代码有问题 但是本强迫症研究了一下&#xff0c;按照以下方法关掉就不会显示这个报错了。 点微信开发者工具中的右上角的详情。点本地设置。勾选不校验。。。HTTPS证书。 即可关闭该报错&#xff1a;...

计算机网络基础(三):IPv4编址方式、子网划分、IPv4通信的建立与验证及ICMP协议

**IPv4地址是一个32位长的二进制数。**而这个32位二进制数又通常会表示为4个用点隔开的十进制数。那么&#xff0c;这个32位二进制数要如何通过4个十进制数表示出来呢&#xff1f; 我们在配置IPv4地址时&#xff0c;同时配置的“掩码”又有何用途&#xff1f; 1.IPv4编址方式…...

Error: GlobalConfigUtils setMetaData Fail Cause:java.lang.NullPointerException

文章目录 1、在开发中会出现这样的错误。2、其次&#xff0c;再看其他错误&#xff1a; 1、在开发中会出现这样的错误。 完整错误&#xff1a;Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Error: GlobalConfigUtils setMetaData Fail ! Cause…...

OpenHarmony 应用全局的 UI 状态存储:AppStorage

AppStorage 是应用全局的 UI 状态存储&#xff0c;是和应用的进程绑定的&#xff0c;由 UI 框架在应用程序启动时创建&#xff0c;为应用程序 UI 状态属性提供中央存储。 和 AppStorage 不同的是&#xff0c;LocalStorage 是页面级的&#xff0c;通常应用于页面内的数据共享。而…...

外置告警蜂鸣器使用小坑

告警蜂鸣器调试小坑 昨天调试新产品&#xff0c;由于IMO、MSC组织和IEC标准规定&#xff0c;不能使用带红色指示灯的蜂鸣器&#xff0c;于是更换了个不带灯。然而奇怪的现象出现了两次短响的程序在有的页面正常&#xff0c;有的页面就变成一声了。搞了一天&#xff0c;把各种寄…...

SSO身份验证如何帮助加强密码安全性

单点登录 &#xff08;SSO&#xff09; 是一种身份验证服务&#xff0c;可帮助用户使用一组凭据快速安全地访问所有应用程序。在员工需要访问多个应用程序才能完成工作的企业环境中&#xff0c;每次需要访问时都必须为每个应用程序输入登录凭据&#xff0c;这是一个主要的烦恼来…...

JIRA 在 2024 年完全停止服务器版本支持

在服务器上的开源许可证版本已经要过期了&#xff0c;想去更新下。 发现&#xff0c;JIRA 的所有服务器版本的支持马上就要结束了。 这就意味着&#xff0c;如果你部署的服务器版本的 JIRA 的话&#xff0c;你将没有办法对服务器进行更新。 貌似&#xff0c;必须使用 JIRA 提供…...

Ubuntu18.04安装gdal3.4

一.依赖关系 所以&#xff0c;安装顺序&#xff1a;SQLite -> Proj -> Gdal...

C#好资源网址推荐

C#好资源网址推荐 Microsoft 官方资料 C# 文档 https://learn.microsoft.com/zh-cn/dotnet/csharp/ C# 编程指南 https://learn.microsoft.com/zh-CN/dotnet/csharp/programming-guide/ 变量 https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/language-sp…...

npm install 相关命令

npm install 相关命令 基本安装命令 # 安装 package.json 中列出的所有依赖 npm install npm i # 简写形式# 安装特定包 npm install <package-name># 安装特定版本 npm install <package-name><version>依赖类型选项 # 安装为生产依赖&#xff08;默认&…...

后端下载限速(redis记录实时并发,bucket4j动态限速)

✅ 使用 Redis 记录 所有用户的实时并发下载数✅ 使用 Bucket4j 实现 全局下载速率限制&#xff08;动态&#xff09;✅ 支持 动态调整限速策略✅ 下载接口安全、稳定、可监控 &#x1f9e9; 整体架构概览 模块功能Redis存储全局并发数和带宽令牌桶状态Bucket4j Redis分布式限…...

华为云Flexus+DeepSeek征文 | MaaS平台避坑指南:DeepSeek商用服务开通与成本控制

作者简介 我是摘星&#xff0c;一名专注于云计算和AI技术的开发者。本次通过华为云MaaS平台体验DeepSeek系列模型&#xff0c;将实际使用经验分享给大家&#xff0c;希望能帮助开发者快速掌握华为云AI服务的核心能力。 目录 作者简介 前言 一、技术架构概览 1.1 整体架构设…...

可下载旧版app屏蔽更新的app市场

软件介绍 手机用久了&#xff0c;app越来越臃肿&#xff0c;老手机卡顿成常态。这里给大家推荐个改善老手机使用体验的方法&#xff0c;还能帮我们卸载不需要的app。 手机现状 如今的app不断更新&#xff0c;看似在优化&#xff0c;实则内存占用越来越大&#xff0c;对手机性…...

开源 vGPU 方案:HAMi,实现细粒度 GPU 切分

本文主要分享一个开源的 GPU 虚拟化方案&#xff1a;HAMi&#xff0c;包括如何安装、配置以及使用。 相比于上一篇分享的 TimeSlicing 方案&#xff0c;HAMi 除了 GPU 共享之外还可以实现 GPU core、memory 得限制&#xff0c;保证共享同一 GPU 的各个 Pod 都能拿到足够的资源。…...

如何让非 TCP/IP 协议驱动屏蔽 IPv4/IPv6 和 ARP 报文?

——从硬件过滤到协议栈隔离的完整指南 引言 在现代网络开发中,许多场景需要定制化网络协议(如工业控制、高性能计算),此时需确保驱动仅处理特定协议,避免被标准协议(如 IPv4/IPv6/ARP)干扰。本文基于 Linux 内核驱动的实现,探讨如何通过硬件过滤、驱动层拦截和协议栈…...

迁移科技3D视觉系统:重塑纸箱拆垛场景的智能革命

一、传统拆垛场景的困局与破局之道 在汽车零部件仓库中&#xff0c;每天有超过2万只异形纸箱需要拆垛分拣。传统人工拆垛面临三大挑战&#xff1a; 效率瓶颈&#xff1a;工人每小时仅能处理200-300件&#xff0c;且存在间歇性疲劳安全隐患&#xff1a;20kg以上重箱搬运导致年…...

ubuntu自定义服务自动启动

自定义服务 在路径 /etc/systemd/system/ 下 定义example.service [Unit] DescriptionMy Custom Script[Service] ExecStart/root/exe_start.sh Typeoneshot RemainAfterExityes[Install] WantedBymulti-user.target在/root/ 路径下执行 vi exe_start.shcd /root/mes_server/…...

Three.js + Vue3 加载GLB模型项目代码详解

本说明结合 src/App.vue 代码,详细解释如何在 Vue3 项目中用 three.js 加载并显示 glb 模型。 1. 依赖与插件导入 import {onMounted, onUnmounted } from vue import * as THREE from three import Stats from stats.js import {OrbitControls } from three/examples/jsm/co…...

Python数据可视化科技图表绘制系列教程(二)

目录 表格风格图 使用Seaborn函数绘图 设置图表风格 设置颜色主题 图表分面 绘图过程 使用绘图函数绘图 定义主题 分面1 分面2 【声明】&#xff1a;未经版权人书面许可&#xff0c;任何单位或个人不得以任何形式复制、发行、出租、改编、汇编、传播、展示或利用本博…...