android中Hilt的使用流程

依賴注入

  • Dependency Injection碳却,簡(jiǎn)稱DI拟杉;
  • 依賴項(xiàng)注入可以使代碼解耦,便于復(fù)用弄屡,重構(gòu)和測(cè)試

什么是依賴項(xiàng)注入

  • 類通常需要引用其他類题禀,可通過以下三種方式獲取所需的對(duì)象:
  1. 在類中創(chuàng)建所需依賴項(xiàng)的實(shí)例
class CPU () {
    var name: String = ""
    fun run() {
        LjyLogUtil.d("$name run...")
    }
}
class Phone1 {
    val cpu = CPU()
    fun use() {
        cpu.run()
    }
}
  1. 通過父類或其他類獲取
val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkCapabilities = cm.getNetworkCapabilities(cm.activeNetwork)
LjyLogUtil.d("是否有網(wǎng)絡(luò)連接:${networkCapabilities==null}")
  1. 以參數(shù)形式提供舞终,可以在構(gòu)造類時(shí)提供這些依賴項(xiàng)掏觉,或者將這些依賴項(xiàng)傳入需要各個(gè)依賴項(xiàng)的函數(shù);
  • 第三種方式就是依賴項(xiàng)注入

Android中的依賴注入方式

手動(dòng)依賴注入
1. 構(gòu)造函數(shù)注入
//在構(gòu)造類時(shí)提供這些依賴項(xiàng)
class Phone (private val cpu: CPU) {
    fun use() {
        cpu.run()
    }
}
val cpu = CPU()
val phone=Phone(cpu)
phone.use()
2. 字段注入(或 setter 注入)
  • 依賴項(xiàng)將在創(chuàng)建類后實(shí)例化
//將依賴項(xiàng)傳入需要依賴項(xiàng)的函數(shù)
class Phone {
    lateinit var cpu: CPU
    fun use() = cpu.run()
}
val phone = Phone()
val cpu = CPU()
phone.cpu = cpu
phone.use()
  • 上面兩種都是手動(dòng)依賴項(xiàng)注入迹鹅,但是如果依賴項(xiàng)和類過多担孔,手動(dòng)依賴注入就會(huì)產(chǎn)生一些問題
1. 使用越來越繁瑣;
2. 產(chǎn)生大量模板代碼江锨;
3. 必須按順序聲明依賴項(xiàng);
4. 很難重復(fù)使用對(duì)象;
自動(dòng)依賴注入框架
  • 有一些庫(kù)通過自動(dòng)執(zhí)行創(chuàng)建和提供依賴項(xiàng)的過程解決此問題,實(shí)現(xiàn)原理有如下幾種方案:
1\. 通過反射糕篇,在運(yùn)行時(shí)連接依賴項(xiàng);
2\. 通過注解啄育,編譯時(shí)生成連接依賴項(xiàng)的代碼;
3\. kotlin 強(qiáng)大的語法糖和函數(shù)式編程;
1. Dagger:
  • Android領(lǐng)域最廣為熟知的依賴注入框架,可以說大名鼎鼎了
Dagger 1.x版本:Square基于反射實(shí)現(xiàn)的拌消,有兩個(gè)缺點(diǎn)一個(gè)是反射的耗時(shí)挑豌,另一個(gè)是反射是運(yùn)行時(shí)的,編譯期不會(huì)報(bào)錯(cuò)墩崩。而使用難度較高氓英,剛接觸時(shí)有經(jīng)常容易寫錯(cuò),造成開發(fā)效率底鹦筹;
Dagger 2.x版本:Google基于Java注解實(shí)現(xiàn)的铝阐,完美解決了上述問題,
2. Koin
  • 為 Kotlin 開發(fā)者提供的一個(gè)實(shí)用型輕量級(jí)依賴注入框架铐拐,采用純 Kotlin 語言編寫而成徘键,僅使用功能解析,無代理遍蟋、無代碼生成吹害、無反射(通過kotlin 強(qiáng)大的語法糖(例如 Inline、Reified 等等)和函數(shù)式編程實(shí)現(xiàn))虚青;
3. Hilt:
  • 由于Dagger的復(fù)雜度和使用難度較大它呀,Android團(tuán)隊(duì)聯(lián)合Dagger2團(tuán)隊(duì),一起開發(fā)出來的一個(gè)專門面向Android的依賴注入框架Hilt,最明顯的特征就是:1. 簡(jiǎn)單钟些;2. 提供了Android專屬的API烟号;3. Google官方支持,和Jetpack其他組件配合使用政恍;

