困境:何时使用Fragment而不是Activity:

861
我知道Activities被设计为表示应用程序中的单个屏幕,而Fragments被设计为可重复使用的带有内部逻辑的UI布局。
直到不久前,我按照它们应该开发的方式开发了一个应用程序。我创建了一个Activity来表示应用程序的屏幕,并在ViewPagerGoogle Maps中使用Fragments。我很少创建ListFragment或其他可以多次重用的UI。
最近我偶然发现了一个项目,其中只包含2个Activities,一个是SettingsActivity,另一个是MainActivityMainActivity的布局填充了许多隐藏的全屏UI片段,只显示一个。在Activity逻辑中,存在许多FragmentTransitions在应用程序的不同屏幕之间进行切换。
我喜欢这种方法的原因是因为应用程序使用ActionBar,它保持完整,不会随着屏幕切换动画移动,这就是Activity切换所发生的情况。这使得屏幕转换更加流畅。
所以我想要求您分享关于这个主题的当前开发方式,我知道乍一看它可能像是一个基于观点的问题,但我认为这是一个关于Android设计和架构的问题……不是真正基于观点的问题。
更新(01.05.2014):根据Square的Eric Burke所做的演示(我必须说这是一个非常棒的演示,提供了许多对Android开发人员有用的工具。而且我与Square没有任何关系)。

http://www.infoq.com/presentations/Android-Design/

从我过去几个月的个人经验中,我发现构建应用程序的最佳方法是创建一组片段,这些片段代表应用程序中的一个流,并在一个活动中呈现所有这些片段。因此,您的应用程序中将有与流的数量相同的活动数量。
这样,操作栏在所有流屏幕上保持不变,但在更改流时会重新创建,这非常合理。正如Eric Burke所说,以及我也认识到的那样,尽可能使用少量的活动的哲学并不适用于所有情况,因为它会在他所称的“God”活动中造成混乱。

