Hilt - SMAXLYB/JetpackPractice GitHub Wiki

Hilt

Hilt是一个依赖注入库,具体什么是依赖注入、怎么使用请看官方文档,这里只进行大致的原理讲解。

注解使用

1.@HiltAndroidApp

在Application类中标记@@HiltAndroidApp触发代码生成,例如:

@HiltAndroidApp
public final class MainApplication extends Application {
}

编译后生成的代码:

public abstract class Hilt_MainApplication extends Application implements GeneratedComponentManagerHolder {
  // 这里创建了application组件管理类的对象
  private final ApplicationComponentManager componentManager = new ApplicationComponentManager(new ComponentSupplier() {
    @Override
    public Object get() {
      // component负责把module提供的依赖注入到@inject标记的地方
      return DaggerMainApplication_HiltComponents_SingletonC.builder()
          // 保存了applicationContext,后续如果要访问applicationContext,将会从这里取
          // ApplicationContextModule是hilt自带的类,不是用户写的,因为只提供appilcationContext,没有其他逻辑
          .applicationContextModule(new ApplicationContextModule(Hilt_MainApplication.this))
          .build();
    }
  });

  @Override
  public final ApplicationComponentManager componentManager() {
    return componentManager;
  }

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

  @CallSuper
  @Override
  public void onCreate() {
    // 这里MainApplication_GeneratedInjector是一个接口,和Dragger是一样的,如果application本身也有字段要依赖注入,那么赋值的操作就是在这里完成的
    ((MainApplication_GeneratedInjector) generatedComponent()).injectMainApplication(UnsafeCasts.<MainApplication>unsafeCast(this));
    super.onCreate();
  }
}

注意MainApplication也会变化,在transform阶段被替换:

@HiltAndroidApp
public final class MainApplication extends Hilt_MainApplication {
   @Override
   public void onCreate() {
      super.onCreate();  // 如果application内还有字段注入,那么注入的时机就在这里
   }
}

2.@Inject

依赖注入的方式可以分为3种,一种是构造函数注入(constructor),另一种是字段注入(setter,注入的时机要尽可能的早,避免访问到null),还有就是方法注入(和字段注入差不多),使用@Inject注解指定注入依赖项的方式,但具体谁来提供这个依赖暂时不用管。例子:

public class Car {

   // 当构造函数的权限是public+仅有一个无参构造函数时,可以省略@Inject
   @Inject public Car(Engine engine) { ... }
  
   // 字段不能为final
   @Inject private Provider<Seat> seatProvider;
  
   // Injectable package-private method
   @Inject void install(Windshield windshield, Trunk trunk) { ... }
}

3.@AndroidEntryPoint

在Android中,可以为以下类提供注入:

  • Application(其中通过使用 @HiltAndroidApp)
  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() { ... }

生成的代码如下:

@Generated("dagger.hilt.android.processor.internal.androidentrypoint.ActivityGenerator")
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) {
    // 这里同样生成了一个component,用来注入activity里面的字段
    ((MainActivity_GeneratedInjector) generatedComponent()).injectMainActivity(UnsafeCasts.<MainActivity>unsafeCast(this));
    super.onCreate(savedInstanceState);
  }

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

  protected final ActivityComponentManager componentManager() {
    if (componentManager == null) {
      synchronized (componentManagerLock) {
        if (componentManager == null) {
          componentManager = new ActivityComponentManager(this);
        }
      }
    }
    return componentManager;
  }
  
  // 这里复写了viewmodelFractory, fragment也是
  @Override
  public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
    return DefaultViewModelFactories.getActivityFactory(this);
  }
}

4.@HiltViewModel

在activity或者fragment注入viewmodel和注入其他字段不一样,viewmodel是要通过宿主拿到的。在创建的时候使用了by viewmodels:

@HiltViewModel
public class DonutViewModel {
   public DonutViewModel(@Assisted SavedStateHandle handle, RecipeRepository repository) {
   ...
   }
}
@AndroidEntryPoint
class StatisticsFragment : Fragment() {
    private val viewModel by viewModels<DonutViewModel>()
    ...
}
@MainThread
public fun <VM : ViewModel> Fragment.createViewModelLazy(
    viewModelClass: KClass<VM>,
    storeProducer: () -> ViewModelStore,
    // 这里一直传的是空
    factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
    val factoryPromise = factoryProducer ?: {
        // 会使用这个来反射调构造函数创建viewmoel, activity也是一样的
        defaultViewModelProviderFactory
    }
    return ViewModelLazy(viewModelClass, storeProducer, factoryPromise)
}

5.@Named/@Qualifier

如果在module里面提供相同的返回值,就要用限定符进行区分:

 @Module
 @InstallIn(ActivityComponent::class)
 object AnalyticsModule {
    @Named(NAME_TITLE)
    @Provides
    fun testNamed(): String {
        return "原始Named限定符标题"
    }

    @Named(NAME_DESC)
    @Provides
    fun test1Named(): String {
        return "原始Named限定符描述"
    }
 }
@Module
 @InstallIn(ActivityComponent::class)
 object AnalyticsModule {

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

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

     @QualifierTitle
     @Provides
     fun testQualifier(): String {
         return "原始Qualifier标题"
     }

     @QualifierDesc
     @Provides
     fun test1Qualifier(): String {
         return "原始Qualifier描述"
     }
 }
// 第一种 Name方式
data class FakeService @Inject constructor(@Named(NAME_TITLE) var title: String, @Named(NAME_DESC) val des: String)
// 第二种
data class Fake1Service @Inject constructor(@AnalyticsModule.QualifierTitle var title: String, @AnalyticsModule.QualifierDesc var des: String)

6.@Binds

//第一步:提供了一个接口`AnalyticsService`
interface AnalyticsService {
  fun analyticsMethods()
}
//第二步:`AnalyticsServiceImpl`实现`AnalyticsService`接口,并实现内部方法
class AnalyticsServiceImpl @Inject constructor(
  ...
) : AnalyticsService { ... }
//第三步:可能还存在一个`AnalyticsServiceImpl_Test`也实现`AnalyticsService`接口,并实现内部方法
class AnalyticsServiceImpl_Test @Inject constructor(
  ...
) : AnalyticsService { ... }

@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {
  //第三步:通过`@Binds`将告知`AnalyticsService`的实现为`AnalyticsServiceImpl`而不是‘AnalyticsServiceImpl_Test’ 
  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

7.@Provides

如果无法通过构造函数进行注入,比如第三方库,或者通过builder来创建等等:

@Provides
fun provideOkHttpClient(): OkHttpClient {
    return OkHttpClient.Builder()
        .build()
}

@Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
     return Retrofit.Builder()
         .baseUrl(BASE_URL)
         .client(okHttpClient)
         .addConverterFactory(GsonConverterFactory.create())
         .build()
}
⚠️ **GitHub.com Fallback** ⚠️