You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Dagger Hilt单例在应用移除任务重启后未实例化的解决方法

问题分析

你的问题核心在于ExoPlayer被声明为进程级单例(@Singleton + SingletonComponent),但你在onTaskRemoved中手动释放了ExoPlayer资源。由于Dagger Hilt的SingletonComponent单例与进程生命周期绑定,只要应用进程未完全销毁,Hilt就不会重新创建ExoPlayer实例。当你重启应用时,注入的仍是已经被release()的废弃实例,其内部关联的Handler线程已死亡,从而抛出Handler sending message to a dead thread异常。

解决方案

方案1:调整ExoPlayer作用域,绑定到Service生命周期

将ExoPlayer的依赖注入作用域从SingletonComponent改为ServiceComponent,让ExoPlayer的生命周期与EPlayerSessionService绑定,每次Service启动都会生成新的实例:

  1. 修改AppModule的安装组件和作用域:
@Module
@InstallIn(ServiceComponent::class) // 改为ServiceComponent
class AppModule {

    @Provides
    @ServiceScoped // 使用Service级作用域
    fun providesAudioAttributes(): AudioAttributes = AudioAttributes.Builder()
        .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
        .setUsage(C.USAGE_MEDIA)
        .build()

    @OptIn(UnstableApi::class)
    @Provides
    @ServiceScoped
    fun provideExoPlayer(
        @ApplicationContext context: Context,
        audioAttributes: AudioAttributes,
    ): ExoPlayer {
        Log.d("ExoPlayer", "Creating new instance of ExoPlayer")
        return ExoPlayer.Builder(context)
            .setAudioAttributes(audioAttributes, true)
            .setHandleAudioBecomingNoisy(true)
            .setTrackSelector(DefaultTrackSelector(context))
            .build()
    }
}
  1. EPlayerSessionService的注入逻辑保持不变,现在每次Service启动时,Hilt都会提供全新的ExoPlayer实例,避免使用已释放的废弃对象。

方案2:用工厂模式管理ExoPlayer实例(适用于跨组件共享场景)

如果需要在多个组件(如Activity、Service)中共享ExoPlayer,可创建工厂类管理实例状态,释放后置空以允许重新创建:

  1. 创建ExoPlayer工厂类:
class ExoPlayerFactory @Inject constructor(
    @ApplicationContext private val context: Context,
    private val audioAttributes: AudioAttributes
) {
    private var exoPlayer: ExoPlayer? = null

    @OptIn(UnstableApi::class)
    fun getInstance(): ExoPlayer {
        return exoPlayer ?: run {
            Log.d("ExoPlayer", "Creating new instance of ExoPlayer")
            ExoPlayer.Builder(context)
                .setAudioAttributes(audioAttributes, true)
                .setHandleAudioBecomingNoisy(true)
                .setTrackSelector(DefaultTrackSelector(context))
                .build()
                .also { exoPlayer = it }
        }
    }

    fun release() {
        exoPlayer?.release()
        exoPlayer = null // 置空,下次getInstance时会重新创建
    }
}
  1. 在AppModule中提供工厂的单例:
@Module
@InstallIn(SingletonComponent::class)
class AppModule {

    @Provides
    @Singleton
    fun providesAudioAttributes(): AudioAttributes = AudioAttributes.Builder()
        .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
        .setUsage(C.USAGE_MEDIA)
        .build()

    @Provides
    @Singleton
    fun provideExoPlayerFactory(
        @ApplicationContext context: Context,
        audioAttributes: AudioAttributes
    ): ExoPlayerFactory {
        return ExoPlayerFactory(context, audioAttributes)
    }
}
  1. 修改Service中的注入和释放逻辑:
@AndroidEntryPoint
class EPlayerSessionService:MediaSessionService() {

    @Inject
    lateinit var mediaSession: MediaSession

    @Inject
    lateinit var notificationManager: EPlayerNotificationManager

    @Inject
    lateinit var playerServiceHandler: PlayerServiceHandler

    @Inject
    lateinit var exoPlayerFactory: ExoPlayerFactory
    private lateinit var exoPlayer: ExoPlayer

    override fun onCreate() {
        super.onCreate()
        exoPlayer = exoPlayerFactory.getInstance()
    }

    override fun onTaskRemoved(rootIntent: Intent?) {
        super.onTaskRemoved(rootIntent)
        if (exoPlayer.playWhenReady) {
            exoPlayer.pause()
        }
        exoPlayerFactory.release() // 调用工厂的release方法置空实例
        mediaSession.release()
        stopSelf()
    }
}

额外注意事项

  • 如果应用中有其他组件(如Activity)使用ExoPlayer,需同步调整注入逻辑,确保Service释放后,其他组件能获取新实例。
  • 避免直接持有ExoPlayer的全局引用,始终通过工厂或作用域正确管理实例生命周期。

内容的提问来源于stack exchange,提问作者daniel ernest

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 20:03:28