Hilt

  • Hilt 通過為項(xiàng)目中的每個(gè) Android 類提供容器并自動(dòng)為您管理其生命周期,定義了一種在應(yīng)用中執(zhí)行 DI 的標(biāo)準(zhǔn)方法达传。
  • Hilt 在熱門 DI 庫(kù) Dagger 的基礎(chǔ)上構(gòu)建而成篙耗,因而能夠受益于 Dagger 提供的編譯時(shí)正確性、運(yùn)行時(shí)性能宪赶、可伸縮性和 Android Studio 支持宗弯。

Hilt使用流程

添加依賴項(xiàng)

//1\. 配置Hilt的插件路徑
buildscript {
    ...
    dependencies {
        ...
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
    }
}
//2\. 引入Hilt的插件
plugins {
    ...
    id 'dagger.hilt.android.plugin'
    id 'kotlin-kapt'
}
//3\. 添加Hilt的依賴庫(kù)及Java8
android {
  ...
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
  //對(duì)于 Kotlin 項(xiàng)目,需要添加 kotlinOptions
  kotlinOptions {
      jvmTarget = "1.8"
  }
}
dependencies {
    ...
    implementation "com.google.dagger:hilt-android:2.28-alpha"
    kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}

Hilt 應(yīng)用類

  • 用@HiltAndroidApp注解Application;
  • @HiltAndroidApp注解 會(huì)觸發(fā) Hilt 的代碼生成操作搂妻,生成的代碼包括應(yīng)用的一個(gè)基類蒙保,該基類充當(dāng)應(yīng)用級(jí)依賴項(xiàng)容器;
@HiltAndroidApp
class MyApplication : MultiDexApplication() {
    ...
}

將依賴項(xiàng)注入 Android 類

1. 用@AndroidEntryPoint注釋類;
  • 目前支持6類入口點(diǎn):Application(通過使用 @HiltAndroidApp),Activity欲主,F(xiàn)ragment邓厕,View,Service扁瓢,BroadcastReceiver
  • 使用 @AndroidEntryPoint 注解 Android 類详恼,還必須為依賴于該類的 Android 類添加注釋,例如為注解 fragment 引几,則還必須為該 fragment 依賴的 Activity 添加@AndroidEntryPoint注釋昧互。
2. 使用 @Inject 注釋執(zhí)行字段
  • @AndroidEntryPoint 會(huì)為項(xiàng)目中的每個(gè) Android 類生成一個(gè)單獨(dú)的 Hilt 組件。這些組件可以從它們各自的父類接收依賴項(xiàng), 如需從組件獲取依賴項(xiàng)伟桅,請(qǐng)使用 @Inject 注釋執(zhí)行字段注入敞掘, 注意:Hilt注入的字段是不可以聲明成private的;
3. 構(gòu)造函數(shù)中使用 @Inject 注釋
  • 為了執(zhí)行字段注入楣铁,需要在類的構(gòu)造函數(shù)中使用 @Inject 注釋玖雁,以告知 Hilt 如何提供該類的實(shí)例:
@AndroidEntryPoint
class HiltDemoActivity : AppCompatActivity() {
    @Inject
    lateinit var cpu: CPU
    ...
}

class CPU @Inject constructor() {
    var name: String = ""

    fun run() {
        LjyLogUtil.d("$name run...")
    }
}
帶參數(shù)的依賴注入:
  • 如果構(gòu)造函數(shù)中帶有參數(shù),Hilt要如何進(jìn)行依賴注入呢民褂?
  • 需要構(gòu)造函數(shù)中所依賴的所有其他對(duì)象都支持依賴注入
class CPU @Inject constructor() {
    var name: String = ""
    fun run() {
        LjyLogUtil.d("$name run...")
    }
}

class Phone @Inject constructor(val cpu: CPU) {
    fun use() {
        cpu.run()
    }
}

@AndroidEntryPoint
class HiltActivity : AppCompatActivity() {
    @Inject
    lateinit var phone: Phone

    fun test() {
        phone.cpu.name = "麒麟990"
        phone.use()
    }
}

