1. 中介者模式在Android开发中的价值
DialogFragment作为Android开发中常用的弹窗组件,经常需要与Activity或其他Fragment进行复杂的交互。传统做法是直接在这些组件之间建立引用关系,但这种强耦合的架构会带来几个典型问题:
- 组件间通信路径复杂,形成网状依赖
- 修改一个组件可能影响多个关联组件
- 难以单独测试某个功能模块
- 代码可维护性随功能增加急剧下降
中介者模式(Mediator Pattern)正是为解决这类问题而生。它通过引入一个中介对象来封装一组对象之间的交互,使各对象不需要显式地相互引用,从而使其耦合松散。在Android开发中,DialogFragment作为视图容器,非常适合承担中介者的角色。
关键理解:中介者不是简单的消息转发站,而是真正承担了协调逻辑的"智能路由器"。它知道何时以及如何协调不同组件间的交互。
2. DialogFragment作为中介者的实现架构
2.1 基础实现方案
典型的DialogFragment中介者实现包含以下核心组件:
kotlin复制class OrderDialogFragment : DialogFragment() {
// 定义通信接口
interface OrderDialogListener {
fun onConfirm(order: Order)
fun onCancel()
}
private lateinit var listener: OrderDialogListener
override fun onAttach(context: Context) {
super.onAttach(context)
// 绑定监听器
listener = context as OrderDialogListener
}
fun submitOrder(order: Order) {
// 处理订单逻辑...
listener.onConfirm(order)
}
}
Activity实现该接口完成闭环:
kotlin复制class MainActivity : AppCompatActivity(), OrderDialogFragment.OrderDialogListener {
override fun onConfirm(order: Order) {
// 更新UI或处理业务逻辑
}
override fun onCancel() {
// 处理取消逻辑
}
}
2.2 进阶优化方案
基础实现虽然解耦了视图层,但仍有改进空间:
- 使用ViewModel作为业务逻辑中介
- 引入LiveData实现响应式通信
- 通过DI框架管理依赖
优化后的架构:
kotlin复制class OrderViewModel : ViewModel() {
private val _orderEvents = MutableLiveData<OrderEvent>()
val orderEvents: LiveData<OrderEvent> = _orderEvents
fun processOrder(order: Order) {
// 验证和处理订单...
_orderEvents.value = OrderEvent.Success(order)
}
}
class OrderDialogFragment : DialogFragment() {
private val viewModel: OrderViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.btnConfirm.setOnClickListener {
viewModel.processOrder(buildOrderFromInput())
}
viewModel.orderEvents.observe(viewLifecycleOwner) { event ->
when(event) {
is OrderEvent.Success -> dismiss()
// 处理其他事件...
}
}
}
}
3. 实战:电商应用中的复杂交互场景
3.1 商品筛选场景实现
电商应用中常见的筛选对话框需要协调多个组件:
- 筛选条件之间的联动(如选择品类后更新品牌列表)
- 将最终筛选结果回传给商品列表页
- 处理用户取消操作
kotlin复制class FilterDialogFragment : DialogFragment() {
private val viewModel: FilterViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 观察品类数据变化
viewModel.categories.observe(viewLifecycleOwner) { categories ->
updateCategorySpinner(categories)
}
// 品类选择影响品牌列表
binding.spinnerCategory.setOnItemSelectedListener {
viewModel.loadBrands(it.selectedItem as Category)
}
binding.btnApply.setOnClickListener {
viewModel.applyFilters(
category = binding.spinnerCategory.selectedItem as Category,
brand = binding.spinnerBrand.selectedItem as Brand?,
priceRange = binding.rangeSlider.values
)
dismiss()
}
}
}
3.2 多步骤表单处理
对于复杂的多步骤表单,DialogFragment可以很好地管理各步骤间的状态:
kotlin复制class MultiStepFormDialog : DialogFragment() {
private val viewModel: FormViewModel by activityViewModels()
private var currentStep = 0
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.step1Data.observe(viewLifecycleOwner) { data ->
if(currentStep == 0) updateStep1UI(data)
}
binding.btnNext.setOnClickListener {
when(currentStep) {
0 -> {
viewModel.saveStep1(collectStep1Data())
showStep2()
currentStep++
}
1 -> {
viewModel.submitForm(collectStep2Data())
dismiss()
}
}
}
}
private fun showStep2() {
// 切换UI到第二步...
}
}
4. 性能优化与内存管理
4.1 避免内存泄漏的实践
DialogFragment作为中介者时,需要特别注意生命周期管理:
- 使用viewLifecycleOwner替代lifecycleOwner
- 及时清理回调引用
- 正确处理配置变更
kotlin复制override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 正确做法:使用viewLifecycleOwner
viewModel.data.observe(viewLifecycleOwner) { data ->
updateUI(data)
}
// 错误做法:直接使用this可能导致内存泄漏
// viewModel.data.observe(this) { ... }
}
override fun onDestroyView() {
// 清理视图相关引用
binding = null
super.onDestroyView()
}
4.2 大型数据传递优化
当需要传递大量数据时,建议:
- 使用ViewModel共享数据
- 对于复杂对象,实现Parcelable接口
- 考虑使用ID引用而非完整对象
kotlin复制// 实现Parcelable的优化方案
data class Product(
val id: String,
val name: String,
val price: Double
) : Parcelable {
// Parcelable实现...
}
// 在DialogFragment中接收
arguments?.getParcelable<Product>("key_product")
5. 测试策略与调试技巧
5.1 单元测试方案
针对中介者DialogFragment的测试要点:
- 测试接口回调的正确性
- 验证ViewModel交互逻辑
- 模拟各种用户操作路径
kotlin复制@Test
fun `should notify listener when confirm clicked`() = runTest {
val fragment = TestOrderDialogFragment()
val mockListener = mock<OrderDialogListener>()
fragment.listener = mockListener
fragment.show(testRule.activity.supportFragmentManager, "test")
onView(withId(R.id.btn_confirm)).perform(click())
verify(mockListener).onConfirm(any())
}
@Test
fun `should update liveData when processing order`() {
val viewModel = OrderViewModel()
val testObserver = viewModel.orderEvents.test()
viewModel.processOrder(testOrder)
testObserver.assertValue { it is OrderEvent.Success }
}
5.2 常见问题排查
-
回调不执行:
- 检查Fragment是否已attach到Activity
- 验证接口实现是否正确
- 确认没有重复创建DialogFragment实例
-
状态丢失:
- 使用ViewModel保存重要状态
- 正确处理配置变更
- 实现onSaveInstanceState保存必要数据
-
内存泄漏:
- 使用Android Profiler检查
- 确保没有长期持有Context引用
- 在onDestroyView中清理绑定
6. 设计模式组合应用
中介者模式常与其他模式配合使用:
6.1 结合观察者模式
kotlin复制// 使用LiveData作为观察者模式实现
viewModel.state.observe(viewLifecycleOwner) { state ->
when(state) {
is Loading -> showProgress()
is Success -> updateData(state.data)
is Error -> showError(state.message)
}
}
6.2 结合工厂模式
kotlin复制object DialogFactory {
fun createFilterDialog(type: FilterType): DialogFragment {
return when(type) {
FilterType.PRICE -> PriceFilterDialog()
FilterType.CATEGORY -> CategoryFilterDialog()
}
}
}
// 使用处
val dialog = DialogFactory.createFilterDialog(FilterType.PRICE)
dialog.show(supportFragmentManager, "filter")
6.3 结合策略模式
kotlin复制interface ValidationStrategy {
fun validate(input: String): Boolean
}
class OrderDialogFragment : DialogFragment() {
private lateinit var validationStrategy: ValidationStrategy
fun setValidationStrategy(strategy: ValidationStrategy) {
this.validationStrategy = strategy
}
private fun validateInput(input: String): Boolean {
return validationStrategy.validate(input)
}
}
7. 交互设计最佳实践
7.1 动画与过渡效果
xml复制<!-- res/anim/slide_up.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromYDelta="100%"
android:toYDelta="0"
android:duration="300"/>
</set>
// 在DialogFragment中应用
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.window?.setWindowAnimations(R.style.DialogAnimation)
return dialog
}
7.2 无障碍支持
- 添加内容描述
- 确保焦点顺序合理
- 支持键盘导航
kotlin复制binding.btnConfirm.contentDescription = getString(R.string.confirm_order)
binding.btnConfirm.isFocusable = true
7.3 暗黑模式适配
xml复制<!-- res/values-night/styles.xml -->
<style name="AppDialogTheme" parent="ThemeOverlay.MaterialComponents.Dialog">
<item name="android:windowBackground">@color/dialog_background_dark</item>
<item name="colorPrimary">@color/primary_dark</item>
</style>
8. 高级应用场景
8.1 跨模块通信
在模块化项目中,DialogFragment可以作为模块间通信的桥梁:
kotlin复制// core模块定义接口
interface CheckoutHandler {
fun onCheckoutSuccess(order: Order)
fun onCheckoutFailed(error: Throwable)
}
// feature模块实现
class PaymentDialogFragment : DialogFragment() {
private val checkoutHandler: CheckoutHandler by inject()
private fun processPayment() {
try {
val result = paymentProcessor.charge(amount)
checkoutHandler.onCheckoutSuccess(result.order)
dismiss()
} catch(e: Exception) {
checkoutHandler.onCheckoutFailed(e)
}
}
}
8.2 动态功能模块支持
kotlin复制fun showPremiumDialog(activity: FragmentActivity) {
if(isDynamicFeatureAvailable()) {
val intent = Intent().setClassName(
activity,
"com.example.premium.PremiumDialogActivity"
)
activity.startActivity(intent)
} else {
// 回退到基本对话框
BasicDialogFragment().show(activity.supportFragmentManager, "basic")
}
}
9. 兼容性处理
9.1 旧版本Android支持
- 使用AndroidX兼容库
- 避免使用新版API中的方法
- 提供回退方案
kotlin复制fun showDialog(manager: FragmentManager) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 使用新版API
CustomDialogFragment().show(manager, "dialog")
} else {
// 回退方案
LegacyDialogFragment().show(manager, "dialog")
}
}
9.2 不同设备尺寸适配
kotlin复制override fun onStart() {
super.onStart()
dialog?.window?.setLayout(
if(isTablet()) MATCH_PARENT else WRAP_CONTENT,
WRAP_CONTENT
)
}
10. 实际项目经验分享
在电商App的订单确认对话框中,我们遇到了几个典型问题:
-
问题:对话框旋转后状态丢失
解决:改用ViewModel保存状态,并在onCreateDialog中恢复 -
问题:多个对话框叠加导致混乱
解决:实现对话框队列管理,使用showNow替代show -
问题:异步操作导致对话框提前关闭
解决:在ViewModel中维护isLoading状态,在观察中控制dismiss时机
kotlin复制viewModel.checkoutState.observe(viewLifecycleOwner) { state ->
when(state) {
is Loading -> binding.progress.show()
is Success -> {
binding.progress.hide()
if(!state.shouldKeepOpen) {
dismiss()
}
}
}
}
另一个实用技巧是使用DialogFragment的setStyle来控制对话框样式:
kotlin复制override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, R.style.FullScreenDialog)
}
对于需要自定义高度的对话框,可以在onResume中调整:
kotlin复制override fun onResume() {
super.onResume()
dialog?.window?.setLayout(
resources.getDimensionPixelSize(R.dimen.dialog_width),
resources.getDimensionPixelSize(R.dimen.dialog_height)
)
}
