Android--Hilt入门

谷歌接管Dagger后,推出了自己的Hilt框架,Hilt基于Dagger做了一层封装,大大简化了Dagger的使用,定制了一系列规范,并支持Jetpack中部分组件,是一个专门为安卓开发的DI框架

一、构造函数注入

和Dagger相同,Hilt也分两种注入方式,以上篇Dagger中的代码为例子,来对比两个框架的使用区别

1.gradle中配置依赖

工程gradle中导入插件:

dependencies {
        ...
        classpath "com.google.dagger:hilt-android-gradle-plugin:2.40.4"
    }

moudle中进行依赖:

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}

dependencies {
    ...
    def hilt_version = "2.40.4"
    implementation "com.google.dagger:hilt-android:$hilt_version"
    kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
}
2.使用@Inject注解定义需要注入的类
/**
 * 模拟本地数据源
 */
class LocalDataSource @Inject constructor()
/**
 * 模拟远程数据源
 */
class RemoteDataSource @Inject constructor()

定义包装类DataSource,包含上面两个类,同样对构造函数使用@Inject注解

/**
 * 数据源包装类
 * Created by aruba on 2021/12/4.
 */
data class DataSource @Inject constructor(
    var remoteDataSource: RemoteDataSource,
    var localDataSource: LocalDataSource
)
3.使用@HiltAndroidApp注解Application,表示注入中间件

Dagger使用的是@Component注解表示一个组件,上篇文章也提到过,一个项目对应一个Component就足够了,Hilt规范了Component

@HiltAndroidApp
class App : Application()
4.在Activity中使用@Inject注解对象
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var dataSource: DataSource

...
5.对Activity使用@AndroidEntryPoint注解

在Dagger中需要调用Component的注入方法,Hilt中直接使用注解就可以实现注入

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var dataSource: DataSource

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

        Log.i("aruba_log", dataSource.toString())
    }
}

日志结果:
I/aruba_log: DataSource(remoteDataSource=com.aruba.hiltapplication.di.datasource.RemoteDataSource@f7f11fd, localDataSource=com.aruba.hiltapplication.di.datasource.LocalDataSource@743eef2)

Android--Hilt入门_第1张图片

和Dagger相比,我们多导入了一个插件,此插件是利用Javassist,将编译后将@AndroidEntryPoint注解的Activity继承至自己生成的类

Android--Hilt入门_第2张图片

下面是该例子生成的Hilt_MainActivity:

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;
      // 调用component的注入方法
      ((MainActivity_GeneratedInjector) this.generatedComponent()).injectMainActivity(UnsafeCasts.unsafeCast(this));
    }
  }

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

二、模块注入

1.依赖网络框架
dependencies {
    ...

    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation "com.squareup.retrofit2:converter-scalars:2.9.0"
}

别忘了在Manifest.xml中添加权限

2.定义Retrofit API
interface BaiduApiService {
    @GET("/index.html")
    fun index(): Call
}
3.定义模块
  • 和Dagger相同,使用@Moudle注解就可以表示一个模块,使用@Provides注解提供给Component生成注入对象的方法
  • 使用@InstallIn注解,指定该模块需要装载到哪些Component中,并且我们不必再定义组件了,Hilt预定义了我们移动开发中所需的组件和子组件

这边指定其装载到SingletonComponent中,也就是全局APP中,旧版本的ApplicationComponent已废弃

@InstallIn(SingletonComponent::class)
@Module
class NetworkModule {

    @Provides
    fun getBaiduApiService(): BaiduApiService {
        return Retrofit.Builder()
            .baseUrl("https://www.baidu.com")
            .addConverterFactory(ScalarsConverterFactory.create())
            .build().create(BaiduApiService::class.java)
    }
}
4.在Activity中使用
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var dataSource: DataSource
    @Inject
    lateinit var baiduApiService: BaiduApiService
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Log.i("aruba_log", dataSource.toString())

        getIndex()
    }

    /**
     * 获取百度首页
     */
    private fun getIndex() {
        baiduApiService.index().enqueue(object : Callback {
            override fun onResponse(call: Call, response: Response) {
                findViewById(R.id.tv_hello).text = response.body()
            }

            override fun onFailure(call: Call, t: Throwable) {
            }
        })
    }
}

效果:

Android--Hilt入门_第3张图片

三、预定义组件与作用域

1.预定义的Component

