dagger and Hilt 看完这篇就会啦

马上入职新公司了,新公司使用了Hilt,所以这几天先熟悉一下。Hilt是对Dagger的封装,所以还得先把Dagger练习一下。

这俩都是依赖注入框架,依赖注入是基于控制反转的原则,控制特定代码的执行也就是控制对象的创建,由外部容器创建对象,外部容器创建好之后,在注入到当前对象里面。

依赖注入是一种广泛应用的编程原则,遵循这种规范可以让代码更加的灵活,比如下面几个方面

  1. 解耦:对象的创建依赖于依赖注入的容器, 如果某个对象在很多地方使用,某天该对象改变了,或者实现类改变了,只需改变容器里的生成方式即可。
  2. 方便重构:项目中的网络框架、图片框架等都可以通过依赖注入来一键切换。
  3. 易于测试:测试的时候经常会mock假的对象,如果某段代码使用的对象本身就是从外部传过来的,那么mock一个对象传入进入就可以测试,加入是内部new的对象就没这么方便了。

依赖注入的方式有下面几种:

  1. 通过构造方法注入
  2. 通过setter方法注入
  3. 通过第三方类库动态注入,主要有两种方式:运行时动态方案,通过注解加反射的方式运行时生成依赖。编译时静态方案,在编译时生成连接依赖的代码。Dagger就是使用的第二种方案。

我们得先会用然后在去研究其实现原理,下面通过官方举的一个小例子来感受一下Dagger和Hilt的最基础的用法。这个小例子的功能是:

LoginActivity => LoginViewPresenter => LoginRepository => LoginLocalDatasource和LoginRemoteDatasource

就是一个LoginActivity通过LoginPresenter获取本地数据和远程数据的过程

Android中Dagger的用法

导包
apply plugin: 'kotlin-kapt'

dependencies {
    implementation 'com.google.dagger:dagger:2.x'
    kapt 'com.google.dagger:dagger-compiler:2.x'
}
通过@Inject注解告知Dagger如何创建当前对象

这里我们先把数据类和presenter类创建出来

class LoginPresenter @Inject constructor(
    private val loginRepository: LoginRepository
) {
    fun getData(){
        loginRepository.getData()
    }
}
class LoginRepository @Inject constructor(
    private val localDatasource: LoginLocalDatasource,
    private val loginRemoteDatasource: LoginRemoteDatasource
){
    fun getData() {
        localDatasource.getLocalData()
        loginRemoteDatasource.getRemoteData()
    }
}
class LoginLocalDatasource @Inject constructor() {
    fun getLocalData() {
        Log.d("getData","本地数据来啦")
    }
}
class LoginRemoteDatasource @Inject constructor() {
    fun getRemoteData() {
        Log.d("getData","远程数据来啦")
    }
}
通过@Component注解告诉Dagger哪些类需要注入
@Component
interface ApplicationComponent {
    fun injectLoginActivity(activity: LoginActivity)
    
    fun injectOtherctivity(activity: Otherctivit)
    
    ...
}

@Component作用在接口上,其内部的方法不支持多态,也就是说加入我们有多个activity需要注入,那就有几个就写几个类似的方法。

在application中拿到全局的Component组建对象供别处使用
class MyApp:Application() {
    val applicationComponent = DaggerApplicationComponent.create()
}
在LoginActivity中完成对象的注入并使用
class LoginActivity:AppCompatActivity() {
    @Inject
    lateinit var loginPresenter: LoginPresenter
    override fun onCreate(savedInstanceState: Bundle?) {
        (applicationContext as MyApp).applicationComponent.injectLoginActivity(this)
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        loginPresenter.getData()
    }
}

需要注意的是注入方法的调用需要写在super.onCreate()之前,避免出现Fragment恢复的问题。在 super.onCreate() 中的恢复阶段,Activity 会可能需要访问 Activity 绑定的 Fragment。

这时候通过@Inject注解注入一个loginPresenter对象,就可以在当前activity中直接使用了。

what! 为了创建一个对象竟然做了这么多的工作,这也太麻烦了吧,也正因为如此,也劝退了很多人,

不过由于依赖注入框架的优点也很多,谷歌为了让我们在应用中使用以来注入框架也是煞费苦心,Dagger难用,那就在简化一下吧,于是Hilt出现了。

Android中Hilt的基本用法

项目的gradle文件中引入gradle插件

classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'

app.gradle中引入

classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'

获取数据的类跟Dagger中的都是一样的这里就略过了,从别的不同的步骤开始

给application添加@HiltAndroidApp注解

此注解会触发Hilt 的代码生成操作