2
请查看我在SO上的帖子 - https://dev59.com/tGAf5IYBdhLWcg3wbSUi#24647574 - My God
单一活动:为什么、何时以及如何(Android Dev Summit '18)https://www.youtube.com/watch?v=2k8x8V77CrU&feature=youtu.be - Ben Butterworth
今天,对于这个问题最好的答案是学习SingleActivityArchitecture。 - zihadrizkyef
17个回答

308

专家会告诉你:“当我看到UI时,我就知道是否要使用Activity还是Fragment”。起初这可能毫无意义,但随着时间的推移,你实际上可以判断是否需要Fragment

我发现了一种非常有用的好习惯。当我试图向我的女儿解释某些事情时,我意识到了这一点。

也就是说,想象一个代表屏幕的盒子。你能在这个盒子里加载另一个屏幕吗?如果使用新的盒子,你是否必须复制第一个盒子中的多个项目?如果答案是“是”,那么你应该使用Fragments,因为根Activity可以保存所有重复的元素以节省创建它们的时间,并且你可以简单地替换盒子的部分内容。

不要忘记,你总是需要一个盒子容器(Activity),否则你的部分将会散落。因此,一个带有内部部件的盒子。

注意不要滥用这个盒子。Android UX专家建议(你可以在YouTube上找到他们)我们何时应明确加载另一个Activity,而不是使用Fragment(例如,当我们处理具有类别的导航抽屉时)。一旦你感到熟悉Fragments,你可以观看所有关于它们的视频。此外,它们是强制性学习材料。

你现在能否查看你的UI并确定是否需要ActivityFragment?你获得了新的视角吗?我认为你已经获得了。


10
你有提到的YouTube视频链接吗?我搜索了“Android UX专家”和“Android UX”,但不太确定你指的是哪些视频。 - me--
2
不需要了,我已经在一年前看过了。搜索一下安卓开发者官方谈论用户体验的视频。 - sandalone
1
一个考虑的例子:Activity 有 parentActivity,所以我们可以在从通知进入时合成后退栈,但我不认为有这样的 parentFragment。 - fikr4n
@BornToCode 这里有getParentFragment方法:https://developer.android.com/reference/android/support/v4/app/Fragment#getparentfragment - ToolmakerSteve
@ToolmakerSteve 是的,它是getParentFragment,但这不是我想说的,看 https://developer.android.com/guide/topics/manifest/activity-element#parent - fikr4n

154

我的理念是:

只有在绝对必要的情况下才创建活动。通过提供用于提交一堆片段事务的后退堆栈,我尽可能少地在应用程序中创建活动。此外,在各个片段之间进行通信比在活动之间来回发送数据更加容易。

活动转换很耗费资源,不是吗?至少我是这么认为的——因为旧的活动必须被销毁/暂停/停止、推入堆栈,然后必须创建/启动/恢复新的活动。

这只是自从引入片段以来我所持有的理念。


3
是的,但有时需要使用活动。一个例子是相机屏幕,在横向模式下使用更好。另一个例子是当您在“桌面”(启动器应用程序)上放置自定义appWidget时显示的配置屏幕。 - android developer
1
感谢您的回答和分享经验。那么您认为在Android中,如果应用程序架构允许,将应用程序限制为一个Activity并为所有屏幕使用Fragment是一种好的实践吗? - Emil Adz
1
@EmilAdz 是的,我也这么认为。尽可能地限制你的应用程序使用最少的活动 - 如果可能的话,以这样的方式设计它。 - VJ Vélan Solutions
2
那么,你如何解决需要相互传递“状态”的片段问题呢?所有片段的状态都需要存在于一个活动中,否则你就必须使用单例模式。 - Mr_E
48
我并不认为在各个碎片之间进行通信比在活动之间来回发送数据更容易。 - Denny
5
至少,在Android应用程序中,onActivityResult()比片段回调更加安全且易于使用。 - CoolMind

72
根据Google的讲座(可能在这里,我记不清了),您应该在可能的情况下考虑使用片段,因为它使您的代码更易于维护和控制。
然而,在某些情况下,它可能会变得过于复杂,因为托管片段的活动需要在它们之间导航/通信。
我认为您应该自行决定什么对您最好。将活动转换为片段或反之通常不是很困难。
如果您希望进一步阅读,我已经在这里创建了一个帖子来讨论这个问题。

7
谢谢您的答案和分享经验。那么您认为在安卓应用中限制只有一个Activity,并使用Fragment来展示所有屏幕,如果应用程序架构允许的话,这是一个好的编程实践? - Emil Adz
1
这取决于项目,但如果对你来说变得太复杂了,你也可以将其分成多个活动。不要害怕使用任何方法。你也可以同时使用它们。也许有时候使用片段而不是活动会太难了。我认为你应该尝试使用片段,但如果它在你的路上阻碍太多,就不要强迫到处都使用它。 - android developer
如果我想保持ActionBar的影响不变,同时所有内容都被切换,有没有可能使用Activities实现这一点? - Emil Adz
什么是Fragment?https://youtu.be/k3IT-IJ0J98?list=PLOU2XLYxmsILe6_eGvDN3GyiodoV3qNSC - Emer

45

2
今天我第一次了解Jetpack :) 自从引入片段以来,我们构建单个活动应用程序。多个活动更加复杂。 - The incredible Jan
1
@TheincredibleJan 你说得对,单一活动应用程序架构在Jetpack之前就是更好的解决方案。 - Francis

34

为什么我在所有情况下都更喜欢Fragment而不是Activity。

  • Activity很昂贵。在Fragment中,视图和属性状态是分离的 - 每当一个片段在backstack中时,它的视图将被销毁。所以你可以堆叠比Activity更多的Fragments。

  • Backstack操作。使用FragmentManager,轻松清除所有Fragments、插入多个Fragments等等。但对于Activity来说,操纵这些东西将是一场噩梦。

  • 更可预测的生命周期。只要主机Activity没有被回收,backstack中的Fragments就不会被回收。因此,可以使用FragmentManager::getFragments()查找特定的Fragment(不建议)。


嗨,我阅读了您关于Frag相对Act的优势的评论,您是否有在Github Repo中展示同样内容的项目? - Ümañg ßürmån

15

我认为这不是很相关。需要考虑的关键因素是:

  1. 你将多频繁地重复使用UI部分(例如菜单)
  2. 该应用程序是否也适用于平板电脑?

