Android DI框架-Hilt

news2024/11/18 21:43:29

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

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

public class MainActivity extends Activity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     TextView mTextView=(TextView) findViewById(R.id.mTextView);
     mTextView.setOnClickListener(new OnClickListener() {
        @Override
        public 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;
 
 @Autowired
 public model model
 
 @Override
 protected 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{
   @Override
   public void inject(MainActivity activity){
      activity.textView =activity.findViewById(R.id.text)
   }
}
ARouter原理:大致相同,详情参考:ARouter简明扼要原理分析 :
//arouter的参数自动提取
public class MainActivity$$ARouter$$Autowired implements ISyringe {
  @Override
  public 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:User
   fun  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'
    //Hilt
    id 'kotlin-kapt'
    id 'com.google.dagger.hilt.android'
}

android {
    
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility 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() {

    //声明该对象需要被动态注入
    @Inject
    lateinit var emptyView: EmptyView

    override 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 context
        val 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
    @Provides
    fun provideGeneralLoginServiceImpl(): ILoginService {
        //note 这么写没问题,或是外界generalLoginService.password = 123456 用这种方式赋值属性
        //     但与在构造方法中传入参数相比,这样就少了 传入的强制性和直观性
        return LoginServiceImpl("General")
    }

    @VipLoginServiceImpl
    @Provides
    fun provideVipLoginServiceImpl2(): ILoginService {
        return LoginServiceImpl("Vip")// 这样写是不行的 会报错
    }

//    @VipLoginServiceImpl
//    @Provides
//    fun provideVipLoginServiceImpl2(account:String): ILoginService {
//        return LoginServiceImpl(account)// 这样写是不行的 可以。但改如何注入这个参数呢?会不会过于复杂呢 hint:参数包装进方法里
//    }

    @Provides
    fun vipLoginServiceAccount():String{
        return "Default"//这个是给 iLoginService使用的
    }
}

@AndroidEntryPoint
class EmptyFragment : Fragment() {

    @Inject
    @JvmField//note 需要外界重新赋值这个加上这个注解
    var iLoginService: ILoginService? = null

    @Inject
    @MainModule.GeneralLoginServiceImpl
    lateinit var generalLoginService: ILoginService

    @Inject
    @MainModule.VipLoginServiceImpl
    lateinit var vipLoginService: ILoginService

    override 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(): WorkService

    companion 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 10
    invoke-super {p0}, Lorg/ggxz/kotlin/hiltsourcecode/Hilt_HiltApplication;->onCreate()V

    .line 11
    return-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 15
    invoke-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() {
    @Override
    public Object get() {
      return DaggerHiltApplication_HiltComponents_ApplicationC.builder()
          .applicationContextModule(new ApplicationContextModule(Hilt_HiltApplication.this))
          .build();
    }
  });

  protected final ApplicationComponentManager componentManager() {
    return componentManager;
  }

  @Override
  public final Object generatedComponent() {
    return componentManager().generatedComponent();
  }

  @CallSuper
  @Override
  public 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() {
    @Override
    public 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
      }
  )
  @Singleton
  public 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
     @jvmFiled
     var data:List<City>?=null
 }

 @module
 @InstallIn(ActivityComponent::Class)
 object MainModule{
   @Providers
   fun 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
  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    inject();
    super.onCreate(savedInstanceState);
  }

  @Override
  public 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));
  }

  @Override
  public 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()方法:

@Override
      public 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 改成 @ActivityScoped
    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()
    }

}

生成后的代码:

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.class

  private 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对比依赖注入的实现方式的怎么样的,优缺点各是什么?如何选择?

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1105922.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vueday01——双向绑定

1.在template里面创建一个button&#xff08;原生的不需要导入&#xff09; <button click"myclick()">数值是{{ count }}</button> 2.在setup里面声明变量和方法&#xff0c;以及将其导出 setup() {const count ref(0);const myclick () >{count.…

ROC与AUC与主动学习评价指标ALC