@HiltAndroidApp
class MyApp : Application() {}
在需要注入的类上添加@AndroidEntryPoint

此注解告诉Hilt注入点在哪里比如下面:

@AndroidEntryPoint
class LoginActivity:AppCompatActivity() {
    @Inject
    lateinit var loginPresenter: LoginPresenter
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        loginPresenter.getData()
    }
}

到这里我们就可以拿到loginPresenter对象自由的进行操作了。

跟前面的Dagger的使用对比一下可以发现,Hilt主要省去了我们手动写@Component组件相关的代码,在导入Hilt包的时候,我们发现引入了一个gradle插件hilt-android-gradle-plugin,谷歌在这里面做了一些文章,给我们自动插入了注入的代码。

到这里dagger和Hilt的最基本的用法就完成了,可以看到Hilt使用起来还是非常简单的。

Dagger 和 Hilt 其他用法

Dagger模块注入

前面通过在构造方法中添加@Inject注解来告诉dagger如何创建一个对象的实例,还有一种方法是通过dagger中的module模块来创建。就比如我们想要初始化一个第三方库中的对象,我们不可能去给它的构造方法天加@Inject注解,那就只能使用这种方式了。

@Module
class MyModule {

    @Provides
    fun httpObject(): HttpObject{
        return HttpObject()
    }

    @Provides
    fun httpDb(): DbObject{
        return DbObject()
    }
}
class HttpObject {
    fun doHttp(){
        Log.i("doHttp","doHttp method handle")
    }
}
class DbObject {
    fun doDb(){
        Log.i("doDb","doDb method handle")
    }
}

@Module注解告诉dagger这是一个module模块,其内部配合@Provides注解来使用 @Provides注解告诉dagger如何创建某个对象的实例

为了让dagger知道我们自定义的这个模块,需要在@Component注解后面加上modules参数,该参数是个数组,我们可以添加多个不同的module。

@Component(modules = [MyModule::class])
interface ApplicationComponent {

    fun injectLoginActivity(activity: LoginActivity)

    fun injectMainActivity(activity: MainActivity)
}

官方建议尽量使用@Inject注解来告知dagger如何创建一个对象,当该注解不能满足的时候在使用module,比如我们在创建某个对象之前需要做一些别的计算操作。或者一些第三方库无法通过构造方法来注入。因为每当需要提供某个类的实例的时候,dagger都会运行@Provides注解修饰的函数

dagger 的作用域

作用域可以将某个实例限定在特定的生命周期内,比如我们前面的HttpObject实例我们想要全局唯一,可以使用dagger自带的@Singleton注解来修饰ComponentModule,我们也可以自定义不同名称的注解,比如命名为@ApplicationScope

@Scope
@Documented
@Retention(RUNTIME)
public @interface ApplicationScope { }

使用构造函数注入(通过 @Inject)时,应在类上添加作用域注释;使用 Dagger 模块时,应在 @Provides 方法中添加作用域注释。比如下面带代码

@Singleton
@Component(modules = [MyModule::class])
interface ApplicationComponent {

    fun injectLoginActivity(activity: LoginActivity)

    fun injectMainActivity(activity: MainActivity)
}
@Singleton
class LoginLocalDatasource @Inject constructor() {
    fun getLocalData() {
        Log.d("getData","本地数据来啦")
    }
}
@Singleton
    @Provides
    fun httpObject(): HttpObject{
        return HttpObject()
    }
Hilt模块注入

跟dagger一样,当单纯的构造方法无法满足注入的时候,Hilt也提供了module模块来完成注入,跟dagger不同的是,Hilt需要在添加一个@InstallIn注解来告诉dagger该模块需要运行在哪个Android类中,比如:

@Module
@InstallIn(ActivityComponent::class) // 以告知 Hilt 每个模块将用在或安装在哪个 Android 类中。
object MyModule {
    @Provides
    fun httpObject(): HttpObject{
        return HttpObject()
    }
    @Provides
    fun httpDb(): DbObject{
        return DbObject()
    }
}

因为Hilt是专门给Android设计的,Hilt中提供了一些可供选择的Android类,我们可以根据自己的需求来选择不同作用域的对象。

Hilt 组件注入器面向的对象创建时机销毁时机
ApplicationComponentApplicationApplication#onCreate()Application#onDestroy()
ActivityRetainedComponentViewModelActivity#onCreate()Activity#onDestroy()
ActivityComponentActivityActivity#onCreate()Activity#onDestroy()
FragmentComponentFragmentFragment#onAttach()Fragment#onDestroy()
ViewComponentViewView#super()视图销毁时
ViewWithFragmentComponent带有 @WithFragmentBindings 注释的 ViewView#super()视图销毁时
ServiceComponentServiceService#onCreate()Service#onDestroy()
Hilt注入接口