片段的主要用途是构建多窗格活动,这使得它非常适合平板电脑/手机响应式应用程序。


1
我认为片段的主要用途是制作自定义视图,而无需将它们视为自定义视图。这就是实际发生的事情。最初,Google展示了片段作为制作平板电脑响应式应用程序的便捷方式,因此如果需要,您可以将它们粘贴到不同的活动中。这是一种将代码附加到视图的方法,或多或少地,使它们可以在您想要的位置上粘贴(而无需制作自定义视图)。 - Lassi Kinnunen

13

不要忘记,Activity 是应用程序的块/组件,可以通过 Intent 共享和启动! 因此你的应用程序中的每个 Activity 只应解决一种任务。如果你的应用程序只有一个任务,那么我认为你只需要一个 Activity 和许多 Fragment(如果需要)。当然,你可以在未来的 Activity 中重用这些 Fragment,以解决其他任务。这种方法会清晰地、逻辑上地分离任务。你也无需维护一个 Activity,根据不同的 Fragment 集合定义不同的 Intent 过滤器参数。你应该根据需求在开发过程的设计阶段定义任务。


在我们的应用程序中,Activity 的一个任务是持有导航抽屉以进入不同的片段。 :) 为什么我要为片段使用意图?对于全局数据,持有静态引用到“全局”数据类并将某些值传递给片段的 create instance 方法是清晰和合理的。 - The incredible Jan

10

你需要注意的是,启动的活动并不会隐式销毁调用活动。当然,你可以设置这样一个场景:用户点击按钮转到一个页面,开始该页面的活动并销毁当前的活动。但这会带来很多开销。我能给你的最好建议是:

** 只有在同时打开主要活动和其他活动都有意义时(考虑多个窗口),才启动新的活动。

Google Drive 是一个很好的例子,它提供了文件浏览器主要活动,当您打开一个文件时,会启动一个新的活动以查看该文件。您可以按最近使用的应用程序按钮返回浏览器而不关闭已打开的文档,然后甚至可以同时打开第二个文档与第一个文档交替。


1
关于“只有在同时打开主活动和新活动时才有意义时才启动新活动(考虑多个窗口)”,我不这么认为。使用片段attach/detach方法可以很好地解决这种情况。 - ToolmakerSteve

9

我做的事情:尽可能使用更少的片段。不幸的是,在几乎所有情况下都会用到,所以我最终得到了很多片段和很少的活动。

  • ActionBar & 菜单:当2个片段具有不同的标题和菜单时,这将很难处理。例如:添加新的片段时,您可以更改操作栏标题,但当从backstack中弹出它时,没有办法恢复旧标题。对于这种情况,您可能需要在每个片段中添加一个工具栏,但请相信我,这将花费更多时间。
  • 当我们需要startForResult时,活动有,但片段没有。
  • 默认情况下没有转换动画。

我的解决方案是使用一个活动来包装一个碎片。因此,我们拥有单独的操作栏、菜单、startActivityForResult、动画等。


3
非常有用的建议,谢谢。您能否澄清“将片段包装在活动中”是什么意思?您为每个片段创建了单独的活动吗?如果是这样,那么是否需要Fragment呢?答:非常有用的建议,感谢。 “将片段包装在活动中”是指创建一个Activity来容纳Fragment。是的,为每个Fragment创建了单独的Activity,但仍需要Fragment来在Activity中显示UI元素并处理生命周期事件。 - ToolmakerSteve
3
有一种方法可以恢复标题和其他内容。使用getSupportFragmentManager().addOnBackStackChangedListener添加一个监听器。在该监听器中获取当前片段,然后设置标题和其他内容。 - babay

6
Fragment相对于Activity的一个重要优势在于,用于Fragment的代码可以用于不同的活动中。因此,在应用程序开发中,它提供了代码的可重用性

3
当然,我可以为您提供一些例子来说明。 - sofs1
1
@sofs1 你的问题不太合理。无论从哪个活动实例化片段,片段中的任何代码都保持不变。 - The incredible Jan
1
@TheincredibleJan 但是我们也可以说:“无论第二个活动是从哪个活动实例化而来,任何活动中的代码都保持不变。”我看不出有什么区别。 - iforce2d

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