Hilt定义的组件为SingletonComponent,子组件在dagger.hilt.android.components包下

Android--Hilt入门_第4张图片

这些组件对应的生命周期为:

组件 创建时机 销毁时机
SingletonComponent Application#onCreate() Application#onDestroy()
ActivityRetainedComponent Activity#onCreate() Activity#onDestroy()
ServiceComponent Service#onCreate() Service#onDestroy()
ActivityComponent Activity#onCreate() Activity#onDestroy()
ViewModelComponent ViewModel#super() ViewModel#clear()
FragmentComponent Fragment#onAttach() Fragment#onDestroy()
ViewComponent View#super() 视图销毁时
ViewWithFragmentComponent View#super() 视图销毁时
2.预定义的Scope

Hilt定义的子组件作用域在dagger.hilt.android.scopes包下

Android--Hilt入门_第5张图片

这些作用域都是和子组件一一对应的,组件的层级关系如下图:

Android--Hilt入门_第6张图片
组件-作用域层级关系
3.模块中使用作用域

Hilt的作用域就简单很多了,因为它预定义了组件和子组件 ,同时又定义了这些组件对应的作用域,上面的例子中,如何保证只实例化一份?使用SingletonComponent对应的作用域@Singleton即可,使用方法也是和Dagger相同的

@InstallIn(SingletonComponent::class) //表示全局组件
@Module
class NetworkModule {

    @Singleton
    @Provides
    fun getBaiduApiService(): BaiduApiService {
        return Retrofit.Builder()
            .baseUrl("https://www.baidu.com")
            .addConverterFactory(ScalarsConverterFactory.create())
            .build().create(BaiduApiService::class.java)
    }
}

在Activity中,注入两个对象,并打印下两者的hashcode:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var baiduApiService1: BaiduApiService
    @Inject
    lateinit var baiduApiService2: BaiduApiService
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Log.i("aruba_log","baiduApiService1 hashcode:${baiduApiService2.hashCode()}")
        Log.i("aruba_log","baiduApiService2 hashcode:${baiduApiService2.hashCode()}")
    }
}

日志结果:
I/aruba_log: baiduApiService1 hashcode:174572891
I/aruba_log: baiduApiService2 hashcode:174572891

4.构造方法使用作用域

ViewModelComponent是新出的子组件,对应的作用域为ViewModelScope,作用为:一个ViewModel中多个同类型注入对象,则使用同一份实例。以前实现ViewModel中注入还需要依赖其他框架,这次来使用ViewModelScope作为例子

4.1 定义注入类,并使用@ViewModelScope注解

注意:如果把参数放入主构造,并且赋了默认值,那么会生成两个构造方法,Hilt就会报错

@ViewModelScoped
class UserInfo @Inject constructor() {
    var name: String = "张三"
}
4.2 定义ViewModel,并为其添加@HiltViewModel注解

在构造中注入对象

@HiltViewModel
class MainViewModel @Inject constructor(var userInfo: UserInfo) : ViewModel()
4.3 Activity中使用
@AndroidEntryPoint
class ViewModelActivity : AppCompatActivity() {
    val viewModel: MainViewModel by lazy {
        ViewModelProvider(this).get(MainViewModel::class.java)
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_view_model)

        Log.i("aruba_log", viewModel.userInfo.name)
    }
}

日志结果:
I/aruba_log: 张三

5.@Qualifier注解解决注入冲突
  • 对于构造函数注入,只能有一个构造函数被@Inject注解,否则编译时报错
  • 对于模块注入,如果多个@Provides注解的方法返回相同类型,使用@Qualifier注解可以解决冲突,@Qualifier注解相当于为其取了个别名,在使用对象注入时也相应的使用@Qualifier注解,即可得到对应的注入对象
5.1 @Named解决注入冲突

@Named注解源码中,使用了@Qualifier元注解:

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {

    /** The name. */
    String value() default "";
}

模块代码如下:

@InstallIn(ViewModelComponent::class)
@Module
class UserInfo2Fetcher {

    @Named("zhao")
    @ViewModelScoped
    @Provides
    fun provideUserInfo(): UserInfo2 {
        return UserInfo2("赵四")
    }

    @Named("wang")
    @ViewModelScoped
    @Provides
    fun provideUserInfo2(): UserInfo2 {
        return UserInfo2("王五")
    }
}

UserInfo2:

