Jetpack Hilt

1,587 阅读12分钟

Hilt是Google推出的,基于Dragger的,供Android使用的依赖注入库。文章内容会介绍什么是依赖注入?并介绍Hilt的使用。
Github的项目地址

一:什么是依赖注入?

// 比如房间里面有张桌子
class Room{
  val desk:Desk?=null
}
class Desk{
  var name = ""
}

此时Desk就是Room的依赖。而我们初始化一个依赖有两种方式:第一种自己初始化

class Room{
  val desk = Desk()
}

第二种外面帮你初始化

class DeskFactory{
  fun newDesk():Desk{
    val desk = Desk().apply{
      name = "书桌"
    }
    return desk
  }
}
class Room{
  val desk:Desk?=null 
}

class Examle{
  fun main(){
    val room = Room()
    room.desk = DeskFactory().newDesk()
  }
}

像这种外面帮你初始化的方式,就是依赖注入,只要是外部初始化的方式都是依赖注入。而Hilt做的事情就是自动的去初始化依赖。而无需我们手动初始化依赖,所以依赖注入其实平时我们一直在使用,并非Hilt特有,而Hilt主要解决的就是自动去进行依赖注入的问题。这点需要弄清楚,下面将如何使用

二:添加依赖

首先,将 hilt-android-gradle-plugin 插件添加到project的 build.gradle 文件中:

buildscript {
    ...
    dependencies {
        ...
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
    }
}

然后,应用 添加插件并在 app/build.gradle 文件中添加以下依赖项:

...
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'

android {
    ...
}

dependencies {
    implementation "com.google.dagger:hilt-android:2.28-alpha"
    kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}
// Hilt 使用 Java 8 功能。如需在项目中启用 Java 8,请将以下代码添加到 app/build.gradle 文件中:
android {
  ...
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}

注意:同时使用 Hilt 和数据绑定的项目需要 Android Studio 4.0 或更高版本。

三:使用

1.依赖注入Android类

Hilt 目前支持以下 Android 类:

  • Application(通过使用 @HiltAndroidApp)
  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver 注意:在 Hilt 对 Android 类的支持方面还要注意以下几点:
  • Hilt 仅支持扩展 ComponentActivity 的 Activity,如 AppCompatActivity。
  • Hilt 仅支持扩展 androidx.Fragment 的 Fragment。
  • Hilt 不支持保留的 Fragment。
1.1.@HiltAndroidApp的使用

在Application添加@HiltAndroidApp

@HiltAndroidApp
class HiltTestApp :Application(){}
1.2.@AndroidEntryPoint的使用

在Acitivity中使用

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var user:UserModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        user.name = "Activity UserName"
        user.id = 1
        tv_name.text = user.name
    }
}

class UserModel @Inject constructor():Serializable{

    var id = 0
    var name = ""

    override fun toString(): String {
        return "UserModel(name=$name id$id)"
    }
}
// activity_main.xml文件
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:text="Hello World!"
        android:gravity="center"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

在Fragment中使用

@AndroidEntryPoint
class UserFragment :Fragment(){

    var mRootView: View? = null
    @Inject lateinit var mUser:UserModel
    lateinit var tvName:TextView

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        mRootView = inflater.inflate(R.layout.fragment_user,null)
        initView(mRootView!!)
        return mRootView
    }

    private fun initView(view:View){
        tvName = view.findViewById(R.id.tv_name)
        mUser.name = "Fragment UserName"
        tvName.text = mUser.name
    }

}

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
 	supportFragmentManager.beginTransaction().replace(R.id.layout_content,UserFragment()).commitAllowingStateLoss()
    }
}

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/layout_content"
        app:layout_constrainedHeight="true"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

在View中使用

@AndroidEntryPoint
class UserView:LinearLayout{

    @Inject lateinit var user:UserModel

    constructor(context: Context):this(context,null,0)
    constructor(context: Context, attrs:AttributeSet?):this(context,attrs,0)
    constructor(context: Context, attrs:AttributeSet?,defStyleAttr:Int=0):super(context,attrs,0)

