Android碎片基础知识:为什么?这个概念从概念上讲是错误的吗?

13

我有一个关于在Android中“正确编程”的问题。

我目前正在使用片段(fragment)开发一个应用程序。它涉及动态添加片段到Activity,从XML膨胀的片段,以及从XML或动态添加的嵌套片段。让我们说,几乎包含了所有类型。

这个问题关注的概念是与片段之间涉及的通信过程。因此,我已经阅读了文档,并且这不是我第一次尝试使用片段。

常识(和文档)告诉我们,如果片段想要与其活动进行对话或通信,则应使用接口。

示例:

TestFragment

public class TestFragment extends Fragment {

  private TestFragmentInterface listener; 

  public interface TestFragmentInterface {

      void actionMethod();

  }


  @Override
  public void onViewCreated(View view, Bundle savedInstanceState) {

      if (getActivity() instanceof TestFragmentInterface) {
          listener = (TestFragmentInterface) getActivity();
      }

      // sending the event
      if (listener != null) listener.actionMethod();
  }

}

测试活动

public class Test implements TestFragmentInterface {

  @Override
  public void actionMethod() {
    ..
  }
}

一切都很好。

这样做可以提高可重用性,因为我的TestFragment可以与任何实现我声明的接口的Activity进行交互。

反过来,Activity可以通过持有引用并调用其公共方法与Fragment进行交互。这也是建议的片段到片段通信方式,使用Activity作为桥梁。

这很酷,但有时候感觉使用接口可能有点“过度”了。

问题 A

在我所附加的片段具有相当专注的角色的情况下,这意味着它们是为特定活动完成的,并且不会被其他地方使用,忽略接口实现并只做类似以下的事情,在概念上是否错误?

((TestActivity) getActivity().myCustomMethod();

这也适用于这种场景:我需要处理各种不同的碎片(虽然不是我的情况,但可以将其看作“最坏的情况”),这意味着它应该为每个要处理的碎片实现一个方法。这将使代码变成一堆“潜在不必要的行”。

更进一步:仍然使用“专注”的碎片,旨在仅以某种方式工作,那么嵌套碎片的使用如何呢?

像这样添加它们:

public class TestFragment extends Fragment {


  private void myTestMethod() {

    NestedFragment nested = new NestedFragment();

    getChildFragmentManager()
      .beginTransaction()
      .add(R.id.container, nested)
      .commit();
  }

}

这将NestedFragment绑定到TestFragment。我再说一遍,NestedFragment和TestFragment一样,只能以这种方式使用,否则就没有意义。

回到问题,我该如何在这种情况下行事?

问题B

1)我应该在NestedFragment中提供一个接口,并使TestFragment实现NestedFragmentInterface吗? 在这种情况下,我将采取以下措施:

NestedFragment

public class NestedFragment extends Fragment {

  private NestedFragmentInterface listener; 

  public interface NestedFragmentInterface {

      void actionMethodNested();

  }


  @Override
  public void onViewCreated(View view, Bundle savedInstanceState) {

      if (getParentFragment() instanceof NestedFragmentInterface) {
          listener = (NestedFragmentInterface) getParentFragment();
      }

      // sending the event
      if (listener != null) listener.actionMethodNested();
  }

}

2) 我应该(或者可以)忽略接口,直接调用吗?

getParentFragment().publicParentMethod();

?

3)我应该在NestedFragment中创建接口,但让activity实现它,以便activity调用TestFragment吗?

问题C

关于使用Activity作为片段之间桥梁的想法,我认为这是为了正确处理所有这些对象的生命周期。如果尝试手动处理系统可能抛出的异常,直接从片段到片段(使用接口或直接调用公共方法)是否仍然可行?


看看这个是否有帮助:https://corner.squareup.com/2014/10/advocating-against-android-fragments.html - Vishal Vyas
我已经阅读了您发布的链接,虽然它确实是一篇有趣且包含大量信息的文章,但恐怕它并没有回答我的问题。无论如何,还是非常感谢。 - FrancescoC
2个回答

10

我会尽力回答这堆文字 :)

问题A:

片段旨在成为可重复使用的模块,可以与任何活动进行插拔式交互。因此,与活动进行接口交互的唯一正确方法是使活动从片段了解的接口继承。