Hilt还可以通过@Bind注解注入接口,比如

@Module
@InstallIn(ActivityComponent::class) 
abstract class HttpModule {

    @BindOkhttp
    @Binds
    abstract fun bindOkhttp(okhttpRequest: OkhttpRequest):IHttpRequest

    @BindVolley
    @Binds
    abstract fun bindVolley(volleyRequest: VolleyRequest):IHttpRequest

}
interface IHttpRequest {
    fun get()
}
class OkhttpRequest @Inject constructor(@ApplicationContext context: Context)
    :IHttpRequest{
    override fun get() {
        Log.d("IHttpRequest","OkhttpRequest handle")
    }
}
class VolleyRequest @Inject constructor(@ActivityContext context: Context)
    :IHttpRequest{
    override fun get() {
        Log.d("IHttpRequest","VolleyRequest handle")
    }
}

我们有一个接口和一些接口的实现类,通过@Binds注解,dagger在运行的时候,就会把参数中的实现类创建出来赋值给接口实例。我们就可以在其他地方使用该实例了。

我们知道接口是可以多实现的,当我们的接口有多个实现类的时候,上面的代码中我们可能需要写多个@Binds修饰的方法,那么如何区分要使用哪个实现类呢?Hilt中提供了自定义的注解限定符。这些限定符可以作用在@Binds@Provides修饰的方法上。比如下面定义两个限定符

// 不同的限定符只需名字不一样即可
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindOkhttp()
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindVolley()

在最终使用的地方也添加上需要使用哪种实现类的注解即可。这种方式可以在隔离第三方框架的时候使用,比如上面的网络请求框架OkHttp和Volley,最终只需要在下面的MainActivity中修改一个注解就可以完成切换。

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @BindOkhttp
    @Inject
    lateinit var httpRequest: IHttpRequest

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        httpRequest.get()
    }
}

Hilt中还预定了一些默认的限定符,比如前面代码中的@ApplicationContext@ActivityContext。如果我们类中的方法中需要这两个context,只需要加上这两个注解就可以使用了。

到这里dagger和Hilt的常用的方法就练习完啦,那它到底是怎么完成注入的呢

dagger的注入原理

首先前面我们导包的时候,通过kapt导入了dagger的注解处理器,所以我们知道dagger是在编译时通过注解处理器生成了一些辅助代码来创建对象。位置在/app/build/generated/source/kapt/debug文件夹里面

我们从前面的的HttpObject的例子看起

  1. 首先我们在application中调用了初始化的方法val applicationComponent = DaggerApplicationComponent.create()
  public static ApplicationComponent create() {
    return new Builder().build();
  }
  public ApplicationComponent build() {
    if (myModule == null) {
      this.myModule = new MyModule();
    }
    return new DaggerApplicationComponent(myModule);
  }
  private DaggerApplicationComponent(MyModule myModuleParam) {
    this.myModule = myModuleParam;
  }

创建出MyModule的实例对象并保存在DaggerApplicationComponent类的成员变量中。

  1. 然后我们在MainActivity中调用了初始化的方法(applicationContext as MyApp).applicationComponent.injectMainActivity(this)injectMainActivity是我们自定义的Component接口中的一个方法,点进去之后会看到它现在已经自动有了一个实现的方法。
 @Override
  public void injectMainActivity(MainActivity activity) {
    injectMainActivity2(activity);F
  }
 private MainActivity injectMainActivity2(MainActivity instance) {
    MainActivity_MembersInjector.injectHttpObject(instance, HttpModule_HttpObjectFactory.httpObject(myModule));
    return instance;
  }

HttpModule_HttpObjectFactory.httpObject(myModule)这句话点进去可以看到return Preconditions.checkNotNullFromProvides(instance.httpObject());直接调用了了myModule的httpObject()方法,而这个方法恰好就是我们自定义的如何创建HttpObject对象的方法,最后返回需要创建的对象。

  1. 最后调用MainActivity_MembersInjector.injectHttpObject方法
 @InjectedFieldSignature("com.yjt.daggertest.MainActivity.httpObject")
  public static void injectHttpObject(MainActivity instance, HttpObject httpObject) {
    instance.httpObject = httpObject;
  }

将前面创建好的对象,赋值给activity的成员变量httpObject注入完成。

总结一下很简单:我们写一些辅助代码告诉dagger如何创建某个对象,使用的时候我们将activity对象传入到dagger容器中,dagger拿到activity的对象,调用我们事先写好的创建对象的方法创建出该对象,赋值给activity的成员变量完事。