    init {
        LayoutInflater.from(context).inflate(R.layout.layout_userview,this)
        user.name = "UserView UserName"
        tv_name.text = user.name
    }

}

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}
// activity_main的xml文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.example.jetpackhilttest.widget.UserView
        android:id="@+id/userView"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</androidx.constraintlayout.widget.ConstraintLayout>

需要注意的是,如果使用 @AndroidEntryPoint 为某个 Android 类添加注释,则还必须为依赖于该类的 Android 类添加注释。例如,如果您为某个 Fragment 添加注释,则还必须为使用该 Fragment 的所有 Activity 添加注释。

1.3 @Inject的使用

Hilt需要知道给谁提供实例,并且如何去提供实例
@Inject注解在成员变量上:告知是Hilt要给该谁提供实例,如下:

// 告知是Hilt要给该类提供实例
@Inject lateinit var user:UserModel 

@Inject注解在类上,去告知Hilt 如何去提供实例。

class UserModel @Inject constructor():Serializable{
    var id = 0
    var name = ""
}

上面例子就是Hilt的基础用法,我们举例了Activity,View,Fragment。Service跟BroadcastReceiver就不举例,跟Activity等类似。

2.Hilt @Module的用法

有时,类型不能通过构造函数注入。发生这种情况可能有多种原因。例如,您不能通过构造函数注入接口。此外,您也不能通过构造函数注入不归您所有的类型,如来自外部库的类。在这些情况下,您可以使用 Hilt的@Module向 Hilt 提供绑定信息

  • Hilt的模块是一个带有 @Module 注释的类,它会告知 Hilt 如何提供某些类型的实例,
  • 且必须使用 @InstallIn 为 Hilt 模块添加注释,以告知 Hilt 每个模块将用在或安装在哪个 Android 类中
  • 并使用 @Binds 注入接口实例:@Binds 主要是用于接口,注释会告知 Hilt 在需要提供接口的实例时要使用哪种实现
  • 使用 @Provides 注入实例 :@Provides 主要是用于第三方类,注释告知 Hilt 如何提供此类型的实例
2.1 @Bind的使用例子

比如有个接口叫IAnimal

interface IAnimal {
    fun eat()
    fun animalName():String
}

有三个实现类继承了这个IAnimal,分别是Monkey,Tiger,Sheep

class Monkey @Inject constructor():IAnimal{

    override fun eat() {
        Log.d("monkey===","eat banana")
    }

    override fun animalName(): String {
        return "我是猴子"
    }
}
class Tiger @Inject constructor():IAnimal{

    override fun eat() {
        Log.d("tiger===","eat meat")
    }

    override fun animalName(): String {
        return "我是老虎"
    }
}
class Sheep @Inject constructor():IAnimal{

    override fun eat() {
        Log.d("sheep===","eat green grass")
    }

    override fun animalName(): String {
        return "我是羊"
    }
}

当Activity中使用了IAnimal这个依赖的时候,我们不知道具体的这个IAnimal到底要用哪个实现。

// @Inject lateinit var animal:IAnimal 使用了依赖注入,但是我们不知道这个animal的具体实现
@AndroidEntryPoint
class AnimalActivity : AppCompatActivity() {

    @Inject lateinit var animal:IAnimal

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_animal)
        tv_activity_animal.text = animal.animalName()
    }
}

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv_activity_animal"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:gravity="center"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

@Inject lateinit var animal:IAnimal 我们不知道Activity中的animal具体是用了何种实现。这时候@Binds就派上用场了,他会去告知Hilt,具体用了哪种实现。如下:

@Module
@InstallIn(ActivityComponent::class)
abstract class AnimalModule {
    @Binds
    abstract fun bindMonkey(monkey: Monkey):IAnimal
}

@Binds
abstract fun bindMonkey(monkey: Monkey):IAnimal 去告诉了Hilt要用Monkey这个实现,作用范围是@InstallIn(ActivityComponent::class) 这个ActivityComponent表示所有Activity。\

总结:由于需要注入的是接口(IAnimal),由于不知道使用的具体是哪个实现类,所以无法在构造器里面注入,这时候我们考虑用@Module,并考虑用@Binds 来告知具体用何种实现。\

