在Activity和嵌套的Fragment之间进行通信,使用Activity.onAttachFragment还是Fragment.onAttach更好?

28
Android文档建议为了从Activity到托管的Fragment进行通信,Fragment可以定义一个回调接口,并要求宿主Activity实现它。基本模式涉及在Fragment中实现onAttach,并将Activity强制转换为回调接口。请参见http://developer.android.com/guide/components/fragments.html#CommunicatingWithActivity

以下是一个示例,演示如何为Fragment提供一些初始化数据以及监听导航回调。

public class HostActivity extends Activity implements FragmentHost {
  @Override
  UiModel getUiModel() {
    return mUiModel;
  }
  @Override
  FragmentNavListener getNavListener() {
    return mNavListener;
  }
...
}

public class HostedFragment extends Fragment {
  @Override
  public void onAttach(Activity activity) {
    super.onAttach(activity);
    if (activity instanceof FragmentHost) {
      FragmentHost host = (FragmentHost) activity;
      setUiModel(host.getUiModel());
      setNavListener(host.getFragmentNavListener());
    }
  }
  ...
}

将此与在宿主 Activity 中使用 onAttachFragment 来显式初始化 fragment 进行比较:

public class HostActivity extends Activity {
  @Override
  public void onAttachFragment(Fragment fragment) {
    super.onAttachFragment(fragment);
    if (fragment instanceof HostedFragment) {
      HostedFragment hostedFragment = ((HostFragment) fragment);
      hostedFragment.setUiModel(mUiModel);
      hostedFragment.setNavListener(mNavListener);
    }
  }
  ...
}

在我看来,第一个模式存在一些缺点:

  1. 它使得片段在不同的活动中更难使用,因为所有这些活动都必须实现所需的接口。我可以想象出这样的情况:给定的片段实例不要求被主机活动完全配置,但是所有潜在的主机活动都需要实现主机接口。
  2. 对于不熟悉该模式的人来说,代码稍微难以理解。在onFragmentAttached中初始化片段似乎更容易理解,因为初始化代码位于创建片段的同一类中。
  3. 使用像Robolectric这样的库进行单元测试变得更加困难,因为在调用onAttach时,您现在必须实现FragmentHost而不仅仅是调用onAttach(new Activity()。

对于那些已经完成了从活动到片段通信的人,你们发现哪种模式更可取,为什么?使用主机活动中的onAttachFragment是否存在缺点?

3个回答

6

关于测试,我个人无法发表意见,但是有一些替代fragment / activity回调接口通信的方法。

例如,您可以使用事件总线来解耦碎片和活动。这里有一个优秀的事件总线:

Otto - Square公司的事件总线

它由Square公司的一些非常有才华的工程师积极开发。您还可以使用Android支持库中打包的LocalBroadcastManager。

LocalBroadcastManager

Square的Eric Burke在他的演示文稿中提到了两者,可以在此处找到:

Android应用程序解剖学


2
Otto现已被弃用。 - Louis
LocalBroadcastManager已被弃用https://developer.android.com/reference/androidx/localbroadcastmanager/content/LocalBroadcastManager,建议使用此链接https://developer.android.com/training/basics/fragments/communicating。 - Sundeep1501

4

更新:根据最新指南,我们应该使用一个sharedViewModel类在片段和活动之间进行通信。

然而,如果您不使用viewModels而使用回调函数,则仍应考虑删除onAttachFragment回调函数,因为它已被弃用

相反,在onAttachFragment上,建议的方式是向与该事务相关联的fragmentManager添加侦听器,使用addFragmentOnAttachListener

请参见下面的示例:

   childFragmentManager.addFragmentOnAttachListener { _, fragment ->
        if (fragment is RetryDialogFragment) {
           //Do your work here.
        }
    }

你还需要注意添加监听器的位置。很可能你希望在执行片段事务之前添加此监听器,并确保你不会重复添加监听器。


在导航组件中,应该在哪里使用此监听器?我已经使用导航组件设置了BottomNavigationView,并在BaseActivity的onCreate()中实现了此监听器。第一次调用片段的onAttach()时,此监听器会通知,但是下一次手动调用onAttach()时,监听器不会通知。你能帮我吗?提前感谢您。 - Reyhane Farshbaf
手动调用 onAttach() 是什么意思?我对导航组件不是很熟悉,但为了收到通知,您必须使用相同的“fragmentManager”进行片段事务,该事务已添加“addFragmentOnAttachListener”。 - Sonu Sanjeev
我有一个场景,其中片段需要在片段内的按钮点击后重新加载。这个过程正确进行并导致onAttach()方法再次被调用,但监听器不会再次接收到回调。 - Reyhane Farshbaf
尝试在stackoverflow上提出一个单独的问题,其中包含您添加和重新加载片段的代码。 - Sonu Sanjeev

2
我在上一个项目中使用了Fragment.onAttach(...)模式,我看到了两个优点:
  1. 您可以早期检查托管活动是否实现所需接口,如果没有,则抛出异常
  2. 在分离片段后,持有托管上下文的引用的风险较小
为了利用第二个优点,您不应存储对UiModelNavListener的引用,就像您在代码示例中所做的那样。相反,每当您想要与这些实例交互时,您应该使用类似于((FragmentHost) getActivity).getNavListener().onNav(this)或者((FragmentHost) getActivity).onNav(this)的代码。您可以将片段宿主存储在字段中,并在onDetach(...)中将其设置为null作为一种折衷方法,如果您想避免常量转换。
我同意从创建它的活动中初始化片段似乎更直观。
话虽如此,在我的当前项目中,我将完全跳过片段。以下帖子很好地反映了我上一个项目中学到的经验:https://corner.squareup.com/2014/10/advocating-against-android-fragments.html

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接