Hilt Module

  • 有時(shí)一些類型參數(shù)不能通過構(gòu)造函數(shù)注入, 如 接口 或 來自外部庫(kù)的類茄菊,此時(shí)可以使用 Hilt模塊 向Hilt提供綁定信息;
  • Hilt 模塊是一個(gè)帶有 @Module 注釋的類赊堪,并使用 @InstallIn 設(shè)置作用域
使用 @Binds 注入接口實(shí)例
//1\. 接口
interface ICPU {
    fun run()
}
//2\. 實(shí)現(xiàn)類
class KylinCPU @Inject constructor() : ICPU {
    override fun run() {
        LjyLogUtil.d("kylin run...")
    }
}
//3\. 被注入的類面殖,入?yún)⑹墙涌陬愋?class Phone @Inject constructor(val cpu: ICPU) {
    fun use() {
        cpu.run()
    }
}
//4\. 使用@Binds注入接口實(shí)例
@Module
@InstallIn(ActivityComponent::class)
abstract class CPUModel {
    @Binds
    abstract fun bindCPU(cpu: KylinCPU): ICPU
}
//5\. 使用注入的實(shí)例
@AndroidEntryPoint
class HiltActivity : AppCompatActivity() {
    @Inject
    lateinit var phone: Phone

    fun test() {
        phone.cpu.name = "麒麟990"
        phone.use()
    }
}
使用 @Provides 注入實(shí)例
  • 如果某個(gè)類不歸您所有(因?yàn)樗鼇碜酝獠繋?kù),如 Retrofit哭廉、OkHttpClient 或 Room 數(shù)據(jù)庫(kù)等類)脊僚,或者必須使用構(gòu)建器模式創(chuàng)建實(shí)例,也無法通過構(gòu)造函數(shù)注入。
@Module
@InstallIn(ApplicationComponent::class)
class NetworkModel {
    @Provides
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient().newBuilder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(60, TimeUnit.SECONDS)
            .writeTimeout(90, TimeUnit.SECONDS)
            .build()
    }
}
為同一類型提供多個(gè)綁定
  • 比如網(wǎng)絡(luò)請(qǐng)求中可能需要不同配置的OkHttpClient辽幌,或者不同BaseUrl的Retrofit
  • 使用@Qualifier注解實(shí)現(xiàn)
//1\. 接口和實(shí)現(xiàn)類
interface ICPU {
    fun run()
}

class KylinCPU @Inject constructor() : ICPU {
    override fun run() {
        LjyLogUtil.d("kylin run...")
    }
}

class SnapdragonCPU @Inject constructor() : ICPU {
    override fun run() {
        LjyLogUtil.d("snapdragon run...")
    }
}

//2\. 創(chuàng)建多個(gè)類型的注解
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindKylinCPU

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

//@Retention:注解的生命周期
//AnnotationRetention.SOURCE:僅編譯期增淹,不存儲(chǔ)在二進(jìn)制輸出中
//AnnotationRetention.BINARY:存儲(chǔ)在二進(jìn)制輸出中,但對(duì)反射不可見
//AnnotationRetention.RUNTIME:存儲(chǔ)在二進(jìn)制輸出中乌企,對(duì)反射可見

//3\. 在Hilt模塊中使用注解
@Module
@InstallIn(ActivityComponent::class)
abstract class CPUModel {
    @BindKylinCPU
    @Binds
    abstract fun bindKylinCPU(cpu: KylinCPU): ICPU

    @BindSnapdragonCPU
    @Binds
    abstract fun bindSnapdragonCPU(cpu: SnapdragonCPU): ICPU
}

//4\. 使用依賴注入獲取實(shí)例虑润,可以用在字段注解,也可以用在構(gòu)造函數(shù)或者方法入?yún)⒅?class Phone5 @Inject constructor(@BindSnapdragonCPU private val cpu: ICPU) {
    @BindKylinCPU
    @Inject
    lateinit var cpu1: ICPU

    @BindSnapdragonCPU
    @Inject
    lateinit var cpu2: ICPU

    fun use() {
        cpu.run()
        cpu1.run()
        cpu2.run()
    }

    fun use(@BindKylinCPU cpu: ICPU) {
        cpu.run()
    }
}

組件默認(rèn)綁定

  • 由于可能需要來自 Application 或 Activity 的 Context 類加酵,因此 Hilt 提供了 @ApplicationContext 和 @ActivityContext 限定符拳喻。
  • 每個(gè) Hilt 組件都附帶一組默認(rèn)綁定,Hilt 可以將其作為依賴項(xiàng)注入您自己的自定義綁定