@Binds必须是抽象类里 (上面例子:abstract class AnimalModule),修饰抽象方法(上面例子:abstract fun bindMonkey(monkey: Monkey)),并且返回值是接口类型 (上面例子:IAnimal)。而且抽象方法的入参,就表示实现类。比如这里入参是monkey:Monkey,就表示实现类用的是Monkey\

@InstallIn(ActivityComponent::class) 表示作用的组件是Activity。@InstallIn这个后面单独讲。

2.2 @Qualifier为同一种类型提供多个绑定

上面我们提到IAnimal有三个实现类,而上面我们的例子中@Binds只做了一种实现类,那如果我们三个都要呢?比如在Activity中使用Monkey,在View中使用Sheep,在Fragment中实现Tiger。 这时候就需要用到@Qualifier 给相同类型的类或接口注入不同的实例。如下:
使用@Qualifier 定义三种绑定

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

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

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

接着在Module里面做修改

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

    @bindMonkeyAnimal
    @Binds
    abstract fun bindMonkey(monkey: Monkey):IAnimal

    @bindTigerAnimal
    @Binds
    abstract fun bindTiger(tiger: Tiger):IAnimal

    @bindSheepAnimal
    @Binds
    abstract fun bindSheep(sheep: Sheep):IAnimal
}

我们会看到 @bindMonkeyAnimal 表示使用Monkey实现,@bindTigerAnimal使用Tiger实现,@bindSheepAnimal使用Sheep实现,接着在使用到IAnimal注入的地方也需要标明具体是用何种实现。

@AndroidEntryPoint
class AnimalActivity : AppCompatActivity() {

    @bindMonkeyAnimal
    @Inject lateinit var animal:IAnimal

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

        tv_activity_animal.text = animal.animalName()
        supportFragmentManager.beginTransaction().replace(R.id.layout_content, AnimalFragment()).commitAllowingStateLoss()
    }
}

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv_activity_animal"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:gravity="center"
        app:layout_constraintTop_toTopOf="parent" />

    <com.example.jetpackhilttest.widget.AnimalView
        android:id="@+id/animal_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        app:layout_constraintTop_toBottomOf="@+id/tv_activity_animal" />

    <FrameLayout
        android:id="@+id/layout_content"
        app:layout_constrainedHeight="true"
        app:layout_constraintTop_toBottomOf="@+id/animal_view"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_width="match_parent"
        android:layout_height="0dp"/>

</androidx.constraintlayout.widget.ConstraintLayout>

如上在Activity标明用@bindMonkeyAnimal Monkey实现

@AndroidEntryPoint
class AnimalView:LinearLayout{

    @bindSheepAnimal
    @Inject lateinit var animal:IAnimal

    constructor(context: Context):this(context,null,0)
    constructor(context: Context, attrs:AttributeSet?):this(context,attrs,0)
    constructor(context: Context, attrs:AttributeSet?,defStyleAttr:Int=0):super(context,attrs,0)

    init {
        LayoutInflater.from(context).inflate(R.layout.layout_userview,this)
        tv_name.text = animal.animalName()
    }
}

如上在AnimalView。该View里面表明用@bindSheepAnimal Sheep实现。

@AndroidEntryPoint
class AnimalFragment :Fragment(){

    var mRootView: View? = null
    @bindTigerAnimal
    @Inject lateinit var mAnimal:IAnimal
    lateinit var tvName:TextView

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        mRootView = inflater.inflate(R.layout.fragment_user,null)
        initView(mRootView!!)
        return mRootView
    }

    private fun initView(view:View){
        tvName = view.findViewById(R.id.tv_name)
        tvName.text = mAnimal.animalName()
    }

}

如上在Fragment里标明用 @bindTigerAnimal Tiger实现。
上面这样就实现了,我们说的Activity用Monkey,Fragment用Tiger,View用Sheep去实现IAnimal的不同实现类。

2.3 @Providers 注入实例

上面我们讲到当接口无法通过构造函数注入类型的时候,可以用@Module跟@Binds,但接口不是无法通过构造函数注入类型的唯一一种情况。如果某个类不归您所有(因为它来自外部库,如 Retrofit、OkHttpClient 或 Room 数据库等类),或者必须使用构建器模式创建实例,也无法通过构造函数注入。这时候可以考虑用@Module跟@Providers进行注入实例。例子如下:

