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")}
}
findViewById
、getIntent().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原理:
- 编译时按照命名规则生成相应实现类,编织好findViewById的代码
- 运行时根据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()
之前,调用injectHiltApplication
为HiltApplication
进行注入。通过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();}});
- ApplicationContextModule是Hilt自动生成的类,其本身也是@Module类型,用来保存和对外提供Application/Context,当被注入的对象标注@ApplicationContext注解,Hilt会为其自动注入ApplicationContext参数。
- 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的生命周期,我们再来看下对象是如何创建,已经如何生成单例的。再次之前我们先概括下结合核心类的关系:
单例创建
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的核心代码也不多。
- 首先在上面我们分析过,Hilt会讲MainActivity的父类,动态替换成Hilt_MainActivity。此时在onCreate()内,super.onCreate(savedInstanceState)之前调用inject()进行注入,所以并不是不需要注入的代码,而是Hilt帮我们完成了。
- 而要想完成注入,必须借助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
到底该如何理解<依赖注入> 模版代码:食之无味,弃之可惜 public class MainActivity extends Activity {Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);TextView mTextView(TextView) findVi…...

基于寄生捕食优化的BP神经网络(分类应用) - 附代码
基于寄生捕食优化的BP神经网络(分类应用) - 附代码 文章目录 基于寄生捕食优化的BP神经网络(分类应用) - 附代码1.鸢尾花iris数据介绍2.数据集整理3.寄生捕食优化BP神经网络3.1 BP神经网络参数设置3.2 寄生捕食算法应用 4.测试结果…...
【Java常见的几种设计模式】
Java常见的几种设计模式 1. 单例模式(Singleton Pattern)2. 工厂模式(Factory pattern)3. 抽象工厂模式(Abstract Factory Pattern)4. 建造者模式(Builder Pattern)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文件的 需要在注册表修改鼠标右键才能出来 注册表修改如下: 1.先windowsR,在命令框中输入regedit 打开注册表 2.在路径’计算机\HKEY_CLASSES_ROOT\directory\background\shell’下新建一个wt,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.对称二叉树
借用二叉树是否相同的代码改动左右孩子相等对应关系,即为是否对称。 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/bool isSameTree(struct TreeNode* p, struct Tr…...

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

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

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

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

小程序的console中出现:。。。不在以下 request 合法域名列表中,请参考文档:。。。的报错解决
报错效果: 其实这个报错不代表自己的代码有问题 但是本强迫症研究了一下,按照以下方法关掉就不会显示这个报错了。 点微信开发者工具中的右上角的详情。点本地设置。勾选不校验。。。HTTPS证书。 即可关闭该报错:...

计算机网络基础(三):IPv4编址方式、子网划分、IPv4通信的建立与验证及ICMP协议
**IPv4地址是一个32位长的二进制数。**而这个32位二进制数又通常会表示为4个用点隔开的十进制数。那么,这个32位二进制数要如何通过4个十进制数表示出来呢? 我们在配置IPv4地址时,同时配置的“掩码”又有何用途? 1.IPv4编址方式…...

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

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

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

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

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

Ubuntu18.04安装gdal3.4
一.依赖关系 所以,安装顺序: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…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...

vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...

自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...

ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...