class UserInfo2(val name: String)

ViewModel中,指定注入使用@Named("wang")

@HiltViewModel
class UserViewModel @Inject
constructor(
    var userInfo: UserInfo,
    @Named("wang") var userInfo2: UserInfo2
) : ViewModel()

最后在Activity中打印结果

Log.i("aruba_log", viewModel.userInfo2.name)

结果:
I/aruba_log: 王五

5.2 自定义注解解决冲突

我们也可以使用@Qualifier定义一个注解

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class Zhao()

使用自定义的注解,解决冲突

@InstallIn(ViewModelComponent::class)
@Module
class UserInfo2Fetcher {

    @Zhao
    @ViewModelScoped
    @Provides
    fun provideUserInfo(): UserInfo2 {
        return UserInfo2("赵四")
    }

    @Named("wang")
    @ViewModelScoped
    @Provides
    fun provideUserInfo2(): UserInfo2 {
        return UserInfo2("王五")
    }
}

ViewModel中替换成自定义的注解后运行

@HiltViewModel
class UserViewModel @Inject
constructor(
    var userInfo: UserInfo,
    @Zhao var userInfo2: UserInfo2
) : ViewModel()

结果:
I/aruba_log: 赵四

四、接口注入

当我们有一个接口,并且有它的实现类,那么Hilt也可以注入生成该接口。这也是Dagger的功能

1.定义接口
interface ICallback {
    fun onSuccess()
    fun onFailure()
}
2.实现类,并使用@Inject注解
class CallbackImpl @Inject constructor() : ICallback {
    override fun onSuccess() {
        Log.i("aruba_log", "onSuccess")
    }

    override fun onFailure() {
        Log.i("aruba_log", "onFailure")
    }
}
3.定义模块抽象类,提供抽象方法返回ICallback接口

该方法需要入参为实现类,并使用@Binds注解

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

    @Binds
    abstract fun provideCallback(callback: CallbackImpl): ICallback
}
4.Activity中注入,并调用方法:
@AndroidEntryPoint
class ViewModelActivity : AppCompatActivity() {
    @Inject
    lateinit var callback: ICallback

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_view_model)

        callback.onSuccess()
    }
}

日志结果:
I/aruba_log: onSuccess

五、默认绑定

Hilt定义的组件都绑定了安卓上下文相关对象,如:在ActivityComponent中注入的类,直接可以通过注入获取Activity对象
以上面接口实现类为例子

1.构造方法中使用@ActivityContext注解注入Context

除了@ActivityContext,还可以通过@ApplicationContext注入全局上下文
这边在回调时,弹一个toast

class CallbackImpl @Inject constructor(@ActivityContext var context: Context) : ICallback {
    override fun onSuccess() {
        Log.i("aruba_log", "onSuccess")
        Toast.makeText(context, "onSuccess", Toast.LENGTH_SHORT).show()
    }

    override fun onFailure() {
        Log.i("aruba_log", "onFailure")
        Toast.makeText(context, "onFailure", Toast.LENGTH_SHORT).show()
    }
}

运行效果:

Android--Hilt入门_第7张图片
2.注入Activity

我们也可以直接定义一个Activity的成员变量,并通过构造函数注入

class CallbackImpl @Inject constructor(var activity: Activity) : ICallback {
    override fun onSuccess() {
        Log.i("aruba_log", "onSuccess")
        Toast.makeText(activity, "onSuccess", Toast.LENGTH_SHORT).show()
    }

    override fun onFailure() {
        Log.i("aruba_log", "onFailure")
        Toast.makeText(activity, "onFailure", Toast.LENGTH_SHORT).show()
    }
}
3.组件-绑定对应关系

组件下注入类可以获取对应的绑定对象

组件 创建时机
SingletonComponent Application
ActivityRetainedComponent Application
ServiceComponent Application、Service
ActivityComponent Application、Activity
ViewModelComponent Application
FragmentComponent Application、Activity、Fragment
ViewComponent Application、Activity、View
ViewWithFragmentComponent Application、Activity、Fragment、View

总结

DI框架虽然带来了极大的便利,但无论是Dagger还是Hilt,在使用过程中,有必要使用流程图、UML图等来设计记录组件和作用域的绑定关系,以便后续使用与维护

Demo地址:https://gitee.com/aruba/hilt-application.git

你可能感兴趣的:(java,android,spring,安卓,移动开发)