php和大象_Android开发人员的美丽故事,多个活动和束缚的大象

php和大象

We are often bound to do certain things out of habit, based on what we’ve learned, or have trained ourselves to do over time. A day-by-day routine, we might rely on the same pattern over and over again, each day, because that’s what we did yesterday. There’s a good chance we’ll do the same thing tomorrow.

我们经常会根据所学知识或训练自己的工作习惯,习惯性地做某些事情。 在日常工作中,我们每天可能会一遍又一遍地依靠相同的模式,因为那是我们昨天所做的。 我们明天很有可能会做同样的事情。

But why?

但为什么?

Sometimes, some of our habits aren’t particularly helpful, nor effective. Maybe you’re still spending time on a game despite “end of service” coming in June. Maybe you’re still clicking on /r/androiddev out of habit instead of /r/android_devs.

有时,我们的某些习惯并不是特别有用,也没有效果。 尽管6月“服务终止”到来了,也许您仍在花时间在游戏上。 也许您仍然出于习惯而不是/ r / android_devs来单击/ r / androiddev。

Maybe you’re still using multiple Activities to model multiple screens in your Android application.

也许您仍在使用多个Activity在Android应用程序中为多个屏幕建模。

到底什么是活动? (What is an Activity anyway?)

As originally outlined in the “how should I model my Android application?” document by Dianne Hackborn on the now-defunct Google+,

正如最初在“我应该如何为我的Android应用程序建模?”中概述的那样 Dianne Hackborn在现已失效的Google+上的文档

Activity is the entry into an application for interacting with the user. From the system’s perspective, the key interactions it provides with the app are:

活动是进入与用户交互的应用程序的入口。 从系统的角度来看,它与应用程序提供的主要交互是:

• Keep track of what the user currently cares about (what is on screen) to ensure the process hosting that is kept running.• Know that previously used processes contain things the user may return to (stopped activities), and thus more highly prioritize keeping those processes around.• Help the application deal with the situation where its process is killed so the user can return to activities with their previous state restored [to the state they had before they were stopped].

•跟踪用户当前所关心的内容(屏幕上显示的内容),以确保继续运行的进程托管。•知道以前使用的进程包含用户可能返回的内容(停止的活动),因此优先考虑保持•帮助应用程序处理其进程被杀死的情况,以便用户可以恢复到先前状态(恢复到停止之前的状态)的活动。

An Activity is a top-level OS component that serves as an entry point into an Android application, launched by the OS in response to a given type of “intent” (that may or may not contain data), and is associated with a Window so that we can show a layout in its content frame.

活动是顶级OS组件,用作Android应用程序的入口点,由OS响应给定类型的“意图”(可能包含或不包含数据)启动,并与Window关联。这样我们就可以在其内容框架中显示一个布局。

Even the very first “Hello world!” tutorial you see ends with “how do I start a second Activity?”. You’ve learned that this is the way to do things on Android.

甚至是第一个“ Hello world!” 您看到的教程以“如何开始第二个活动?”结尾 。 您已经了解到这是在Android 上执行操作的方法

It is definitely one particular way to do things. But is it really the best way?

这绝对是一种特殊的做事方式。 但这真的是最好的方法吗?

身份验证流程的奥秘 (The mystery of the authentication flow)

If you look around and see what developers think about “single-activity applications”, you may find something along the lines of this:

如果环顾四周,看看开发人员对“单一活动应用程序”的看法,您可能会发现一些类似的东西:

“I still like using two activities. One for login and startup authentication then one for the actual app.”

“我仍然喜欢进行两次活动。 一种用于登录和启动身份验证,另一种用于实际应用。”

Definitely an improvement over “an Activity per Screen”, especially if you wish to avoid complexities that arise from concepts such as “task reparenting” or “intent flags”, and instead prefer to have more control over your own application’s behavior.

绝对是对“每个屏幕的活动”的改进,特别是如果您希望避免由于“任务重定”或“意图标志”之类的概念而引起的复杂性,而是希望对您自己的应用程序的行为进行更多控制。