class Test1 @Inject constructor(@ApplicationContext private val context: Context)

class Test2 @Inject constructor(@ActivityContext private val context: Context)
  • 對(duì)于Application和Activity這兩個(gè)類型猪腕,Hilt也是給它們預(yù)置好了注入功能(必須是這兩個(gè)冗澈,即使子類也不可以)
class Test3 @Inject constructor(val application: Application)

class Test4 @Inject constructor(val activity: Activity)

Hilt內(nèi)置組件類型

  • 上面使用Hilt Module時(shí),有用到@InstallIn(), 意思是把這個(gè)模塊安裝到哪個(gè)組件中
Hilt內(nèi)置了7種組件可選:
  1. ApplicationComponent:對(duì)應(yīng)Application陋葡,依賴注入實(shí)例可以在全項(xiàng)目中使用
  2. ActivityRetainedComponent:對(duì)應(yīng)ViewModel(在配置更改后仍然存在亚亲,因此它在第一次調(diào)用 Activity#onCreate() 時(shí)創(chuàng)建,在最后一次調(diào)用 Activity#onDestroy() 時(shí)銷毀)
  3. ActivityComponent:對(duì)應(yīng)Activity腐缤,Activity中包含的Fragment和View也可以使用捌归;
  4. FragmentComponent:對(duì)應(yīng)Fragment
  5. ViewComponent:對(duì)應(yīng)View
  6. ViewWithFragmentComponent:對(duì)應(yīng)帶有 @WithFragmentBindings 注釋的 View
  7. ServiceComponent:對(duì)應(yīng)Service
  • Hilt 沒有為 broadcast receivers 提供組件,因?yàn)?Hilt 直接從 ApplicationComponent 注入 broadcast receivers柴梆;

組件作用域

  • Hilt默認(rèn)會(huì)為每次的依賴注入行為都創(chuàng)建不同的實(shí)例陨溅。
Hilt內(nèi)置7種組件作用域注解
  1. @Singleton:對(duì)應(yīng)組件ApplicationComponent,整個(gè)項(xiàng)目共享同一個(gè)實(shí)例
  2. @ActivityRetainedScope:對(duì)應(yīng)組件ActivityRetainedComponent
  3. @ActivityScoped:對(duì)應(yīng)組件ActivityComponent绍在,在同一個(gè)Activity(包括其包含的Fragment和View中)內(nèi)部將會(huì)共享同一個(gè)實(shí)例
  4. @FragmentScoped:對(duì)應(yīng)組件FragmentComponent
  5. @ViewScoped:對(duì)應(yīng)組件ViewComponent和ViewWithFragmentComponent门扇;
  6. @ServiceScopedService:對(duì)應(yīng)ServiceComponent
  • 比如我們經(jīng)常會(huì)需要一個(gè)全局的OkhttpClient或者Retrofit,就可以如下實(shí)現(xiàn)
interface ApiService {
    @GET("search/repositories?sort=stars&q=Android")
    suspend fun searRepos(@Query("page") page: Int, @Query("per_page") perPage: Int): RepoResponse
}

@Module
@InstallIn(ApplicationComponent::class)
class NetworkModel {

    companion object {
        private const val BASE_URL = "https://api.github.com/"
    }

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

    //組件作用域:Hilt默認(rèn)會(huì)為每次的依賴注入行為都創(chuàng)建不同的實(shí)例。
    @Singleton
    @Provides
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create())
            .baseUrl(BASE_URL)
            .client(okHttpClient)
            .build()
    }

    @Singleton
    @Provides
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient().newBuilder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(60, TimeUnit.SECONDS)
            .writeTimeout(90, TimeUnit.SECONDS)
            .build()
    }
}
  • 或是使用Room操作本地?cái)?shù)據(jù)庫(kù)
@Database(entities = [RepoEntity::class], version = Constants.DB_VERSION)
abstract class AppDatabase : RoomDatabase() {
    abstract fun repoDao(): RepoDao
}

@Module
@InstallIn(ApplicationComponent::class)
object RoomModule {
    @Provides
    @Singleton
    fun provideAppDatabase(application: Application): AppDatabase {
        return Room
            .databaseBuilder(
                application.applicationContext,
                AppDatabase::class.java,
                Constants.DB_NAME
            )
            .allowMainThreadQueries() //允許在主線程中查詢
            .build()
    }

