Skip to content

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 PerUnitOfWork

4.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)
// 编译错误:循环依赖 detected

7.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) 注入 B

7.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 组件 → ActivityScoped

Q6: 如何避免内存泄漏?

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  // ❌ 屏幕旋转后数据丢失

十、参考资料

官方文档

进阶阅读


🔗 上一篇: Koin 框架详解🔗 下一篇: 测试与模拟