After all, one could argue that you still reap most benefits of a Single-Activity application, if you can ensure that there is only ever 1 Activity on the task stack at the same time.

压脚提升所有,人们可以说,你还是收获一个单活动应用的最大效益,如果你能保证有永远只1在同一时间任务堆栈上的活动。

But what makes the authentication flow so special that it would need its own OS-level entry point? Can it be started by other applications directly via a special intent? Is it an entirely separate flow exposed to other applications (like ACTION_SHARE, or taking a picture with a camera), independent from the regular flow of the application? Is it supposed to be an independent screen that can render itself even in PIP? Does the OS need to care that this new entry point exists?

但是,是什么使身份验证流程如此特别 ,以至于需要自己的OS级入口点呢? 可以由其他应用程序通过特殊意图直接启动它吗? 它是完全独立于其他应用程序(例如ACTION_SHARE或使用相机拍照)的流,而不依赖于应用程序的常规流吗? 它应该是即使在PIP中也可以呈现的独立屏幕吗? 操作系统是否需要关心这个新的入口点是否存在?

If not, (and the answer is “not”), then there can only be one reason: habit.

如果没有(答案为“否”),那么只能有一个原因: 习惯

To model an application differently, we have to abandon the old chains that latch onto us from outdated tutorials, tutorials that preach outdated practices, and the practices we feel compelled to follow even when there are other alternatives.

为了对应用程序进行不同的建模,我们必须放弃过时的教程过时的实践教程以及即使有其他替代方法也 必须遵循的实践中存在的束缚

你还能做什么? (What else can you do?)

As also outlined in the original “how should I model my Android application?” docs:

正如最初的“我应该如何为我的Android应用程序建模?”中概述的那样 docs:

Once we have gotten in to this entry-point to your UI, we really don’t care how you organize the flow inside. Make it all one activity with manual changes to its views, use fragments (a convenience framework we provide) or some other framework, or split it into additional internal activities. Or do all three as needed. As long as you are following the high-level contact of activity (it launches in the proper state, and saves/restores in the current state), it doesn’t matter to the system.

一旦进入了您的UI的这个入口点,我们就真的不在乎您如何组织内部流程了。 通过手动更改其视图,使用片段(我们提供的便捷框架)或某些其他框架,或将其拆分为其他内部活动,使所有活动都变为活动。 或根据需要执行所有三个操作。 只要您跟踪活动的高层联系(活动以适当的状态启动,并以当前状态保存/恢复),对系统就没有关系。

Which says the system doesn’t care how you organize your flows. But you probably do!

这表示系统不在乎您如何组织流程。 但是你可能会的!

The article outlines some options:

本文概述了一些选项:

  • Single activity with views

    单一活动的景观
  • Single activity with fragments

    单个活动有碎片
  • Single activity with another framework (Conductor, RIBs, Shards, etc.)

    与另一个框架(导体,RIB,碎片等)的单一活动
  • Multiple activities if really needed

    如果确实需要进行多项活动

There had been attempts to make creating single-activity applications easy since early 2014 especially by Square (advocating for Views instead of Fragments using Flow and Mortar, some shiny libraries of the time, though not maintained nor popular anymore in their original form).

自2014年初以来,人们一直在尝试使创建单一活动的应用程序变得容易,尤其是Square(倡导使用Flow和Mortar 代替Views而不是Fragments ,这是当时的一些闪闪发光的库,尽管不再以其原始形式维护或流行)。

In Droidcon NYC 2017, Jake Wharton explicitly says:

在Droidcon NYC 2017中, 杰克·沃顿 ( Jake Wharton) 明确表示

”One Activity for the whole app. You can use Fragments, just don’t use the backstack of Fragments, because it’s bad.” — Jake Wharton

”整个应用的一项活动。 您可以使用Fragments,只是不要使用Fragments的堆栈,因为这很糟糕。” 杰克·沃顿

