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 提供了以下组件:
ApplicationComponent | Application |
---|---|
ActivityRetainedComponent | ViewModel |
ActivityComponent | Activity |
FragmentComponent | Fragment |
ViewComponent | View |
ViewWithFragmentComponent | 带有 @WithFragmentBindings 注释的 View |
ServiceComponent | Service |
注意:Hilt 不会为广播接收器生成组件,因为 Hilt 直接从 ApplicationComponent 注入广播接收器。
3.1 组件生命周期
既然有了这些组件,那么这些组件是在什么时机创建跟销毁的呢?
Hilt 会按照相应 Android 类的生命周期自动创建和销毁生成的组件类的实例。
生成的组件 | 创建时机 | 销毁时机 |
---|---|---|
ApplicationComponent | Application#onCreate() | Application#onDestroy() |
ActivityRetainedComponent | Activity#onCreate() | Activity#onDestroy() |
ActivityComponent | Activity#onCreate() | Activity#onDestroy() |
FragmentComponent | Fragment#onAttach() | Fragment#onDestroy() |
ViewComponent | View#super() | 视图销毁时 |
ViewWithFragmentComponent | View#super() | 视图销毁时 |
ServiceComponent | Service#onCreate() | Service#onDestroy() |
注意:ActivityRetainedComponent 在配置更改后仍然存在,因此它在第一次调用 Activity#onCreate() 时创建,在最后一次调用 Activity#onDestroy() 时销毁。
3.2 组件作用域
默认情况下,Hilt会为每次的依赖注入行为都创建新的实例,但有些时候我们认为这是没有必要的,比如上面的Retrofit来说,不需要每次注入都创建,只要创建一次就可以了,所以我们就加了@Singleton注解,告知只需创建一次。
而像Singleton这种注解就是作用域,
作用域就是为了支持,在特定的时候能给到共享单例,而无需每次都初始化新的实例,除了@Singleton这种作用域外,还有如下对应的几种:
下表列出了生成的每个组件的作用域注释:
Android 类 | 生成的组件 | 作用域 |
---|---|---|
Application | ApplicationComponent | @Singleton |
View Model | ActivityRetainedComponent | @ActivityRetainedScope |
Activity | ActivityComponent | @ActivityScoped |
Fragment | FragmentComponent | @FragmentScoped |
View | ViewComponent | @ViewScoped |
带有 @WithFragmentBindings 注释的 View | ViewWithFragmentComponent | @ViewScoped |
Service | ServiceComponent | @ServiceScoped |
也就是说,如果想要在整个程序内共用某个对象的实例,那么就使用 @Singleton。如果想要在某个Activity,以及它内部包含的 Fragment 和 View 中共用某个对象的实例,那么就使用 @ActivityScoped。以此类推
另外,我们不必非得在某个 Module 中使用作用域注解,也可以直接将它声明到任何可注入类的上方。比如我们对 Sheep 类进行如下声明:
@Singleton
class Sheep @Inject constructor() {}
这就表示,Sheep 在整个程序内都会共享同一个实例,并且整个程序内都可以对 Sheep 类进行依赖注入。
而如果注解改成 @ActivityScoped,那么就表示 Sheep 在同一个 Activity 内部将会共享同一个实例。
那么这个作用域的范围到底是谁更广,谁更小呢?他们的包含关系是怎么样的,下面就是作用域的层次结构图:
3.3 作用域的层次结构图
简单来讲,就是对某个类声明了某种作用域注解之后,这个注解的箭头所能指到的地方,都可以对该类进行依赖注入,同时在该范围内共享同一个实例
比如 @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。
以上就是对不支持的类添加注解的用法