Appearance
05 DI 中的作用域管理 🔖
理解和管理依赖的生命周期,避免内存泄漏
一、作用域基础概念
1.1 什么是作用域?
作用域(Scope)定义了依赖实例的生命周期和可见范围。
作用域的核心问题:
- 依赖何时创建?
- 依赖何时销毁?
- 哪些地方可以访问这个依赖?
- 是否共享同一个实例?1.2 为什么需要作用域?
kotlin
// ==== 没有作用域的问题 ====
// 问题 1:不必要的实例创建
class Repository {
private val api = ApiService() // 每次都创建新的
}
// 问题 2:内存泄漏
object Singleton {
var activity: Activity? = null // ❌ 持有 Activity 引用
}
// 问题 3:状态不一致
class ViewModel {
private val repository = UserRepository() // 多个 ViewModel 实例不同步
}
// ==== 有作用域的解决方案 ====
// 单例:整个应用一个实例
@Singleton
class ApiService { }
// Activity 作用域:与 Activity 同生命周期
@ActivityScoped
class ActivityRepository { }
// ViewModel 作用域:与 ViewModel 同生命周期
@ViewModelScoped
class ViewModelData { }1.3 作用域的类型
┌─────────────────────────────────────┐
│ No Scope (无作用域) │
│ 每次请求创建新实例 │
├─────────────────────────────────────┤
│ Singleton (单例) │
│ 应用生命周期,一个实例 │
├─────────────────────────────────────┤
│ Custom Scope (自定义作用域) │
│ 用户定义的生命周期 │
├─────────────────────────────────────┤
│ Android Scopes │
│ - ActivityScoped │
│ - FragmentScoped │
│ - ViewModelScoped │
│ - ServiceScoped │
│ - ViewScoped │
└─────────────────────────────────────┘二、Singleton vs Scoped vs Factory
2.1 Singleton(单例)
整个应用生命周期内只有一个实例。
kotlin
// ==== Hilt/Dagger 中的 Singleton ====
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideApiService(): ApiService {
return ApiService() // 整个应用只有一个实例
}
@Provides
@Singleton
fun provideRepository(api: ApiService): UserRepository {
return UserRepository(api) // 也是单例
}
}
// ==== Koin 中的 Singleton ====
val appModule = module {
single { ApiService() } // Koin 的 single 就是单例
single { UserRepository(get()) }
}
// ==== 使用场景 ====
/*
适合单例的依赖:
- API 客户端(Retrofit、OkHttp)
- 数据库实例(Room)
- 共享偏好设置(SharedPreferences)
- 全局配置
- 日志器
- 分析追踪器
不适合单例的依赖:
- 持有 Context 的类(除非是 Application Context)
- 有状态的类(用户会话、临时数据)
- 大型对象(可能导致内存压力)
*/Singleton 的注意事项:
kotlin
// ❌ 错误:在单例中持有 Activity
@Singleton
class DangerousClass @Inject constructor(
private val activity: Activity // ❌ 内存泄漏!
)
// ✅ 正确:使用 Application Context
@Singleton
class SafeClass @Inject constructor(
@ApplicationContext private val context: Context
)
// ✅ 正确:使用 Provider 延迟获取
@Singleton
class LazyClass @Inject constructor(
private val activityProvider: Provider<Activity>
) {
fun doSomething() {
val activity = activityProvider.get()
// 使用时才获取,用完释放
}
}2.2 Scoped(作用域)
在特定作用域内共享同一个实例。
kotlin
// ==== Hilt 中的作用域 ====
@Module
@InstallIn(ActivityComponent::class)
abstract class ActivityModule {
@Provides
@ActivityScoped
fun provideAdapter(context: Context): RecyclerView.Adapter<*> {
return MyAdapter(context) // 每个 Activity 一个实例
}
@Provides
@FragmentScoped
fun provideFragmentData(): FragmentData {
return FragmentData() // 每个 Fragment 一个实例
}
}
// ==== Koin 中的作用域 ====
val appModule = module {
scope(named("activity")) {
scoped { MyAdapter(get()) } // 每个作用域一个实例
}
scope(named("session")) {
scoped { UserManager() } // 每个会话一个实例
}
}
// ==== 使用场景 ====
/*
适合 Scoped 的依赖:
- Activity 级别的 UI 组件
- Fragment 级别的数据
- 用户会话数据
- 临时缓存
- 与生命周期绑定的对象
*/2.3 Factory(工厂)
每次请求都创建新实例。
kotlin
// ==== Hilt/Dagger 中的 Factory ====
@Module
@InstallIn(SingletonComponent::class)
object FactoryModule {
@Provides
fun provideLogger(): Logger {
return Logger() // 每次都是新实例
}
@Provides
fun provideHandler(): RequestHandler {
return RequestHandler() // 每次都是新实例
}
}
// ==== Koin 中的 Factory ====
val appModule = module {
factory { Logger() } // 每次 inject 都创建新实例
factory { RequestHandler() }
}
// ==== 使用场景 ====
/*
适合 Factory 的依赖:
- 无状态的工具类
- 轻量级对象
- 需要多个实例的场景
- 有状态但不应共享的对象
- 临时处理器
*/
// ==== 实际例子 ====
// 例子 1:日志器(无状态)
val module = module {
factory { Logger() } // 每个类都可以有自己的日志器
}
// 例子 2:请求处理器(有状态)
class RequestHandler {
private val requests = mutableListOf<Request>()
fun addRequest(request: Request) {
requests.add(request)
}
fun process() {
// 处理请求
requests.clear() // 处理完清空
}
}
val module = module {
factory { RequestHandler() } // 每个处理器独立
}2.4 三种模式对比
kotlin
// ==== 对比表 ====
| 特性 | Singleton | Scoped | Factory |
|------|-----------|--------|---------|
| **实例数量** | 1(全局) | N(作用域内 1) | 无限(每次新实例) |
| **生命周期** | 应用生命周期 | 作用域生命周期 | 调用者管理 |
| **内存占用** | 低 | 中 | 高 |
| **创建开销** | 一次 | 每次作用域一次 | 每次请求 |
| **适用场景** | 共享服务 | 生命周期绑定 | 无状态/临时 |
// ==== 代码对比 ====
@Module
@InstallIn(SingletonComponent::class)
object ComparisonModule {
// Singleton - 整个应用一个实例
@Provides
@Singleton
fun provideSingleton(): Dependency {
return Dependency() // 只创建一次
}
// Scoped - 每个作用域一个实例
@Provides
@ActivityScoped
fun provideScoped(): Dependency {
return Dependency() // 每个 Activity 创建一次
}
// Factory - 每次请求新实例
@Provides
fun provideFactory(): Dependency {
return Dependency() // 每次都创建
}
}
// 使用
class Example @Inject constructor(
private val singleton: Dependency, // 共享
private val scoped: Dependency, // 作用域内共享
private val factory: Dependency // 独立
) {
fun test() {
// singleton == singleton (总是相同)
// scoped == scoped (同一作用域内相同)
// factory != factory (总是不同)
}
}三、Hilt 作用域详解
3.1 @Singleton
应用级别的单例作用域。
kotlin
@Module
@InstallIn(SingletonComponent::class)
object SingletonModule {
@Provides
@Singleton
fun provideApiService(): ApiService {
return ApiService()
}
@Provides
@Singleton
fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"app.db"
).build()
}
@Provides
@Singleton
fun provideRepository(
api: ApiService,
database: AppDatabase
): UserRepository {
return UserRepository(api, database.userDao())
}
}
// 生命周期:
// 创建:Application 创建时
// 销毁:Application 销毁时3.2 @ActivityRetainedScoped
在配置变更时保留的作用域。
kotlin
@Module
@InstallIn(ActivityRetainedComponent::class)
object ActivityRetainedModule {
@Provides
@ActivityRetainedScoped
fun provideExpensiveData(): ExpensiveData {
return ExpensiveData() // 屏幕旋转时保留
}
}
// ViewModel 自动使用此作用域
@HiltViewModel
class MainViewModel @Inject constructor(
private val repository: Repository
) : ViewModel() {
// ViewModel 自动在 ActivityRetainedComponent 中
}
// 生命周期:
// 创建:Activity 首次创建
// 销毁:Activity 真正销毁(不是配置变更)
// 保留:屏幕旋转、语言切换等3.3 @ViewModelScoped
ViewModel 级别的作用域。
kotlin
@Module
@InstallIn(ViewModelComponent::class)
object ViewModelModule {
@Provides
@ViewModelScoped
fun provideRepository(): Repository {
return Repository() // 与 ViewModel 同生命周期
}
@Provides
@ViewModelScoped
fun provideAnalytics(): Analytics {
return Analytics()
}
}
@HiltViewModel
class MainViewModel @Inject constructor(
private val repository: Repository, // ViewModelScoped
private val analytics: Analytics // ViewModelScoped
) : ViewModel() {
// 所有依赖与 ViewModel 同生命周期
}
// 生命周期:
// 创建:ViewModel 创建时
// 销毁:ViewModel 销毁时(onCleared)3.4 @ActivityScoped
Activity 级别的作用域。
kotlin
@Module
@InstallIn(ActivityComponent::class)
abstract class ActivityModule {
@Provides
@ActivityScoped
fun provideAdapter(context: Context): RecyclerView.Adapter<*> {
return MyAdapter(context)
}
@Provides
@ActivityScoped
fun provideNavigator(activity: Activity): Navigator {
return Navigator(activity)
}
@Provides
@ActivityScoped
fun provideLoader(): DataLoader {
return DataLoader()
}
}
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject lateinit var adapter: RecyclerView.Adapter<*>
@Inject lateinit var navigator: Navigator
@Inject lateinit var loader: DataLoader
// 所有依赖与 Activity 同生命周期
}
// 生命周期:
// 创建:Activity 创建时
// 销毁:Activity 销毁时(onDestroy)3.5 @FragmentScoped
Fragment 级别的作用域。
kotlin
@Module
@InstallIn(FragmentComponent::class)
object FragmentModule {
@Provides
@FragmentScoped
fun provideFragmentData(): FragmentData {
return FragmentData()
}
@Provides
@FragmentScoped
fun provideAdapter(): FragmentAdapter {
return FragmentAdapter()
}
}
@AndroidEntryPoint
class MainFragment : Fragment() {
@Inject lateinit var data: FragmentData
@Inject lateinit var adapter: FragmentAdapter
// 所有依赖与 Fragment 同生命周期
}
// 生命周期:
// 创建:Fragment 附加时(onAttach)
// 销毁:Fragment 分离时(onDetach)3.6 @ViewScoped
自定义 View 级别的作用域。
kotlin
@Module
@InstallIn(ViewComponent::class)
object ViewModule {
@Provides
@ViewScoped
fun provideViewTracker(): ViewTracker {
return ViewTracker()
}
@Provides
@ViewScoped
fun provideAnimator(): ViewAnimator {
return ViewAnimator()
}
}
@AndroidEntryPoint
class CustomView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : FrameLayout(context, attrs) {
@Inject lateinit var tracker: ViewTracker
@Inject lateinit var animator: ViewAnimator
init {
tracker.trackView(this)
}
}
// 生命周期:
// 创建:View 附加到窗口时
// 销毁:View 从窗口移除时3.7 @ServiceScoped
Service 级别的作用域。
kotlin
@Module
@InstallIn(ServiceComponent::class)
object ServiceModule {
@Provides
@ServiceScoped
fun provideWorker(): BackgroundWorker {
return BackgroundWorker()
}
@Provides
@ServiceScoped
fun provideNotificationManager(): NotificationManager {
return NotificationManager()
}
}
@AndroidEntryPoint
class MyService : Service() {
@Inject lateinit var worker: BackgroundWorker
@Inject lateinit var notificationManager: NotificationManager
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
worker.doWork()
return START_STICKY
}
}
// 生命周期:
// 创建:Service 创建时(onCreate)
// 销毁:Service 销毁时(onDestroy)3.8 Hilt 作用域层次图
┌─────────────────────────────────────────────────────┐
│ SingletonComponent │
│ @Singleton │
│ 生命周期:Application.onCreate() → Application 销毁 │
├─────────────────────────────────────────────────────┤
│ ActivityRetainedComponent │
│ @ActivityRetainedScoped │
│ 生命周期:Activity 首次创建 → Activity 真正销毁 │
│ 保留:配置变更(屏幕旋转、语言切换) │
├─────────────────────────────────────────────────────┤
│ ViewModelComponent │
│ @ViewModelScoped │
│ 生命周期:ViewModel 创建 → ViewModel.onCleared() │
├─────────────────────────────────────────────────────┤
│ ActivityComponent │
│ @ActivityScoped │
│ 生命周期:Activity.onCreate() → Activity.onDestroy()│
├─────────────────────────────────────────────────────┤
│ FragmentComponent │
│ @FragmentScoped │
│ 生命周期:Fragment.onAttach() → Fragment.onDetach()│
├─────────────────────────────────────────────────────┤
│ ViewComponent │
│ @ViewScoped │
│ 生命周期:View 附加到窗口 → View 从窗口移除 │
├─────────────────────────────────────────────────────┤
│ ServiceComponent │
│ @ServiceScoped │
│ 生命周期:Service.onCreate() → Service.onDestroy() │
├─────────────────────────────────────────────────────┤
│ BroadcastReceiverComponent │
│ @BroadcastReceiverScoped │
│ 生命周期:BroadcastReceiver.onReceive() 执行期间 │
└─────────────────────────────────────────────────────┘
依赖流向:子组件可以访问父组件的依赖,反之不行四、自定义 Scope 注解
4.1 定义自定义 Scope
kotlin
// ==== 定义 Scope 注解 ====
import javax.inject.Scope
import kotlin.annotation.AnnotationRetention.RUNTIME
// 用户会话作用域
@Scope
@Retention(RUNTIME)
annotation class PerUserSession
// 登录期间作用域
@Scope
@Retention(RUNTIME)
annotation class PerLoginSession
// 购物车作用域
@Scope
@Retention(RUNTIME)
annotation class PerCart
// 工作单元作用域
@Scope
@Retention(RUNTIME)
annotation class PerUnitOfWork4.2 使用自定义 Scope(Hilt)
kotlin
// ==== 定义 Component ====
@PerUserSession
@Scope
@InstallIn(SingletonComponent::class)
object UserSessionScope
// Hilt 中自定义作用域需要配合 Component
// 更推荐使用 Component Dependencies
// ==== 使用 Component Dependencies ====
@Scope
@Retention(RUNTIME)
annotation class PerUserSession
@Component(
modules = [UserSessionModule::class],
dependencies = [AppComponent::class]
)
@PerUserSession
interface UserSessionComponent {
fun getUserManager(): UserManager
fun getSessionManager(): SessionManager
@Component.Factory
interface Factory {
fun create(@Component.Dependency app: AppComponent): UserSessionComponent
}
}
@Module
abstract class UserSessionModule {
@Binds
@PerUserSession
abstract fun bindUserManager(impl: UserManagerImpl): UserManager
@Binds
@PerUserSession
abstract fun bindSessionManager(impl: SessionManagerImpl): SessionManager
}4.3 使用自定义 Scope(Koin)
kotlin
// ==== Koin 中定义作用域更简单 ====
val appModule = module {
// 用户会话作用域
scope(named("user_session")) {
scoped { UserManager() }
scoped { SessionManager() }
scoped { UserProfile(get()) }
}
// 购物车作用域
scope(named("cart")) {
scoped { CartManager() }
scoped { CartRepository(get()) }
}
}
// ==== 管理作用域生命周期 ====
class SessionManager {
private var sessionScope: Scope? = null
fun login(username: String) {
// 创建会话作用域
sessionScope = KoinApplication.init().koin.createScope(
scopeId = "session_$username",
scopeName = named("user_session")
)
}
fun logout() {
// 销毁会话作用域
sessionScope?.close()
sessionScope = null
}
fun getUserProfile(): UserProfile {
return sessionScope?.get()
?: throw IllegalStateException("No active session")
}
}4.4 实际应用场景
kotlin
// ==== 场景 1:多租户应用 ====
@Scope
@Retention(RUNTIME)
annotation class PerTenant
@Component(
modules = [TenantModule::class],
dependencies = [AppComponent::class]
)
@PerTenant
interface TenantComponent {
fun getTenantConfig(): TenantConfig
fun getTenantRepository(): TenantRepository
}
class TenantManager {
private val tenantComponents = mutableMapOf<String, TenantComponent>()
fun switchTenant(tenantId: String) {
val component = tenantComponents.getOrPut(tenantId) {
DaggerTenantComponent.factory().create(appComponent)
}
// 使用这个租户的依赖
}
}
// ==== 场景 2:编辑器会话 ====
val editorModule = module {
scope(named("editor_session")) {
scoped { DocumentManager() }
scoped { UndoRedoManager() }
scoped { SelectionManager() }
}
}
class EditorActivity : AppCompatActivity() {
private var editorScope: Scope? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
editorScope = koin.createScope(
scopeId = "editor_$this",
scopeName = named("editor_session")
)
val documentManager = editorScope?.get<DocumentManager>()
}
override fun onDestroy() {
super.onDestroy()
editorScope?.close()
}
}
// ==== 场景 3:下载任务 ====
@Scope
@Retention(RUNTIME)
annotation class PerDownloadTask
class DownloadManager {
private val downloadScopes = mutableMapOf<String, DownloadComponent>()
fun startDownload(url: String): String {
val taskId = generateTaskId()
val component = DaggerDownloadComponent.factory()
.create(appComponent, taskId)
downloadScopes[taskId] = component
val downloader = component.getDownloader()
downloader.download(url)
return taskId
}
fun cancelDownload(taskId: String) {
downloadScopes.remove(taskId)
// Component 销毁,依赖释放
}
}五、作用域与生命周期绑定
5.1 Android 生命周期绑定
kotlin
// ==== Activity 生命周期 ====
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject lateinit var activityScopedDep: ActivityScopedDep
// 在 onCreate 之后可用
// 在 onDestroy 时释放
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// activityScopedDep 现在可用
}
override fun onDestroy() {
super.onDestroy()
// activityScopedDep 将被释放
}
}
// ==== Fragment 生命周期 ====
@AndroidEntryPoint
class MainFragment : Fragment() {
@Inject lateinit var fragmentScopedDep: FragmentScopedDep
// 在 onAttach 之后可用
// 在 onDetach 时释放
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// fragmentScopedDep 现在可用
}
override fun onDestroyView() {
super.onDestroyView()
// View 相关依赖释放
}
override fun onDetach() {
super.onDetach()
// fragmentScopedDep 将被释放
}
}
// ==== ViewModel 生命周期 ====
@HiltViewModel
class MainViewModel @Inject constructor(
@ViewModelScoped private val data: ViewModelData
) : ViewModel() {
// data 与 ViewModel 同生命周期
override fun onCleared() {
super.onCleared()
// data 将被释放
}
}5.2 生命周期感知的作用域
kotlin
// ==== 使用 LifecycleObserver ====
class ScopedManager @Inject constructor() : LifecycleObserver {
private val scopes = mutableMapOf<String, Scope>()
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onCreate(owner: LifecycleOwner) {
// 创建作用域
val scope = KoinApplication.init().koin.createScope(
scopeId = "activity_${owner}",
scopeName = named("activity")
)
scopes[owner.javaClass.simpleName] = scope
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy(owner: LifecycleOwner) {
// 销毁作用域
scopes.remove(owner.javaClass.simpleName)?.close()
}
}
// ==== 在 Activity 中使用 ====
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject lateinit var scopedManager: ScopedManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycle.addObserver(scopedManager)
}
}5.3 作用域与配置变更
kotlin
// ==== 配置变更时的作用域行为 ====
/*
@Singleton:
- 配置变更:保留 ✅
- Activity 销毁:保留 ✅
@ActivityRetainedScoped:
- 配置变更:保留 ✅
- Activity 销毁:销毁 ❌
@ActivityScoped:
- 配置变更:销毁 ❌
- Activity 销毁:销毁 ❌
@ViewModelScoped:
- 配置变更:保留 ✅(ViewModel 保留)
- Activity 销毁:销毁 ❌
*/
// ==== 实际例子 ====
@HiltViewModel
class MainViewModel @Inject constructor(
private val retainedData: RetainedData, // ActivityRetainedScoped
private val activityData: ActivityData // ActivityScoped(错误!)
) : ViewModel() {
// ⚠️ activityData 在配置变更后会变成新实例
// ✅ retainedData 在配置变更后保持相同实例
}
// ✅ 正确做法
@HiltViewModel
class MainViewModel @Inject constructor(
private val retainedData: RetainedData, // ActivityRetainedScoped
private val viewModelData: ViewModelData // ViewModelScoped
) : ViewModel() {
// 所有依赖都在配置变更后保留
}六、内存管理
6.1 常见内存泄漏场景
kotlin
// ==== 泄漏 1:单例持有 Activity ====
// ❌ 错误
@Singleton
class LeakClass @Inject constructor(
private val activity: Activity // ❌ 泄漏!
)
// ✅ 正确
@Singleton
class SafeClass @Inject constructor(
@ApplicationContext private val context: Context
)
// ==== 泄漏 2:作用域不匹配 ====
// ❌ 错误
@Module
@InstallIn(SingletonComponent::class)
object WrongModule {
@Provides
@Singleton
fun provideLeak(@ActivityContext context: Context): LeakClass {
return LeakClass(context) // ❌ Activity Context 用于单例
}
}
// ✅ 正确
@Module
@InstallIn(SingletonComponent::class)
object CorrectModule {
@Provides
@Singleton
fun provideSafe(@ApplicationContext context: Context): SafeClass {
return SafeClass(context) // ✅ Application Context
}
}
// ==== 泄漏 3:忘记关闭作用域 ====
// ❌ 错误
class Manager {
private var scope: Scope? = null
fun start() {
scope = koin.createScope("id", named("session"))
// 忘记关闭
}
}
// ✅ 正确
class Manager {
private var scope: Scope? = null
fun start() {
scope = koin.createScope("id", named("session"))
}
fun stop() {
scope?.close()
scope = null
}
}6.2 使用 WeakReference
kotlin
// ==== 弱引用避免泄漏 ====
class SafeHolder @Singleton constructor() {
private val references = mutableListOf<WeakReference<Any>>()
fun hold(obj: Any) {
references.add(WeakReference(obj))
}
fun cleanup() {
references.removeAll { it.get() == null }
}
}
// ==== 弱引用 Context ====
class ContextHolder @Inject constructor() {
private var contextRef: WeakReference<Context>? = null
fun setContext(context: Context) {
contextRef = WeakReference(context.applicationContext) // 始终用 ApplicationContext
}
fun getContext(): Context? {
return contextRef?.get()
}
}6.3 内存监控
kotlin
// ==== 检测内存泄漏 ====
class MemoryMonitor @Inject constructor() {
private val runtime = Runtime.getRuntime()
fun logMemoryUsage(tag: String) {
val total = runtime.totalMemory() / 1024 / 1024
val free = runtime.freeMemory() / 1024 / 1024
val used = total - free
Log.d(tag, "Memory: used=${used}MB, total=${total}MB, free=${free}MB")
}
fun checkLeak(expectedObjects: Int, actualObjects: Int) {
if (actualObjects > expectedObjects) {
Log.w("MemoryLeak", "Possible leak detected: expected=$expectedObjects, actual=$actualObjects")
}
}
}
// ==== 使用 LeakCanary ====
// build.gradle.kts
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.10")
// Application
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// LeakCanary 自动检测内存泄漏
}
}6.4 作用域清理最佳实践
kotlin
// ==== Koin 作用域清理 ====
abstract class ScopedActivity : AppCompatActivity() {
protected var activityScope: Scope? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityScope = koin.createScope(
scopeId = "activity_$this",
scopeName = named("activity")
)
}
override fun onDestroy() {
super.onDestroy()
activityScope?.close() // ✅ 必须关闭
activityScope = null
}
}
// ==== Hilt 自动清理 ====
// Hilt 自动管理作用域生命周期
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject lateinit var activityDep: ActivityScopedDep
// 无需手动清理,Hilt 在 onDestroy 时自动释放
}
// ==== 手动清理缓存 ====
@Singleton
class CacheManager @Inject constructor() {
private val cache = mutableMapOf<String, Any>()
fun put(key: String, value: Any) {
cache[key] = value
}
fun get(key: String): Any? = cache[key]
fun clear() {
cache.clear() // 手动清理
}
// 定期清理
@OnLifecycleEvent(Lifecycle.Event.ON_LOW_MEMORY)
fun onLowMemory() {
cache.clear()
}
}七、循环依赖解决
7.1 循环依赖问题
kotlin
// ==== 循环依赖示例 ====
// ❌ 错误:直接循环依赖
class A @Inject constructor(private val b: B)
class B @Inject constructor(private val a: A)
// 编译错误:循环依赖 detected
// ❌ 错误:间接循环依赖
class A @Inject constructor(private val b: B)
class B @Inject constructor(private val c: C)
class C @Inject constructor(private val a: A)
// 编译错误:循环依赖 detected7.2 解决方案 1:使用 Lazy
kotlin
// ✅ 使用 Lazy 延迟初始化
class A @Inject constructor(
private val bProvider: Lazy<B>
) {
fun doSomething() {
val b = bProvider.value // 需要时才获取
b.doWork()
}
}
class B @Inject constructor(
private val a: A // 直接注入
) {
fun doWork() {
a.doSomething()
}
}
// 工作原理:
// 1. 创建 A 时,bProvider 是 Lazy,不立即创建 B
// 2. 创建 B 时,需要 A,A 已存在
// 3. 当 A 需要 B 时,通过 bProvider.value 获取7.3 解决方案 2:使用 Provider
kotlin
// ✅ 使用 Provider 每次获取
class A @Inject constructor(
private val bProvider: Provider<B>
) {
fun doSomething() {
val b = bProvider.get() // 每次获取(可能是新实例)
b.doWork()
}
}
class B @Inject constructor(
private val a: A
)
// Lazy vs Provider:
// Lazy: 首次获取时创建,后续复用
// Provider: 每次获取都创建新实例7.4 解决方案 3:使用 Setter 注入
kotlin
// ✅ 使用方法注入
class A @Inject constructor() {
private lateinit var b: B
@Inject
fun setB(b: B) {
this.b = b
}
}
class B @Inject constructor(
private val a: A
)
// 工作原理:
// 1. 创建 A(不需要 B)
// 2. 创建 B(需要 A,A 已存在)
// 3. 调用 A.setB(b) 注入 B7.5 解决方案 4:重构设计
kotlin
// ✅ 引入协调者
interface Coordinator {
fun coordinate()
}
class CoordinatorImpl @Inject constructor(
private val a: A,
private val b: B
) : Coordinator {
override fun coordinate() {
a.doWith(b)
b.doWith(a)
}
}
class A @Inject constructor() {
fun doWith(b: B) { /* ... */ }
}
class B @Inject constructor() {
fun doWith(a: A) { /* ... */ }
}
// ✅ 使用接口解耦
interface AObserver {
fun onAChanged()
}
class A @Inject constructor() {
private val observers = mutableListOf<AObserver>()
fun addObserver(observer: AObserver) {
observers.add(observer)
}
fun notifyChange() {
observers.forEach { it.onAChanged() }
}
}
class B @Inject constructor() : AObserver {
override fun onAChanged() {
// 处理 A 的变化
}
}7.6 解决方案 5:使用作用域
kotlin
// ✅ 使用不同作用域打破循环
@Module
@InstallIn(SingletonComponent::class)
object ModuleA {
@Provides
@Singleton
fun provideA(bProvider: Provider<B>): A {
return A(bProvider)
}
}
@Module
@InstallIn(ActivityComponent::class)
abstract class ModuleB {
@Binds
@ActivityScoped
abstract fun bindB(impl: BImpl): B
}
class A @Inject constructor(
private val bProvider: Provider<B> // ActivityScoped
)
class BImpl @Inject constructor(
private val a: A // Singleton
) : B
// A 是 Singleton,B 是 ActivityScoped
// B 依赖 A(可以,父作用域)
// A 通过 Provider 获取 B(延迟,打破循环)八、面试考点
8.1 基础问题
Q1: 什么是 DI 中的作用域?
A:
作用域定义了依赖实例的生命周期:
- 何时创建
- 何时销毁
- 是否共享实例
- 可见范围
常见作用域:
- Singleton: 应用生命周期
- ActivityScoped: Activity 生命周期
- ViewModelScoped: ViewModel 生命周期
- Factory: 每次新实例Q2: @Singleton 和 @Scope 的区别?
A:
kotlin
// @Singleton 是内置的作用域注解
@Singleton
class ApiService { } // 整个应用一个实例
// @Scope 用于定义自定义作用域
@Scope
@Retention(RUNTIME)
annotation class PerUserSession
@PerUserSession
class UserManager { } // 每个用户会话一个实例
// @Singleton 是 @Scope 的特例Q3: Hilt 有哪些内置作用域?
A:
@Singleton - 应用生命周期
@ActivityRetainedScoped - 配置变更保留
@ViewModelScoped - ViewModel 生命周期
@ActivityScoped - Activity 生命周期
@FragmentScoped - Fragment 生命周期
@ViewScoped - View 生命周期
@ServiceScoped - Service 生命周期
@BroadcastReceiverScoped - BroadcastReceiver 生命周期8.2 进阶问题
Q4: 如何解决循环依赖?
A:
kotlin
// 方法 1:使用 Lazy
class A @Inject constructor(private val bProvider: Lazy<B>)
// 方法 2:使用 Provider
class A @Inject constructor(private val bProvider: Provider<B>)
// 方法 3:使用 Setter 注入
class A {
@Inject fun setB(b: B) { }
}
// 方法 4:重构设计,引入协调者
class Coordinator @Inject constructor(private val a: A, private val b: B)Q5: @ActivityRetainedScoped 和 @ActivityScoped 的区别?
A:
@ActivityRetainedScoped:
- 在配置变更时保留(屏幕旋转)
- Activity 真正销毁时才销毁
- ViewModel 自动使用此作用域
@ActivityScoped:
- 配置变更时销毁
- Activity 销毁时销毁
- 适合 UI 组件
选择:
- 需要在配置变更后保留的数据 → ActivityRetainedScoped
- 与 Activity 强绑定的 UI 组件 → ActivityScopedQ6: 如何避免内存泄漏?
A:
kotlin
// 1. 单例中只使用 ApplicationContext
@Singleton fun provideX(@ApplicationContext ctx: Context)
// 2. 及时关闭作用域
scope?.close()
// 3. 避免持有长生命周期的对象
// 4. 使用 WeakReference
// 5. 使用 LeakCanary 检测8.3 高级问题
Q7: 作用域层次结构是什么?
A:
SingletonComponent (根)
└─ ActivityRetainedComponent
└─ ViewModelComponent
└─ ActivityComponent
└─ FragmentComponent
└─ ViewComponent
└─ ServiceComponent
└─ BroadcastReceiverComponent
规则:
- 子组件可以访问父组件的依赖
- 父组件不能访问子组件的依赖
- 作用域必须与 Component 匹配Q8: 自定义作用域如何实现?
A:
kotlin
// 1. 定义注解
@Scope
@Retention(RUNTIME)
annotation class PerUserSession
// 2. 在 Component 中使用
@Component(
modules = [UserModule::class],
dependencies = [AppComponent::class]
)
@PerUserSession
interface UserComponent
// 3. 在 Module 中绑定
@Module
abstract class UserModule {
@Binds
@PerUserSession
abstract fun bindUserManager(impl: UserManagerImpl): UserManager
}Q9: Koin 的作用域如何管理?
A:
kotlin
// 定义
val module = module {
scope(named("session")) {
scoped { UserManager() }
}
}
// 创建
val scope = koin.createScope("id", named("session"))
// 获取
val userManager = scope.get<UserManager>()
// 销毁
scope.close()Q10: 作用域与配置变更的关系?
A:
配置变更(屏幕旋转、语言切换)时:
保留的作用域:
- @Singleton ✅
- @ActivityRetainedScoped ✅
- @ViewModelScoped ✅
销毁的作用域:
- @ActivityScoped ❌
- @FragmentScoped ❌
- @ViewScoped ❌
设计原则:
- 需要在配置变更后保留的数据使用 ActivityRetainedScoped
- UI 组件使用 ActivityScoped/FragmentScoped九、最佳实践总结
9.1 推荐实践 ✅
kotlin
// 1. 选择合适的作用域
@Singleton fun provideApi(): ApiService // 共享服务
@ViewModelScoped fun provideRepo(): Repository // ViewModel 数据
@ActivityScoped fun provideAdapter(): Adapter // UI 组件
// 2. 使用 ApplicationContext
@Singleton fun provideX(@ApplicationContext ctx: Context)
// 3. 及时关闭作用域
override fun onDestroy() {
scope?.close()
}
// 4. 用 Lazy/Provider 解决循环依赖
@Inject constructor(private val depProvider: Lazy<Dep>)
// 5. 使用 LeakCanary 检测泄漏
debugImplementation("com.squareup.leakcanary:leakcanary-android")9.2 避免的陷阱 ❌
kotlin
// 1. 单例持有 Activity
@Singleton fun provideX(activity: Activity) // ❌
// 2. 作用域不匹配
@InstallIn(SingletonComponent::class)
object Module {
@Provides @ActivityScoped fun provideX() // ❌
}
// 3. 忘记关闭作用域
val scope = koin.createScope(...)
// 忘记 scope.close() // ❌
// 4. 在配置变更敏感的地方使用 ActivityScoped
@ActivityScoped class Data // ❌ 屏幕旋转后数据丢失