@Module
@InstallIn(ApplicationComponent::class)
object NetworkModule {

    @Singleton
    @Provides
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .connectTimeout(20, TimeUnit.SECONDS)
            .readTimeout(20, TimeUnit.SECONDS)
            .writeTimeout(20, TimeUnit.SECONDS)
            .build()
    }

    @Singleton
    @Provides
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create())
            .baseUrl("https://api.github.com/")
            .client(okHttpClient)
            .build()
    }

    @Singleton
    @Provides
    fun providerGitHubService(retrofit: Retrofit): GitHubService {
        return retrofit.create(GitHubService::class.java)
    }
}

由于Retrofit使用到第三方的类,无法在构造器里注入,所以使用@Module跟@Provides。
由于全局中只有一个Retrofit就行,所以我们@InstallIn(ApplicationComponent::class) 使用的是ApplicationComponent,并且在提供provideRetrofit的方法上面添加@Singleton表示单例唯一。
上面provideOkHttpClient方法是OkHttpClient。
provideRetrofit方法是提供Retrofit
providerGitHubService方法是提供我们定义的GitHubService
上面写法就是@Provides的用法,当某个类不能用构造函数注入,并且该类又不归我们所有,这种情况一般会用@Providers去实现注入,有别于@Binds是,@Provides是直接写正常的方法函数,并且在函数里返回具体的实例。而@Binds确实抽象类抽象函数。
其他代码:这里是retrofit官网的提供的接口

interface GitHubService {
    @GET("users/{user}/repos")
    suspend fun listRepos(@Path("user") user: String): List<Repo>
}

对应的Activity代码如下:

@AndroidEntryPoint
class NetworkActivity : AppCompatActivity() ,CoroutineScope by MainScope(){

    @Inject
    lateinit var service:GitHubService

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

        launch (Dispatchers.Main){
            val repos= service.listRepos("octocat")
            tv_name.text = repos[0].archive_url
        }

    }

    override fun onDestroy() {
        cancel()
        super.onDestroy()
    }
}

3 Hilt的组件

上面我们跳过了 @InstallIn 这个注解,现在解释一下这个注解。InstallIn,就是安装到的意思。那么 @InstallIn(ActivityComponent::class),就是把这个模块安装到 Activity 组件当中。
既然是安装到了 Activity 组件当中,那么自然在 Activity 中是可以使用由这个模块提供的所有依赖注入实例。另外,Activity 中包含的 Fragment 和 View 也可以使用,但是除了 Activity、Fragment、View 之外的其他地方就无法使用了。
比如说,我们在 Service 中使用 @Inject 来对 Retrofit 类型的字段进行依赖注入,就一定会报错,别担心,Hilt给我们提供了可供Service使用的组件。 Hilt 提供了以下组件:

ApplicationComponentApplication
ActivityRetainedComponentViewModel
ActivityComponentActivity
FragmentComponentFragment
ViewComponentView
ViewWithFragmentComponent带有 @WithFragmentBindings 注释的 View
ServiceComponentService

注意:Hilt 不会为广播接收器生成组件,因为 Hilt 直接从 ApplicationComponent 注入广播接收器。

3.1 组件生命周期

既然有了这些组件,那么这些组件是在什么时机创建跟销毁的呢?
Hilt 会按照相应 Android 类的生命周期自动创建和销毁生成的组件类的实例。

生成的组件创建时机销毁时机
ApplicationComponentApplication#onCreate()Application#onDestroy()
ActivityRetainedComponentActivity#onCreate()Activity#onDestroy()
ActivityComponentActivity#onCreate()Activity#onDestroy()
FragmentComponentFragment#onAttach()Fragment#onDestroy()
ViewComponentView#super()视图销毁时
ViewWithFragmentComponentView#super()视图销毁时
ServiceComponentService#onCreate()Service#onDestroy()

注意:ActivityRetainedComponent 在配置更改后仍然存在,因此它在第一次调用 Activity#onCreate() 时创建,在最后一次调用 Activity#onDestroy() 时销毁。

3.2 组件作用域