In May of 2018, the Jetpack Navigation Component was announced, and with that, Google’s official recommendation shifted towards single-activity applications as well.

在2018年5月,Jetpack导航组件被宣布,因此Google的官方建议也转向了单一活动应用程序

Today we are introducing the Navigation component as a framework for structuring your in-app UI, with a focus on making a single-Activity app the preferred architecture.

今天,我们引入了Navigation组件作为构建应用内UI的框架,重点是使单活动应用成为首选架构

And Fragments have had long-standing issues fixed, including “slide on top of other fragment” type animations that are now handled by the FragmentContainerView.

片段已修复了长期存在的问题,包括“由其他片段顶部滑动”类型的动画,现在由FragmentContainerView处理。

The Navigation Component offers to hide any and all FragmentTransactions you’d manually need to write, and simplifies access to a NavController from any view.

导航组件可隐藏您需要手动编写的所有FragmentTransactions,并简化了从任何视图对NavController的访问。

Yet, people still wonder “if they should make the plunge”. So what’s the issue?

但是,人们仍然想知道“是否应该投入”。 那是什么问题呢?

是什么使人们无法转向单一活动? (What keeps people from shifting to a Single Activity?)

Activity navigation is extremely intrusive. It’s the “easiest to add”, yet it’s the hardest to remove and replace. Once you have startActivity(intent) or especially startActivityForResult(intent, 0) in your code, it’s really tricky to rip it out.

活动导航极为麻烦。 它是“最容易添加”的,但最难移除和替换。 一旦您的代码中包含startActivity(intent)或尤其是startActivityForResult(intent, 0) ,将其剔除确实很棘手。

In multi-Activity applications, Fragments communicate through their Activity, commonly directly cast the “one kind of Activity” that can host them. The Activity exists as the shared superscope for Fragments, and they are all coupled. What if we wanted to re-use the Fragments, without their host? They depend on an Activity to handle their communication!

在多活动应用程序中,片段通过其活动进行通信,通常直接转换可承载它们的“一种活动”。 活动作为片段的共享超级作用域存在,并且它们都耦合在一起。 如果我们想在没有主机的情况下重新使用这些片段该怎么办? 他们依靠活动来处理他们的交流!

So by opting into using multiple Activities, we’re opting to move towards a rigid architecture that is hard to change. Let’s say I need these Fragments in a ViewPager with tabs. Now I have to move all the logic from the Activity to a Fragment, move all those Fragments to be child fragments, and make the Activity host be the parent Fragment instead (replace any call to activity with parentFragment). There’s a lot of moving parts, it’s easy to break things. Imagine doing that for each Activity-Fragment relation. It’s easier not to do it.

因此,通过选择使用多个活动,我们选择朝着难以更改的刚性架构迈进。 假设我需要在带有标签的ViewPager中使用这些片段。 现在,我必须将所有逻辑从“活动”移到“片段”,将所有这些“片段”移为子片段,然后使“活动”宿主成为父片段(用parentFragment替换对activity任何调用)。 有很多活动部件,很容易折断东西。 想象一下,为每个Activity-Fragment关系执行此操作。 不这样做比较容易。

What if we just didn’t get into this mess in the first place?

如果我们一开始就没有陷入困境,那该怎么办?

共享范围,结果回调和简化的导航 (Shared scopes, result callbacks, and simplified navigation)

There are actually multiple approaches to creating shared scopes between screens, and simplifying navigation. Shared scopes can serve as a “subscope” between screens, without relying on the Activity itself to share data or events between them.

实际上,有多种方法可以在屏幕之间创建共享范围并简化导航。 共享范围可以充当屏幕之间的“子范围”,而无需依靠活动本身在它们之间共享数据或事件。

Views are inherently nestable (but are not easily lifecycle-aware), Fragments can be nested, and Jetpack Compose will support nesting of scopes through ambients, but currently existing frameworks like either RIBs (by Uber, then Badoo), or the navigation library I maintain (Simple-Stack), or Jetpack Navigation (2.2.0+) can build explicit shared scopes.

