作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Becze Szabolcs的头像

Becze Szabolcs

Becze是一位才华横溢的自由软件工程师, 他之前曾在Garmin等公司担任Android开发人员.

Previously At

Garmin
Share

多年来,我在Android中看到了许多不同的导航模式实现. 一些应用程序只使用Activities, 而其他活动则与片段和/或自定义视图混合在一起.

我最喜欢的导航模式实现之一是基于“一个活动-多个片段”的理念, 或者简单的片段导航模式, 应用程序中的每个屏幕都是一个全屏片段,并且所有或大部分这些片段都包含在一个活动中.

这种方法不仅简化了导航的实现方式, 但它的性能要好得多,因此提供了更好的用户体验.

在本文中,我们将研究Android中一些常见的导航模式实现, 然后介绍了基于片段的导航模式, 与他人进行比较和对比. 实现此模式的演示应用程序已上载到 GitHub.

活动世界

一个典型的Android应用程序只使用活动,它被组织成一个树状结构(更准确地说是一个有向图),其中根活动由启动器启动. 当你在应用程序中导航时,有一个由操作系统维护的活动回栈.

一个简单的例子如下图所示:

Fragment image 1

活动A1是我们应用程序中的入口点(例如, 它代表启动屏幕或主菜单),用户可以从它导航到A2或A3. 当需要在活动之间进行通信时,可以使用 startActivityForResult () 或者您可以在它们之间共享一个全局可访问的业务逻辑对象.

当你需要添加一个新的Activity时,你需要执行以下步骤:

  • 定义新活动
  • 将其注册到 AndroidManifest.xml
  • Open it with a startActivity() 从另一个活动

当然,这个导航图是一个相当简单的方法. 当你需要的时候,它会变得非常复杂 操作后栈 或者当您必须多次重用相同的活动时, 例如,当你想引导用户浏览一些教程屏幕,但每个屏幕实际上都使用相同的活动作为基础时.

幸运的是,我们有一个工具叫做 tasks 还有一些指导方针 正确的叠后导航.

然后,随着API级别11的出现,出现了碎片……

碎片世界

Android在Android 3中引入了fragments.0 (API level 11), 主要是为了在大屏幕上支持更动态和灵活的UI设计, such as tablets. 因为平板电脑的屏幕比手机大得多, 有更多的空间来组合和交换UI组件. Fragments允许这样的设计,而不需要管理视图层次结构的复杂更改. 通过将活动的布局划分为片段, 您可以在运行时修改活动的外观,并将这些更改保存在由活动管理的回栈中. ——引自谷歌 Fragments API指南.

这个新工具允许开发人员构建多窗格UI,并在其他活动中重用组件. 有些开发人员喜欢这个,而另一些则喜欢 don’t. 是否使用片段是一个流行的争论, 但我认为每个人都会同意片段带来了额外的复杂性,开发者真的需要理解它们才能正确地使用它们.

Android的全屏碎片噩梦

我开始看到越来越多的例子,这些碎片不仅仅代表了屏幕的一部分, 但事实上,整个屏幕是包含在一个活动中的一个片段. 有一次我甚至看到一个设计,其中每个活动只有一个全屏片段,而这些活动存在的唯一原因就是承载这些片段. 除了明显的设计缺陷之外,这种方法还有另一个问题. 请看下面的图表:

Fragment image 2

A1如何与F1沟通? A1完全控制了F1,因为它创造了F1. 例如,A1可以在创建F1时传递一个bundle,也可以调用它的公共方法. F1如何与A1沟通? 这个更复杂, 但它可以通过回调/观察者模式解决,其中A1订阅F1, F1通知A1.

但是A1和A2如何相互通信呢? 这个已经讨论过了,例如通过 startActivityForResult ().

现在真正的问题来了:F1和F2如何相互沟通? 即使在这种情况下,我们也可以拥有全局可用的业务逻辑组件, 所以它可以用来传递数据. 但这并不总能带来优雅的设计. 如果F2需要以更直接的方式传递一些数据给F1怎么办? Well, 使用回调模式,F2可以通知A2, 然后A2得到一个结果,这个结果被A1捕获并通知F1.

这种方法需要大量的样板代码,很快就会成为bug、痛苦和愤怒的根源.

如果我们可以摆脱所有的活动,只保留其中一个,保留其余的片段会怎么样?

片段导航模式

多年来,我开始在我的大多数应用程序中使用“一个活动-多个片段”模式,并且我仍然在使用它. 例如,有很多关于这种方法的讨论 here and here. 然而,我错过的是一个具体的例子,我可以看到和测试自己.

让我们看一下下面的图表:

Framgnet image 3

现在我们只有一个容器活动,我们有多个片段,它们也是树状结构. 控件处理它们之间的导航 FragmentManager,它有它的后栈.

注意,现在我们没有 startActivityForResult () 但是我们可以实现一个回调/观察者模式. 让我们来看看这种方法的优缺点:

Pros:

1. 更干净,更易于维护 AndroidManifest.xml