public class MapFragment extends Fragment {

  private MapFragmentInterface listener; 

  public interface MapFragmentInterface {

      //All methods to interface with an activity

  }


  @Override
  public void onViewCreated(View view, Bundle savedInstanceState) {
      // sending the event
      if (listener != null) listener.anyMethodInTheAboveInterface();
  }

}

然后让该活动实现该接口

public class MainActivity extends Activity implement MapFragmentInterface{

//All methods need to be implemented here
}

只要活动实现了此接口,就可以将您的片段与任何活动一起使用。需要此接口的原因是片段可以与任何活动一起使用。调用像

((TestActivity) getActivity().myCustomMethod();

依赖于一个事实,即您的碎片只能在测试活动中工作,因此“违反”了碎片的规则。

问题B和C:

假设您遵循了碎片的正确指南,并且它们是独立的模块。那么,您不应该有碎片需要相互了解的情况。99%的情况下,人们认为他们需要直接通信的碎片可以通过使用MVC模式或类似的东西将其问题重构到我给出的情况中。使活动充当控制器,并告诉碎片何时需要更新,然后创建单独的数据存储。


从你的描述中,我理解到我所做的是可能的,但并不建议,因为使用方式超出了最初的范围。这并不是一件有害的事情,只是没有按照最初的意图来使用。那么,我错了多少呢?:) - FrancescoC
你是100%正确的。像所有编程一样,有很多方法来完成任务。很多时候,我会按照你描述的方式来完成,因为我知道我的代码片段只会在这一个活动中使用。然而,如果你想要“正确”,你应该通过接口来实现,让你的代码解耦合尽可能高。不过,如果你有时间和金钱,你应该总是以正确的方式构建你的应用程序 :) - nbroeking

4

我会尽力让所有内容更加清晰易懂。

首先,考虑一下为片段设置侦听器的方法。在onViewCreated方法中设置侦听器是不好的,因为它会导致每次创建片段时都要重置侦听器。把它设置到onAttach方法中即可。

我提到了代码行。请注意,实现常见行为(例如从资源创建视图和设置FragmentListener)的BaseFragment在您的应用程序中非常有用。

此外,为了减少代码行数并实现部分代码重用,您可以在BaseFragment中使用泛型。因此,请查看以下代码片段:

public abstract BaseFragment<T extends BaseFragmentListener> extends Fragment {

  T mListener;

  public void onAttach(Activity activity) {
    super.onAttach(activity);
    if (Activity instanceof T)
      mListener = (T) activity; 
  }

  abstract int getLayoutResourceId();

  @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View layout = inflater.inflate(getLayoutResourceId(), null);
        // you can use some view injected tools here, mb ButterKnife 
        return layout;
    }
}

答案A(针对问题A):

如果您只有一个活动的片段,您需要决定:“您真的需要在这里使用片段吗?”。但是,也许正好为一个活动准备一个片段可以从活动中提取一些视图逻辑并清除基本逻辑。但是要清除应用程序的基本架构逻辑,请使用侦听器。这将使其他开发人员的生活更加轻松。

答案B: 对于嵌套片段,您需要解决它们需要使用确切的活动还是仅使用片段,并将其用作到其他系统的桥梁。如果您知道嵌套片段将始终嵌套,则需要将父片段声明为侦听器,否则您必须使用其他方法。

注意: 作为在App的不同部分之间通信的基本方法,您可以使用事件,例如尝试查看事件总线。它为通信提供了通用方法,您可以提取调用自定义侦听器方法和更多其他内容的逻辑,所有逻辑都位于处理事件中,您将拥有一个协作的中介系统。

答案C: 我部分地解释了一种协作片段的方法。使用一个事件分发器避免您为所有不同的通信拥有许多侦听器。有时非常有利可图。

或者,我认为更可用的方法是使用活动或其他类在活动中生存,作为片段协作的中介,因为在整个生命周期中处理和系统都会发生许多片段更改的情况。它将所有这些逻辑集中在一个地方,并使您的代码更清晰。

希望我的考虑能够帮助您。


谢谢,这是一个很好的答案,逻辑详细且有帮助,给了我一些好的想法:) - FrancescoC

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