首先需要关注一下什么是混淆矩阵&#xff0c;此处认为1为正类&#xff0c;0为负类 预测为0预测为1真实为0TN真负例&#xff08;预测为0&#xff0c;真实也为0&#xff09;FP假正例&#xff08;预测为1&#xff0c;但真实为0&#xff09;真实为1FN假负例&#xff08;预测为0&am…

VUE 快速上手与基础指令(内附详细案例)

文章目录 前言一、vue快速上手1. vue是什么2. 创建一个vue实例3. 插值表达式4. vue的响应式特性5. 开发者工具安装 二、vue指令1. v-html2. v-if 和 v-show3. v-else 和 v-else-if4. v-on5. v-bind6. 案例-波仔的学习之旅7. v-for8. 案例-小黑的书架9. v-for 中的key10. v-mode…

Python 框架学习 Django篇 (三) 链接数据库

只要你是做后端开发的&#xff0c;那么就离不开各种数据库&#xff0c;Django框架对各种数据库都非常友好&#xff0c;比如常见的PostgreSQL、MySQL、SQLite、Oracle&#xff0c;django都对他们提供了统一调用api&#xff0c;我们这里主要使用mysql数据库作为演示 一、ORM机制 …

报考阿里云acp认证,你得到的是什么?

放眼全球能够和亚马逊AWS、微软Azure竞争的&#xff0c;国内也就只有阿里云了。 阿里云目前稳居国内云计算市场第一&#xff0c;比排后面5名同行市场占有率的总和还要多&#xff0c;全球云计算市场&#xff0c;阿里云目前排名第3位。 阿里云的市场占有率说明市场对于阿里云产…

许战海战略文库|我们的建议:华彬集团改名战马饮料集团

摘要&#xff1a;战马未能有效借势红牛,市场份额不及东鹏特饮。战马要走出当下面临的窘境,需要将华彬集团改名为战马饮料集团&#xff0c;借势红牛加强战马主品牌的认知建设构建战马饮料的产品矩阵;组建战马独立销售网络。 许战海咨询认为&#xff1a;过度差异化造成华彬集团快…

肿瘤科常用评估量表汇总,建议收藏!

根据肿瘤科医生的量表使用情况&#xff0c;笔者整理了10个肿瘤科常用量表&#xff0c;可在线评测直接出结果&#xff0c;可转发使用&#xff0c;可生成二维码使用&#xff0c;可创建项目进行数据管理&#xff0c;有需要的小伙伴赶紧收藏&#xff01; 肿瘤患者的ECOG评分标准 肿…

手机流量卡经营商城小程序的作用是什么

流量卡成为很多用户的选择&#xff0c;同时市场中也出现了不少流量卡卖家&#xff0c;基于多种形式开展生意。然而虽然市场需求度高&#xff0c;但流量卡经营难题也不少。 流量卡客户具有高忠诚度&#xff0c;然而入驻线上第三方平台&#xff0c;客户属于平台&#xff0c;无法…

檀香香料经营商城小程序的作用是什么

檀香香料有安神、驱蚊、清香等作用&#xff0c;办公室或家庭打坐等场景&#xff0c;都有较高的使用频率&#xff0c;不同香料也有不同效果&#xff0c;高品质香料檀香也一直受不少消费者欢迎。 线下流量匮乏&#xff0c;又难以实现全消费路径完善&#xff0c;线上是商家增长必…

grafana v10.1版本设置告警

1. 相关概念概述 如图所示&#xff0c;点击切换菜单标志&#xff0c;可以看到警报相关子选项。 警报规则&#xff1a;通过PromQL语句定义告警规则&#xff0c;即达到怎样的状态触发告警。 联络点&#xff1a; 设置当警报规则实例触发时&#xff0c;如何通知联系人&#xff0c;…

索引优化与查询优化(补充篇)

