Android开发中向领域层用例传递Context是否为最佳实践?
问题解答
直接给UseCase传Context靠谱吗?
不太靠谱,原因如下:
- 职责混乱:UseCase的核心应该是处理业务逻辑(比如计算闹钟时间、存储闹钟数据),而创建
PendingIntent属于Android框架层操作,塞到UseCase里会让它的职责变得不纯粹。 - 测试难度高:依赖
Context后,UseCase会和Android系统绑定,单元测试时需要mock整个Context,操作繁琐,甚至无法脱离Android环境做纯JVM测试。 - 内存泄漏风险:如果不小心传入Activity的Context,一旦UseCase持有这个Context但Activity已销毁,就可能引发内存泄漏。
可行的解决方案
1. 封装闹钟调度逻辑到独立类(首推)
创建专门的AlarmScheduler接口与实现类,把Context、AlarmManager和PendingIntent的创建逻辑全部封装进去,UseCase只依赖这个接口,完全不接触Android框架类。
示例代码:
// 定义抽象接口,规范闹钟调度行为 interface AlarmScheduler { fun scheduleRepeatingAlarm(alarmTime: Long, alarmId: Int) } // Android平台的实现类,处理具体的闹钟操作 class AndroidAlarmScheduler( private val appContext: Context, private val alarmManager: AlarmManager ) : AlarmScheduler { override fun scheduleRepeatingAlarm(alarmTime: Long, alarmId: Int) { val pendingIntent = PendingIntent.getBroadcast( appContext, alarmId, Intent(appContext, AlarmReceiver::class.java).apply { putExtra("ALARM_ID", alarmId) }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) alarmManager.setRepeating( AlarmManager.RTC_WAKEUP, alarmTime, AlarmManager.INTERVAL_DAY, pendingIntent ) } } // 修改后的UseCase,仅依赖业务类与抽象调度接口 class SetReminderAlarmsUseCase( private val hydrateRepository: HydrateRepository, private val alarmScheduler: AlarmScheduler ) { suspend operator fun invoke( wakeUpTime: String, sleepTime: String, drinkAmount: Int, target: Int, ) { val alarms = getAlarmsList(wakeUpTime, sleepTime, drinkAmount, target) insertAlarms(alarms) alarms.forEach { alarm -> alarmScheduler.scheduleRepeatingAlarm(getAlarmTime(alarm), alarm.id) } } // 业务辅助方法保留 private fun getAlarmsList(...) = ... private suspend fun insertAlarms(alarms: List<Alarm>) = hydrateRepository.insertAlarms(alarms) private fun getAlarmTime(alarm: Alarm): Long = ... }
这种方案的优势:
- UseCase彻底与Android框架解绑,单元测试时只需mock
AlarmScheduler接口即可验证业务逻辑正确性。 - 闹钟调度逻辑集中管理,后续修改(比如换用
setExactAndAllowWhileIdle实现精准闹钟)仅需改动实现类,不影响业务代码。
2. 提供PendingIntent工厂类
如果不想封装完整的调度逻辑,可以创建PendingIntentFactory类专门负责创建所需的PendingIntent,将Context依赖转移到工厂中,UseCase仅依赖工厂接口:
// 抽象工厂接口 interface PendingIntentFactory { fun createAlarmPendingIntent(alarmId: Int): PendingIntent } // Android平台的工厂实现 class AndroidPendingIntentFactory(private val appContext: Context) : PendingIntentFactory { override fun createAlarmPendingIntent(alarmId: Int): PendingIntent { return PendingIntent.getBroadcast( appContext, alarmId, Intent(appContext, AlarmReceiver::class.java).apply { putExtra("ALARM_ID", alarmId) }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) } } // 修改后的UseCase class SetReminderAlarmsUseCase( private val hydrateRepository: HydrateRepository, private val alarmManager: AlarmManager, private val pendingIntentFactory: PendingIntentFactory ) { suspend operator fun invoke( wakeUpTime: String, sleepTime: String, drinkAmount: Int, target: Int, ) { val alarms = getAlarmsList(wakeUpTime, sleepTime, drinkAmount, target) insertAlarms(alarms) alarms.forEach { alarm -> val pendingIntent = pendingIntentFactory.createAlarmPendingIntent(alarm.id) alarmManager.setRepeating( AlarmManager.RTC_WAKEUP, getAlarmTime(alarm), AlarmManager.INTERVAL_DAY, pendingIntent ) } } }
这种方式比直接传Context更合理,虽然UseCase仍依赖AlarmManager,但AlarmManager可通过依赖注入提供,测试时也能轻松mock。
注意事项
- 无论采用哪种方案,都要使用Application Context而非Activity Context,避免内存泄漏;可通过依赖注入(如Dagger/Hilt)提供Application Context实例。
- Android 12及以上版本创建
PendingIntent时,必须添加FLAG_IMMUTABLE标记,避免安全风险。
内容的提问来源于stack exchange,提问作者mo_nawas
相关产品推荐
相关产品推荐

