NowInAndroid学习系列 Now in Android !AndroidApp开发的最佳实践,让我看看是怎么个事?
Now in Android学习Compose是怎么切换主题
1、Hilt 依赖注入框架
Hilt 是 Google 推荐的依赖注入(DI)框架,基于 Dagger 并针对 Android 进行了简化。 Hilt 感觉用起来还是很不错的。有点像写Java的意思了,Spring也是依赖注入那一套。 那就让我们看看都有哪些注解。原理懂不懂的以后再说,现在我们就用起来先。
1.1 @HiltAndroidApp
应用的入口点,必须添加到自定义 Application
类,触发 Hilt 的代码生成。
@HiltAndroidApp
class NiaApplication : Application(), ImageLoaderFactory {
@Inject
lateinit var imageLoader: dagger.Lazy<ImageLoader>
@Inject
lateinit var profileVerifierLogger: ProfileVerifierLogger
override fun onCreate() {
super.onCreate()
// Initialize Sync; the system responsible for keeping data in the app up to date.
Sync.initialize(context = this)
profileVerifierLogger()
}
override fun newImageLoader(): ImageLoader = imageLoader.get()
}
1.2 @AndroidEntryPoint
- 作用:标记 Android 组件(如 Activity、Fragment、Service 等),使其支持依赖注入。
- 支持组件:Activity、Fragment、View、Service、BroadcastReceiver。
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
1.3 依赖提供注解@Inject
-
作用:
- 构造函数:标记可注入的类,Hilt 自动创建实例。
- 字段/方法:直接注入已绑定的依赖。
//构造方法,注入
class MainActivityViewModel @Inject constructor(
userDataRepository: UserDataRepository,
) : ViewModel()
//字段注入
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
/**
* Lazily inject [JankStats], which is used to track jank throughout the app.
*/
@Inject
lateinit var lazyStats: dagger.Lazy<JankStats>
1.4 @Module 和 @InstallIn
-
@Module:定义提供依赖的模块(类的容器)。
-
@InstallIn:指定模块安装到哪个 Hilt 组件(决定依赖的生命周期)。 @Provides 和 @Binds
-
@Provides:在模块中标记方法,返回具体实例。
@Provides fun provideOkHttpClient(): OkHttpClient { return OkHttpClient.Builder().build() }
-
@Binds:在模块中标记抽象方法,用于接口/抽象类绑定到实现。
@Module @InstallIn(ActivityComponent::class) interface MyModule { @Binds fun bindRepository(impl: RepositoryImpl): Repository }
我觉得这些应该够用了。咱们去看看Now In Android 里具体是怎么依赖注入的
2 、Now In Android database注入实践
2.1 di Dao
@Module
@InstallIn(SingletonComponent::class)
internal object DaosModule {
@Provides
fun providesTopicsDao(
database: NiaDatabase,
): TopicDao = database.topicDao()
@Provides
fun providesNewsResourceDao(
database: NiaDatabase,
): NewsResourceDao = database.newsResourceDao()
@Provides
fun providesTopicFtsDao(
database: NiaDatabase,
): TopicFtsDao = database.topicFtsDao()
@Provides
fun providesNewsResourceFtsDao(
database: NiaDatabase,
): NewsResourceFtsDao = database.newsResourceFtsDao()
@Provides
fun providesRecentSearchQueryDao(
database: NiaDatabase,
): RecentSearchQueryDao = database.recentSearchQueryDao()
}
@Module 作用:标记该对象是一个 Dagger 模块,用于提供依赖项。 说明:模块类会通过 @Provides 或 @Binds 方法定义如何创建和提供依赖实例。 @InstallIn(SingletonComponent::class) 作用:指定该模块安装在 Hilt 的 SingletonComponent 中,表示其中提供的依赖具有应用单例生命周期。 说明:这意味着这些 DAO 实例在整个应用程序生命周期中只会被创建一次,并保持到应用结束。 :标注在方法上,表示该方法可以提供一个依赖项实例。 说明: 每个 @Provides 函数都返回一个需要被注入的对象(如 TopicDao, NewsResourceDao 等)。 参数(如 database: NiaDatabase)由 Hilt 自动解析并注入。 访问权限 internal:仅在当前模块内可见,避免暴露给其他模块。
2.2 di Database
@Module
@InstallIn(SingletonComponent::class)
internal object DatabaseModule {
@Provides
@Singleton
fun providesNiaDatabase(
@ApplicationContext context: Context,
): NiaDatabase = Room.databaseBuilder(
context,
NiaDatabase::class.java,
"nia-database",
).build()
}
@ApplicationContext 是一个注入限定符,用于获取 Application 的 Context。 它通常与 Hilt 的 @Provides 方法一起使用,确保注入正确的 Context 类型。 适用于需要长期持有 Context 的场景,有助于避免内存泄漏。这样实例化的时候,就把Appcliation的Context 注入进去了。
2.3 这里有一个问题,Dao里的 NiaDatabase 是怎么注入进去的?
** Hilt 会查找 NiaDatabase 类型的提供者:** 在你的项目中,NiaDatabase 实例是由另一个模块 DatabaseModule.kt 中的 providesNiaDatabase 提供的。 该方法使用了 @Provides + @Singleton 注解,并被安装在 SingletonComponent 上,因此 Hilt 知道如何获取它。
注入方式:
当你在 DaosModule 的 @Provides 方法中声明 database: NiaDatabase 参数时,Hilt 会自动将之前提供的 NiaDatabase 单例实例注入进来。
DAO 创建过程:
每个 DAO(如 NewsResourceDao)都是通过调用 NiaDatabase 的相应方法创建的,例如 database.newsResourceDao()。
这些 DAO 实例由 Hilt 自动管理生命周期,并确保在整个应用中正确共享。
2.4 在数据仓库里注入使用,直接使用,数据仓库也需要di一下,
@Binds 接口绑定到实现类。
@Binds
internal abstract fun bindsTopicRepository(
topicsRepository: OfflineFirstTopicsRepository,
): TopicsRepository
internal class OfflineFirstTopicsRepository @Inject constructor(
private val topicDao: TopicDao,
private val network: NiaNetworkDataSource,
) : TopicsRepository {
override fun getTopics(): Flow<List<Topic>> =
topicDao.getTopicEntities()
.map { it.map(TopicEntity::asExternalModel) }
override fun getTopic(id: String): Flow<Topic> =
topicDao.getTopicEntity(id).map { it.asExternalModel() }
override suspend fun syncWith(synchronizer: Synchronizer): Boolean =
synchronizer.changeListSync(
versionReader = ChangeListVersions::topicVersion,
changeListFetcher = { currentVersion ->
network.getTopicChangeList(after = currentVersion)
},
versionUpdater = { latestVersion ->
copy(topicVersion = latestVersion)
},
modelDeleter = topicDao::deleteTopics,
modelUpdater = { changedIds ->
val networkTopics = network.getTopics(ids = changedIds)
topicDao.upsertTopics(
entities = networkTopics.map(NetworkTopic::asEntity),
)
},
)
}