其他优化策略 exist和in的区别 选择的标准&#xff1a;小表驱动大表 SELECT *FROM A WHERE cc IN (SELECT cc FROM B)SELECT *FROM A WHERE EXISTS (SELECT cc FROM B WHERE B.ccA.cc)当A小于B时&#xff0c;用EXISTS。因为EXISTS的实现&#xff0c;相当于外表循环&#xff0…

猜数字游戏(Rust实现)

文章目录 游戏说明游戏效果展示游戏代码游戏代码详解生成神秘数字读取用户输入解析用户输入进行猜测比较 游戏说明 游戏说明 游戏运行逻辑如下&#xff1a; 随机生成一个1-100的数字作为神秘数字&#xff0c;并提示玩家进行猜测。如果玩家猜测的数字小于神秘数字&#xff0c;则…

智慧河湖方案:AI赋能水利水务,构建河湖智能可视化监管大数据平台

一、方案背景 我国江河湖泊众多&#xff0c;水系发达。伴随着经济社会快速发展&#xff0c;水生态水环境问题成为群众最关注的民生议题之一。一些河流开发利用已接近甚至超出水环境承载能力&#xff0c;一些地区废污水排放量居高不下&#xff0c;一些地方侵占河道、围垦湖泊等…

Apache SeaTunnel Web 功能正式发布!

Apache SeaTunnel Web 功能正式发布&#xff01; 在大数据技术的不断进步之下&#xff0c;Apache SeaTunnel 成为了众多开发者和企业关注的焦点。今天&#xff0c;我们很高兴地宣布&#xff1a;Apache SeaTunnel Web功能已正式发布&#xff0c;带来了前所未有的易用性和效率。…

手把手带你使用VSCode 搭建 STM32开发环境!

首先附上一张VS Code图一直都喜欢这种&#xff0c;黑色主题感觉高大上。 一、需要的软件和工具。 下载最新版VS Code: 安装好插件&#xff0c;具有良好的代码补全与调试功能。 “ VS Code下载地址&#xff1a;https://code.visualstudio.com/ ” 下载 LLVM&#xff1a;用于代码…

DeFi世界 MXT脱颖而出 利好不断

​​MixTrust希望成为用户在Web3世界的专用金融平台&#xff0c;注重为用户提供个性化的金融服务。而WorldCoin的愿景则是建设一个全球最大的、公平的数字身份和货币体系&#xff0c;强调构建一个涵盖全球范围的身份认证和货币交易系统。 扩展性 在扩展性方面&#xff0c;双方…

操作系统体系结构和OS

1.冯诺依曼计算机体系 关于冯诺伊曼系统&#xff0c;在这里我只是简单讲一讲&#xff0c;更加详细的内容可以看我的计算机组成系列。 常见的笔记本、台式机&#xff0c;不常见的服务器、工作站&#xff0c;大部分都遵守“冯诺依曼体系”&#xff0c;因此该计算机体系就是现代…

易点易动设备管理系统帮助生产企业提升设备巡检效率

在现代制造业中&#xff0c;设备的正常运行对于生产企业的成功至关重要。然而&#xff0c;设备巡检是确保设备安全性和可靠性的关键环节&#xff0c;但却常常耗费大量时间和资源。为了解决这个问题&#xff0c;许多企业采用了现代化的设备管理系统&#xff0c;其中易点易动设备…

简单谈谈我参加数据分析省赛的感受与体会

数据分析省赛的感受与体会 概要考试前的感受与体会考试注意事项小结 概要 大数据分析省赛指的是在省级范围内举办的大数据分析竞赛活动。该竞赛旨在鼓励和推动大数据分析领域的技术创新和人才培养&#xff0c;促进大数据技术与应用的深度融合&#xff0c;切实解决实际问题。参…

通讯协议学习之路:有线通讯协议总览

通讯协议之路主要分为两部分&#xff0c;第一部分从理论上面讲解各类协议的通讯原理以及通讯格式&#xff0c;第二部分从具体运用上讲解各类通讯协议的具体应用方法。 后续文章会同时发表在个人博客(jason1016.club)、CSDN&#xff1b;视频会发布在bilibili(UID:399951374) 一、…