现在我们只有一个活动, 我们不再需要在每次添加新屏幕时更新清单. 与活动不同,我们不需要声明片段.

这似乎是一件小事, 但是对于拥有50多个活动的大型应用程序,这可以显著提高页面的可读性 AndroidManifest.xml file.

查看示例应用程序的清单文件,它有几个屏幕. 清单文件仍然非常简单.


   package="com.exarlabs.android.fragmentnavigationdemo.ui" >
   
       
           
               
               
           
       
   

2. 集中导航管理

在我的代码示例中,您将看到我使用 NavigationManager 在我的情况下,它被注射到每个碎片中. 这个管理器可以用作日志记录的集中场所, 后台堆栈管理等, 因此,导航行为与业务逻辑的其余部分是分离的,而不是分散在不同屏幕的实现中.

让我们想象一下这样一种情况,我们希望开始一个屏幕,用户可以从一个人员列表中选择一些项目. 您还希望传递一些过滤参数,如年龄、职业和性别.

在活动的情况下,你会写:

Intent = new Intent();
intent.putExtra(“年龄”,40);
intent.putExtra(“占领”,“开发人员”);
intent.putExtra(“性别”,“女性”);
startActivityForResult(意图,100);

然后你要定义 onActivityResult 在下面某个地方处理结果.

@Override
onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
}

我个人对这种方法的问题是,这些论点是“额外的”,它们不是强制性的, 所以我必须确保当一个额外的人失踪时,接收活动处理所有不同的情况. 稍后,当进行一些重构时,不再需要额外的“age”, 然后我必须在我开始这个活动的代码中到处搜索,并确保所有额外的都是正确的.

此外,如果结果(人员列表)以_List的形式到达不是更好吗_而不是必须反序列化的序列化形式?

在基于片段的导航情况下,一切都更直接. 您所要做的就是在 NavigationManager called startPersonSelectorFragment () 使用必要的参数和回调实现.

mNavigationManager.startPersonSelectorFragment(40,“开发者”,“女性”,
      新PersonSelectorFragment.OnPersonSelectedListener () {
          @Override
          public boolean onPersonsSelected(List selection) {
       [do something]
              return false;
          }
      });

Or with RetroLambda

mNavigationManager.startPersonSelectorFragment(40,“开发者”,“女性”, selection -> [do something]);

3. 屏幕之间更好的交流方式

在活动之间,我们只能共享一个可以保存原语或序列化数据的Bundle. 现在有了片段,我们可以实现一个回调模式,例如, F1可以监听F2传递任意对象. 请查看前面示例的回调实现,它返回一个_List_.

4. 构建碎片比构建活动更便宜

当你使用一个有5个菜单项的抽屉时,这一点就变得很明显了,在每个页面上,抽屉都应该再次显示.

在纯活动导航情况下, 每个页面都应该膨胀并初始化抽屉, 哪个当然很贵.

在下面的图表中,您可以看到几个根片段(FR*),它们是可以从抽屉中直接访问的全屏片段, 而且这个抽屉只有在这些碎片被展示出来的时候才能打开. 图中虚线右侧的所有内容都是任意导航方案的示例.

Framgnet image 4

因为容器活动保存了抽屉, 我们只有一个抽屉实例,所以在每个导航步骤中,抽屉应该是可见的 不必再次膨胀和初始化它. 我还是不明白这些是怎么运作的? 看看我的示例应用程序,它演示了抽屉的用法.

Cons

我最大的恐惧一直是,如果我在项目中使用基于片段的导航模式, 在这个过程中,我可能会遇到一个不可预见的问题,这个问题很难解决,因为它增加了碎片的复杂性, 第三方库和不同的操作系统版本. 如果我必须重构到目前为止所做的一切呢?

事实上,我必须解决问题 nested fragments,第三方库也使用片段,如 ShinobiControls, ViewPagers and FragmentStatePagerAdapters.

我必须承认,获得足够的片段经验以解决这些问题是一个相当漫长的过程. 但在每一个案例中,问题并不是哲学不好, 但我没有很好地理解其中的片段. 也许如果你比我更了解碎片,你甚至不会遇到这些问题.

我现在唯一能提到的缺点是,我们仍然会遇到一些问题,这些问题很难解决,因为没有成熟的库可以展示基于片段导航的复杂应用程序的所有复杂场景.

Conclusion

在本文中,我们看到了在控件中实现导航的另一种方法 Android application. 我们将它与使用活动的传统导航理念进行了比较,并且我们已经看到了使用它优于传统方法的几个很好的理由.

如果您还没有,请查看上传到 GitHub implementing. 请随意分叉或贡献一些更好的示例来更好地展示它的用法.

就这一主题咨询作者或专家.
Schedule a call
Becze Szabolcs的头像
Becze Szabolcs

Located in 克卢日-纳波卡,克卢日县,罗马尼亚

Member since November 7, 2015

About the author

Becze是一位才华横溢的自由软件工程师, 他之前曾在Garmin等公司担任Android开发人员.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Previously At

Garmin

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

Toptal Developers

Join the Toptal® community.