Hilt的注入原理

通过前面的练习我们知道Hilt针对dagger的优化部分,主要是省去了我们自己去写Component这块,改为自动生成相关的代码。

首先我们来到注解处理器生成的文件夹/app/build/generated/source/kapt/debug里面,会看到这样的一个文件Hilt_MainActivityMainActivity_GeneratedInjector这两个类

@Generated("dagger.hilt.android.processor.internal.androidentrypoint.ActivityGenerator")
public abstract class Hilt_MainActivity extends AppCompatActivity implements GeneratedComponentManagerHolder {
  private volatile ActivityComponentManager componentManager;

  private final Object componentManagerLock = new Object();

  private boolean injected = false;

  Hilt_MainActivity() {
    super();
    _initHiltInternal();
  }

  Hilt_MainActivity(int contentLayoutId) {
    super(contentLayoutId);
    _initHiltInternal();
  }

  private void _initHiltInternal() {
    addOnContextAvailableListener(new OnContextAvailableListener() {
      @Override
      public void onContextAvailable(Context context) {
        inject();
      }
    });
  }

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

  protected ActivityComponentManager createComponentManager() {
    return new ActivityComponentManager(this);
  }

  @Override
  public final ActivityComponentManager componentManager() {
    if (componentManager == null) {
      synchronized (componentManagerLock) {
        if (componentManager == null) {
          componentManager = createComponentManager();
        }
      }
    }
    return componentManager;
  }

  protected void inject() {
    if (!injected) {
      injected = true;
      ((MainActivity_GeneratedInjector) this.generatedComponent()).injectMainActivity(UnsafeCasts.<MainActivity>unsafeCast(this));
    }
  }

  @Override
  public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
    return DefaultViewModelFactories.getActivityFactory(this, super.getDefaultViewModelProviderFactory());
  }
}

public interface MainActivity_GeneratedInjector {
  void injectMainActivity(MainActivity mainActivity);
}

在该类继承自AppCompatActivity它的构造方法中监听到Context可用的时候,调用inject()注入方法。该方法中又调用到了MainActivity_GeneratedInjector中injectMainActivity方法。这个方法是不是很面熟,跟我们使用dagger的时候自己写的@Component类是一样的。

OK相关的类有了,我们在使用dagger的时候,会在activity的onCreate方法中写初始化注入的方法比如前面的(applicationContext as MyApp).applicationComponent.injectMainActivity(this),那Hilt是怎么将它生成的类跟我们的MainActivity结合到一起的呢?

这就用到了字节码插入的技术了,我们在引入Hilt的库的时候,在工程的根gradle文件中引入了一个hilt的gradle插件,这个插件就是用来插入代码的,下面来看一下插入后的文件。

我现在使用的gradle版本是7.0以上的版本,生成的文件位置在/app/build/intermediates/asm_instrumented_project_classes/debug文件夹下面。7.0以下的在/app/build/intermediates/transforms文件夹下面。

public final class MainActivity extends Hilt_MainActivity {
   @Inject
   public IHttpRequest httpRequest;

   @NotNull
   public final IHttpRequest getHttpRequest() {
      IHttpRequest var1 = this.httpRequest;
      if (var1 != null) {
         return var1;
      } else {
         Intrinsics.throwUninitializedPropertyAccessException("httpRequest");
         return null;
      }
   }

   public final void setHttpRequest(@NotNull IHttpRequest var1) {
      Intrinsics.checkNotNullParameter(var1, "<set-?>");
      this.httpRequest = var1;
   }

   /** @deprecated */
   // $FF: synthetic method
   @BindOkhttp
   public static void getHttpRequest$annotations() {
   }

   protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      this.setContentView(2131427357);
      ((TextView)this.findViewById(2131231121)).setOnClickListener(MainActivity::onCreate$lambda-0);
      this.getHttpRequest().get();
   }

   private static final void onCreate$lambda_0/* $FF was: onCreate$lambda-0*/(MainActivity this$0, View it) {
      Intrinsics.checkNotNullParameter(this$0, "this$0");
      this$0.startActivity(new Intent((Context)this$0, LoginActivity.class));
   }
}

可以看到,通过修改字节码,将我们的MainActivity改为了继承自它自己生成的Hilt_MainActivity了。那么当MainActity创建的时候,Hilt_MainActivity的构造方法也会调用,最终在监听到Context可用的时候执行注入的代码。

OK 完工 到这里应该可以熟练的使用这两个框架啦哈哈哈

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值