视图本质上是可嵌套的(但不容易感知生命周期),可以嵌套片段,并且Jetpack Compose将支持通过环境嵌套作用域,但是当前存在的框架,例如RIB (由Uber,然后由Badoo)或导航库I维护( Simple-Stack )或Jetpack Navigation (2.2.0+)可以建立显式的共享范围。

Simple-Stack provides “scoped services”, while Jetpack Navigation provides support for “NavGraph-scoped ViewModels” (using the NavBackStackEntry of a <navigation block as the ViewModelStoreOwner and SavedStateRegistryOwner of a ViewModel with a SavedStateHandle).

简单协议栈提供“作用域服务”,而Jetpack的导航提供了一种用于“NavGraph作用域的ViewModels”(使用支撑NavBackStackEntry一个的<navigation块作为ViewModelStoreOwnerSavedStateRegistryOwner一个的ViewModelSavedStateHandle )。

Once you have shared scopes, result callbacks are fairly straightforward. Just put a pending result in your shared scope, and read it on the previous screen as you navigate back (BehaviorRelay, MutableLiveData, EventEmitter, LinkedListChannel, etc — or maybe just a regular mutable field, saved/restored into Bundle, potentially using the SavedStateHandle).

共享范围后,结果回调将非常简单。 只需将待处理的结果放入您的共享范围中,然后在导航时返回上一个屏幕(BehaviorRelay,MutableLiveData,EventEmitter,LinkedListChannel等),或者只是一个常规可变字段,可以将其保存/恢复到Bundle中,可能使用SavedStateHandle )。

No need for result codes, bundles, or inter-process communication (IPC), as the shared scope is guaranteed to exist even on back navigation.

不需要结果代码,数据包或进程间通信(IPC),因为即使在向后导航时也可以保证存在共享范围。

And once you have any of these frameworks in place, navigation should be sufficiently abstracted away that all you need to care about is either backstack.goTo(SomeScreen()), or navController.navigate(SomeGraphDirections.someScreen()).

一旦有了这些框架中的任何一个,导航就应该足够抽象,您只需要关心backstack.goTo(SomeScreen())navController.navigate(SomeGraphDirections.someScreen())

Isn’t that easier than FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK, or FLAG_ACTIVITY_BROUGHT_TO_FRONT | FLAG_ACTIVITY_SINGLE_TOP | FLAG_ACTIVITY_REORDER_TO_FRONT?

这比FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK容易吗? FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASKFLAG_ACTIVITY_BROUGHT_TO_FRONT | FLAG_ACTIVITY_SINGLE_TOP | FLAG_ACTIVITY_REORDER_TO_FRONT FLAG_ACTIVITY_BROUGHT_TO_FRONT | FLAG_ACTIVITY_SINGLE_TOP | FLAG_ACTIVITY_REORDER_TO_FRONT FLAG_ACTIVITY_BROUGHT_TO_FRONT | FLAG_ACTIVITY_SINGLE_TOP | FLAG_ACTIVITY_REORDER_TO_FRONT吗?

您何时仍需要进行多个活动? (When do you still need multiple Activities?)

There are actually a few perfectly valid use-cases for using multiple Activities, where you don’t really have other options.

实际上,有几个使用多个Activity的完全有效的用例,而您实际上没有其他选择。

Multi-window using both panels in a split-screen scenario, or multi-process applications.

在分屏方案中使用两个面板的多窗口或多进程应用程序。

It can also make sense for flows that are independent of the main application and are exposed only for other apps, such as ACTION_SHARE, or for the upcoming Bubble API.

对于独立于主应用程序且仅对其他应用程序(例如ACTION_SHARE或即将发布的Bubble API)公开的流,它也很有意义。

But you generally don’t need them just to show a second screen.

但是您通常不需要它们仅显示第二个屏幕。

单活动应用程序是什么样的? (What does a Single-Activity app look like?)