默认情况下,Hilt会为每次的依赖注入行为都创建新的实例,但有些时候我们认为这是没有必要的,比如上面的Retrofit来说,不需要每次注入都创建,只要创建一次就可以了,所以我们就加了@Singleton注解,告知只需创建一次。
而像Singleton这种注解就是作用域,
作用域就是为了支持,在特定的时候能给到共享单例,而无需每次都初始化新的实例,除了@Singleton这种作用域外,还有如下对应的几种:
下表列出了生成的每个组件的作用域注释:

Android 类生成的组件作用域
ApplicationApplicationComponent@Singleton
View ModelActivityRetainedComponent@ActivityRetainedScope
ActivityActivityComponent@ActivityScoped
FragmentFragmentComponent@FragmentScoped
ViewViewComponent@ViewScoped
带有 @WithFragmentBindings 注释的 ViewViewWithFragmentComponent@ViewScoped
ServiceServiceComponent@ServiceScoped

也就是说,如果想要在整个程序内共用某个对象的实例,那么就使用 @Singleton。如果想要在某个Activity,以及它内部包含的 Fragment 和 View 中共用某个对象的实例,那么就使用 @ActivityScoped。以此类推
另外,我们不必非得在某个 Module 中使用作用域注解,也可以直接将它声明到任何可注入类的上方。比如我们对 Sheep 类进行如下声明:

@Singleton
class Sheep @Inject constructor() {}

这就表示,Sheep 在整个程序内都会共享同一个实例,并且整个程序内都可以对 Sheep 类进行依赖注入。
而如果注解改成 @ActivityScoped,那么就表示 Sheep 在同一个 Activity 内部将会共享同一个实例。

那么这个作用域的范围到底是谁更广,谁更小呢?他们的包含关系是怎么样的,下面就是作用域的层次结构图:

3.3 作用域的层次结构图

blockchain

简单来讲,就是对某个类声明了某种作用域注解之后,这个注解的箭头所能指到的地方,都可以对该类进行依赖注入,同时在该范围内共享同一个实例
比如 @Singleton 注解的箭头可以指向所有地方。而 @ServiceScoped 注解的箭头无处可指,所以只能限定在 Service 自身当中使用。@ActivityScoped 注解的箭头可以指向 Fragment、View 当中

4 Hilt中的预定义限定符

Hilt 提供了一些预定义的限定符。例如,由于您可能需要来自Application或 Activity 的 Context 类,因此 Hilt 提供了 @ApplicationContext 和 @ActivityContext 限定符
由于Android中Context有不同的取值,当我们想要注入context的时候,Hilt却不知道到底用的何种Context。这时候预定义限定符就派上用场了。 代码如下:

class AnalyticsAdapter @Inject constructor(
    @ActivityContext private val context: Context
) { ... }

或者

class ExampleController @Inject constructor(
    @ApplicationContext private val context: Context
) { ... }

5 在不支持的类中注入依赖项: @EntryPoint的使用

我们前面学到Hilt支持的类有如下几种:

  • Application
  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver 而当遇到不支持的类,我们又想要去做依赖注入的时候,应该如何处理?我们可以通过@EntryPoint去注解创建入口点。
class ExampleContentProvider : ContentProvider() {
  @EntryPoint
  @InstallIn(ApplicationComponent::class)
  interface ExampleContentProviderEntryPoint {
    fun analyticsService(): GitHubService
  }
  ...
}

拿ContentProvider来举例,我们在里面添加@EntryPoint创建入口点。并且用@InstallIn(ApplicationComponent::class) 标明使用组件,并声明一个接口ExampleContentProviderEntryPoint,在里面定义方法去获取GitHubService

class ExampleContentProvider: ContentProvider() {
    ...
  override fun query(...): Cursor {
    val appContext = context?.applicationContext ?: throw IllegalStateException()
    val hiltEntryPoint =
      EntryPointAccessors.fromApplication(appContext, ExampleContentProviderEntryPoint::class.java)
    val analyticsService = hiltEntryPoint.analyticsService()
    ...
  }
}

访问入口点,通过EntryPointAccessors的fromApplication方法,传入我们定义ExampleContentProviderEntryPoint的接口类。获取到对象,就可以调用对象的analyticsService方法来获取GitHubService。
以上就是对不支持的类添加注解的用法