    @Provides
    @Singleton
    fun provideRepoDao(appDatabase: AppDatabase):RepoDao{
        return appDatabase.repoDao()
    }
}

ViewModel的依賴注入

  • 通過上面的學(xué)習(xí)偿渡,那我們?nèi)绻赩iewModel中創(chuàng)建Repository要如何實(shí)現(xiàn)呢臼寄,可以如下:
//1\. 倉(cāng)庫(kù)層
class Repository @Inject constructor(){
    @Inject
    lateinit var apiService: ApiService
    suspend fun getData(): RepoResponse {
        return apiService.searRepos(1, 5)
    }
}
//2\. ViewModel層
@ActivityRetainedScoped
class MyViewModel @Inject constructor(private val repository: Repository): ViewModel() {
    var result: MutableLiveData<String> = MutableLiveData()
    fun doWork() {
        viewModelScope.launch {
            runCatching {
                withContext(Dispatchers.IO){
                    repository.getData()
                }
            }.onSuccess {
                result.value="RepoResponse=${gson().toJson(it)}"
            }.onFailure {
                result.value=it.message
            }
        }
    }
}
//3\. Activity層
@AndroidEntryPoint
class HiltMvvmActivity : AppCompatActivity() {

    @Inject
    lateinit var viewModel: MyViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_hilt_mvvm)
        viewModel.result.observe(this, Observer {
            LjyLogUtil.d("result:$it")
        })
        lifecycleScope
        viewModel.doWork()
    }
}
ViewModel 和 @ViewModelInject 注解
  • 這種改變了獲取ViewModel實(shí)例的常規(guī)方式, 為此Hilt專門為其提供了一種獨(dú)立的依賴注入方式: @ViewModelInject
//1\. 添加兩個(gè)額外的依賴
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha02'
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha02'
//2\. 修改MyViewModel: 去掉@ActivityRetainedScoped注解,把@Inject改為@ViewModelInject
class MyViewModel @ViewModelInject constructor(private val repository: Repository): ViewModel() {
    ...
}
//3\. activity中viewModel獲取改為常規(guī)寫法
@AndroidEntryPoint
class HiltMvvmActivity : AppCompatActivity() {
//    @Inject
//    lateinit var viewModel: MyViewModel
    val viewModel: MyViewModel by viewModels()
//  或  val viewModel: MyViewModel by lazy { ViewModelProvider(this).get(MyViewModel::class.java) }
    ...
}
SavedStateHandle 和 @assist注解
  • Activity/Fragment被銷毀一般有三種情況:
    1. 界面關(guān)閉或退出應(yīng)用
    2. Activity 配置 (configuration) 被改變溜宽,如旋轉(zhuǎn)屏幕時(shí)吉拳;
    3. 在后臺(tái)時(shí)因運(yùn)行內(nèi)存不足被系統(tǒng)回收;
  • ViewModel 會(huì)處理2的情況适揉,而3的情況就需要使用onSaveInstanceState()保存數(shù)據(jù)留攒,重建時(shí)用SavedStateHandle恢復(fù)數(shù)據(jù),就要用@assist 注解添加 SavedStateHandle 依賴項(xiàng)
class MyViewModel @ViewModelInject constructor(
    private val repository: Repository,
    //SavedStateHandle 用于進(jìn)程被終止時(shí)嫉嘀,保存和恢復(fù)數(shù)據(jù)
    @Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    var result: MutableLiveData<String> = MutableLiveData()
    private val userId: MutableLiveData<String> = savedStateHandle.getLiveData("userId")

    fun doWork() {
        viewModelScope.launch {
            runCatching {
                withContext(Dispatchers.IO) {
                    repository.getData(userId)
                }
            }.onSuccess {
                result.value = "RepoResponse=${Gson().toJson(it)}"
            }.onFailure {
                result.value = it.message
            }
        }
    }
}

在 Hilt 不支持的類中注入依賴項(xiàng)

  • 可以使用 @EntryPoint 注釋創(chuàng)建入口點(diǎn), 調(diào)用EntryPointAccessors的靜態(tài)方法來獲得自定義入口點(diǎn)的實(shí)例
  • EntryPointAccessors提供了四個(gè)靜態(tài)方法:fromActivity炼邀、fromApplication、fromFragment剪侮、fromView拭宁,根據(jù)自定義入口的MyEntryPoint的注解@InstallIn所指定的范圍選擇對(duì)應(yīng)的獲取方法;