I’ve set up two fairly simple samples that still show the general idea, both based on the original Jetpack Navigation Component “conditional navigation” documentation (that, surprisingly enough, makes no mention of either popUpTo or popUpToInclusive="true", the two <action properties that actually allow you to do conditional navigation).

我已经建立了两个相当简单的示例,它们仍然显示了总体思路,这两个示例均基于原始的Jetpack导航组件“条件导航”文档 (令人惊讶的是,没有提到popUpTopopUpToInclusive="true" ,这两个popUpToInclusive="true" <action实际上允许您进行条件导航的<action属性)。

Nonetheless, the “First Time User Experience” is an easy-to-grasp example for how to NOT use a second activity just to show a login screen.

但是,“首次用户体验”是一个易于掌握的示例,说明了如何仅显示登录屏幕就不使用第二个活动。

Here are the samples:

以下是示例:

class CreateLoginCredentialsFragment : KeyedFragment(R.layout.create_login_credentials_fragment) {
    private val viewModel by lazy { lookup<RegistrationViewModel>() }


    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)


        val binding = CreateLoginCredentialsFragmentBinding.bind(view)
        // ...
    }
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/navigation"/>


</FrameLayout>

(There is also the navigation framework called https://github.com/nhaarman/acorn that promises to fully abstract away Android, but unfortunately, I haven’t taken it for a spin. It’s still definitely worth taking a look at, though.)

(还有一个名为https://github.com/nhaarman/acorn的导航框架,它有望完全抽象出Android,但不幸的是,我并没有对此进行过尝试。尽管如此,它仍然绝对值得一看。 )

结论 (Conclusion)

I hope this article provides some insight on how using a Single Activity can in most cases easily replace what “convenience” a Multi-Activity approach seemingly offers (that being “simpler scoping”, and “lower friction navigation”), without forcing us into one particular pattern that later results in trickier navigation between screens, and otherwise noticeably slower screen transitions or flickering animations.

我希望本文能提供一些见识,使人们了解在大多数情况下使用单一活动如何能够轻松地替代看似多活动方法所提供的“便利”(即“简单的范围”和“较低的摩擦导航”),而不必强迫我们一种特定的模式后来会导致屏幕之间的导航更加棘手,否则屏幕过渡或动画闪烁会明显变慢。

Why let the system own the state of our app, if we could own it ourselves? Why make IPC roundtrips and ask the system to open a new Window, if we could just swap two views in our layout? Why complicate the lifecycle and make onStop ambiguous, or duplicate views just to share them between screens?

如果我们可以自己拥有它,为什么还要让系统拥有我们应用程序的状态? 如果我们只能在布局中交换两个视图,为什么还要进行IPC往返并要求系统打开一个新窗口? 为什么生命周期复杂化化妆 o nStop 暧昧,或重复的观点只是在屏幕间共享

Why get stuck in old habits, when you can choose to make reasoning about your application state easier?

当您可以选择简化应用程序状态的推理时,为什么还要习惯旧习惯?

Using Activities to model inter-dependent screen flows is like the chained elephant tied down to a stake, no longer aware that it could be free. Free of Activity intent flags, and unpredictable task stack behavior. But instead, frolicking in the world of shared scopes that still provide proper state persistence across process death (low memory condition).

使用“活动”来建模相互依存的屏幕流,就像被束缚在桩上的束缚大象一样,不再意识到它可能是免费的 。 没有活动意图标志和不可预测的任务堆栈行为。 但是,取而代之的是,在共享作用域的世界中嬉戏,这些作用域仍然在进程死亡(低内存条件)之间提供适当的状态持久性。

Special thanks to anemomylos for showing me The Beautiful Story of the Chained Elephant, which served as an inspiration for this article.

特别感谢anemomylos向我展示了《锁链大象的美丽故事》,这是本文的启发。

Join the discussion thread on /r/android_devs.

加入/ r / android_devs上的讨论线程

翻译自: https://medium.com/swlh/the-beautiful-story-of-android-developers-multiple-activities-and-the-chained-elephant-2a3083a9cb19

php和大象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值