@EntryPoint
@InstallIn(ApplicationComponent::class)
interface MyEntryPoint{
    fun getRetrofit():Retrofit
}
在ContentProvider中使用
  • Hilt支持的入口點(diǎn)中少了一個(gè)關(guān)鍵的Android組件:ContentProvider, 主要原因就是ContentProvider.onCreate() 在Application的onCreate() 之前執(zhí)行,因此很多人會(huì)利用這個(gè)特性去進(jìn)行提前初始化, 詳見Android Jetpack系列--5. App Startup使用詳解, 而Hilt的工作原理是從Application.onCreate()中開始的杰标,即ContentProvider.onCreate()執(zhí)行之前兵怯,Hilt的所有功能都還無法正常工作;
class MyContentProvider : ContentProvider() {
    override fun onCreate(): Boolean {
        context?.let {
            val appContext=it.applicationContext
            //調(diào)用EntryPointAccessors.fromApplication()函數(shù)來獲得自定義入口點(diǎn)的實(shí)例
            val entryPoint=EntryPointAccessors.fromApplication(appContext,MyEntryPoint::class.java)
            //再調(diào)用入口點(diǎn)中定義的getRetrofit()函數(shù)就能得到Retrofit的實(shí)例
            val retrofit=entryPoint.getRetrofit()
            LjyLogUtil.d("retrofit:$retrofit")
        }
        return true
    }
    ...
}
在 App Startup 中使用
  • App Startup 會(huì)默認(rèn)提供一個(gè) InitializationProvider腔剂,InitializationProvider 繼承 ContentProvider媒区;
class LjyInitializer : Initializer<Unit> {
    override fun create(context: Context) {
        //調(diào)用EntryPointAccessors.fromApplication()函數(shù)來獲得自定義入口點(diǎn)的實(shí)例
        val entryPoint= EntryPointAccessors.fromApplication(context, MyEntryPoint::class.java)
        //再調(diào)用入口點(diǎn)中定義的getRetrofit()函數(shù)就能得到Retrofit的實(shí)例
        val retrofit=entryPoint.getRetrofit()
        LjyLogUtil.d("retrofit:$retrofit")
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        return emptyList()
    }
}

報(bào)錯(cuò)解決

  • Expected @HiltAndroidApp to have a value. Did you forget to apply the Gradle Plugin?
android {
    ...
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = ["room.schemaLocation":
                             "$projectDir/schemas".toString()]
            }
        }
    }
}
//If so, try changing from "arguments =" to "arguments +=", as just using equals overwrites anything set previously.
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市掸犬,隨后出現(xiàn)的幾起案子驻仅,更是在濱河造成了極大的恐慌,老刑警劉巖登渣,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異毡泻,居然都是意外死亡胜茧,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門仇味,熙熙樓的掌柜王于貴愁眉苦臉地迎上來呻顽,“玉大人,你說我怎么就攤上這事丹墨±缺椋” “怎么了?”我有些...
    開封第一講書人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵贩挣,是天一觀的道長(zhǎng)喉前。 經(jīng)常有香客問我,道長(zhǎng)王财,這世上最難降的妖魔是什么卵迂? 我笑而不...
    開封第一講書人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮绒净,結(jié)果婚禮上见咒,老公的妹妹穿的比我還像新娘。我一直安慰自己挂疆,他們只是感情好改览,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著缤言,像睡著了一般宝当。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上墨闲,一...
    開封第一講書人閱讀 51,698評(píng)論 1 305
  • 那天今妄,我揣著相機(jī)與錄音,去河邊找鬼。 笑死盾鳞,一個(gè)胖子當(dāng)著我的面吹牛犬性,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播腾仅,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼乒裆,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了推励?” 一聲冷哼從身側(cè)響起鹤耍,我...
    開封第一講書人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎验辞,沒想到半個(gè)月后稿黄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡跌造,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年杆怕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片壳贪。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡陵珍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出违施,到底是詐尸還是另有隱情互纯,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布磕蒲,位于F島的核電站留潦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏亿卤。R本人自食惡果不足惜愤兵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望排吴。 院中可真熱鬧秆乳,春花似錦、人聲如沸钻哩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽街氢。三九已至扯键,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間珊肃,已是汗流浹背荣刑。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工馅笙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人厉亏。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓董习,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親爱只。 傳聞我的和親對(duì)象是個(gè)殘疾皇子皿淋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容