Flutter: 应用程序被操作系统杀死时不保存实例状态

创建于 2016-11-12  ·  139评论  ·  资料来源: flutter/flutter

什么是实例状态,它为什么存在

在 Android 上,系统可以随时杀死Activity 。 当您的 Activity 不在前台时,或者由于未处理的配置更改(例如区域设置更改)而导致 Android 需要内存时,通常会发生这种情况。
为了避免用户在 Android 杀死 Activity 时必须重新启动他所做的事情,系统会在 Activity 暂停时调用onSaveInstanceState(…) ,应用程序应该将其数据保存在Bundle ,并在任务恢复时在onCreate(…)onRestoreInstanceState(…)传递保存的包,如果活动已被系统终止。

颤振中关于它的问题

在我尝试过的 Flutter 应用程序(Flutter Gallery,以及带有 FAB 点按计数器的基础项目)中,如果我打开足够多的应用程序让 Android 杀死 Flutter 应用程序的 Activity,那么当我回到 Flutter Activity 时所有状态都会丢失(而没有从最近的任务中删除任务)。

重现步骤

  1. 安装Flutter Gallery
  2. 打开设备的设置,然后在开发者选项中,打开“不要保持活动”。 (见截图)
    dev_option
    这将允许模拟 Android 何时终止活动,因为它缺乏内存并且您不在前台。
  3. 打开 Flutter Gallery 应用程序并转到主屏幕以外的任何地方。
  4. 按设备的主页按钮转到启动器。
  5. 按概览按钮并返回 Flutter Gallery。 这是错误。

预期结果:该应用程序处于与我们离开时相同的状态,UI 未受影响。
会发生什么:活动从头开始重新启动,丢失所有 UI 状态,甚至是非常长的表单。

P2 annoyance crowd routes framework new feature

最有用的评论

快速更新:我目前正在积极探索此问题的解决方案。

所有139条评论

@eseidelGoogle没错。 如果可以在当前版本中保存 flutter Activity 状态,它可以以哪种格式保存?

现在我们不做任何事情来保存任何东西。

框架本身几乎没有值得保存的状态——它都是动画和类似的东西——所以我们可能总是把这留给应用程序来做。 不过,我们可能应该在 Dart 级别公开它。 (现在它只在 Java 级别公开。)

@Hixie在Android上,所有框架Views的状态都由系统自动保存和恢复,只需要开发者手动保存实例状态的非UI部分。 不应该对所有 UI 小部件以相同的方式工作,以防止开发人员每次都必须编写所有样板来保存用户所在的位置、滚动位置、某些按钮的启用状态、前一屏幕的状态等等在…?

在 Flutter 中,要保存的框架状态非常少。 例如,按钮的启用状态不是状态,而是应用程序提供的输入。 当前路由历史存储在框架中,但不存储在框架可以重建的状态中(因为它是应用程序代码提供的所有对象实例)。 滚动位置是我们唯一可以实际存储的东西(我们目前确实将它保存在商店中,只是没有在应用程序中幸存下来)。

但无论如何,我们绝对应该比今天做得更好。

作为一名经验丰富的 Android 开发人员,我也想参与与 iOS 开发人员的对话,以便在讨论时 Flutter 可以成为构建 iOS 和 Android 应用程序的完美框架。
我认为框架的持久性能力对于以对开发人员最友好的方式保存数据、首选项、缓存数据和状态可能很重要。

哇,我没有意识到是这种情况? 这实际上很关键,值得一提的是,在内存较少的设备上,该进程很容易被杀死!

如何通过onSaveInstanceState serialization.dartPageStore (或相关的)保存为字节流?

@Takhion你能链接你提到的“PageStore”吗? 我有兴趣尝试解决此问题,因为它似乎不会很快得到修复(恕我直言,2029 年 12 月 31 日还很遥远)

@LouisCAD确定它在这里: https :

不过,我认为您需要节省更多:至少是当前的路线,也许还有一些州?
你怎么看@Hixie?

我们目前没有做任何事情来使这变得容易。 我们还没有详细研究这个问题。 目前我建议手动存储您想要保留的信息,并在应用程序恢复时重新应用它。

我们是否将必要的生命周期事件(“你即将被杀死!”、“恭喜,你现在恢复了”)暴露在 Dart 代码中,以便开发人员可以处理持久状态? 这似乎是解锁开发人员探索解决方案的足够能力。 想法?

@sethladd仅将其公开给开发人员是不够的,恕我直言。 Flutter 也需要保存 UI 状态,开发人员不必为每个应用程序重复手动保存 UI 状态,并使用脏且难以维护的冗长样板代码。

@LouisCAD感谢您的反馈。 我在考虑步骤……第一步是什么? 我们是否已经公开了生命周期事件?

@sethladd我能找到的就是这个,它没有公开任何用于保存/恢复实例状态的回调

@Hixie你在 #3427 中提到我们确实有生命周期的钩子。 您的意思是https://docs.flutter.io/flutter/widgets/WidgetsBindingObserver-class.html吗?

Flutter 是否默认使用多个活动? (例如,如果您转到新屏幕)。

进程死亡相当普遍,用户点击主页按钮并启动其他一些内存/CPU 密集型应用程序,您的应用程序将在后台被杀死(或者您的应用程序在后台运行时间过长)。 它是 Android 操作系统不可或缺的一部分 - 每个屏幕(活动)都应该能够保持并恢复其状态,并且可以在 onStop() 之后随时终止该进程。

如果用户返回到已终止的应用程序,Android 将重新创建活动的后台堆栈,首先创建顶部 Activity,然后如果您返回后台堆栈历史记录,则按需重新创建后台堆栈中的活动。 如果 Flutter 使用多个活动(不确定是否使用),这可能是一个更大的问题。

这意味着如果您没有实现状态保存,那么系统将重新创建活动,但其他所有内容都会丢失(进程被终止),从而导致不一致和崩溃。

我写了一篇关于 Android 上进程死亡的文章https://medium.com/inloop/android-process-kill-and-the-big-implications-for-your-app-1ecbed4921cb

此外,没有(干净的)方法可以防止 Android 在超过onStop()状态后终止您的应用程序(在单击 Home 之后或者如果该应用程序不是当前的前台应用程序)。 所以你必须以某种方式处理它。 默认的 Android 小部件将它们的状态(例如在 EditText 中输入的文本)自动保存到 Bundle 实例中。 您的 Activity 将通过调用onSaveInstanceState(Bundle)通知您有必要存储您的状态。 因此,也许 Flutter 应该能够将这个onSaveInstanceState回调从 Activity 转发到您的“屏幕”。 您将在活动中的onCreate(Bundle savedInstanceState)onRestoreInstanceState(Bundle savedInstanceState)生命周期回调中获得具有保存状态的Bundle

所以回顾一下 - Flutter 可能会将onSaveInstanceState()onRestoreInstanceState()回调转发给开发人员。 理想情况下,您还可以将 Android Bundle 对象包装成也可以在 Flutter 中使用的对象。 下一步是屏幕内的所有 Flutter 小部件也将收到有关这些回调的通知,并使用它们来保持其当前状态。
然后,Android 操作系统将获取 Bundle 并将其实际保存在磁盘上,以便在进程被终止并可以再次反序列化时它不会丢失。

祝你好运:-)。 请注意不要向 Flutter 介绍太多这种 Android 状态/生命周期地狱。

我不确定它在 iOS 上是如何工作的 - 但我认为有一些类似的东西,但使用它并不是“必要的”(?)。

生命周期回调对我来说没问题。 我只会存储/加载序列化的 redux 状态。

感谢所有反馈! @Takhion也愿意在这里提供帮助。 也许 API 设计和库是一个好的开始? 如果该库有效,我们可以寻求更正式的集成。 此外,该库将帮助确定我们需要在低级引擎上做什么(如果有的话)。 基本上:具体的 API 提案是什么?

Flutter 是否默认使用多个活动? (例如,如果您转到新屏幕)

@DanielNovak Flutter 使用它自己的“路由”系统,默认情况下它通过单个活动中的单个视图与 Android 集成。 您可以拥有一个混合的 Android/Flutter 应用程序,因此可能有多个活动和 Flutter 视图,但在这种情况下,您可以直接通过 Android 轻松保存/恢复实例状态。

我只会存储/加载序列化的 redux 状态

@zoechi您可能不想这样做,因为保存的实例状态 Bundle 必须通过 IPC,整个 Android 应用程序状态的硬限制为 1MB。 根据定义,实例状态应该只是允许您重新创建用户正在执行的任何正在进行的活动的相同条件的数据片段,例如:文本输入、滚动位置、当前页面等。其他任何内容都应该保存在磁盘上或从其他状态派生。

Flutter 公开Android onPause 生命周期事件Android onDestroy() 生命周期事件未公开。 我想正确的方法是挂钩 onPause() 事件来存储实例相关的状态。
为了与 Android 的onSaveInstanceState()onRestoreInstanceState()兼容,也许向 Flutter 小部件添加类似的方法是有意义的。
@sethladd @LouisCAD有没有人创建过设计方案?

@raju-bitter onPause() 不是最优的,最好挂入 onSaveInstanceState()。 这是来自 onSaveInstanceState 的 javadoc,其中提到 onPause() 比 onSaveInstanceState 更频繁地被调用(您会比必要时更频繁地触发状态保存,或者根本不需要时):

不要将此方法与活动生命周期回调混淆,例如 onPause(),它总是在 Activity 被放置在后台或正在销毁的途中被调用,或者 onStop() 在销毁之前被调用。 调用 onPause() 和 onStop() 而不是调用此方法的一个示例是当用户从活动 B 导航回活动 A 时:无需在 B 上调用 onSaveInstanceState(Bundle) 因为该特定实例永远不会被恢复,所以系统避免调用它。 调用 onPause() 而不是 onSaveInstanceState(Bundle) 的一个例子是当活动 B 在活动 A 之前启动时:如果活动 A 在活动 A 的生命周期内没有被杀死,系统可能会避免在活动 A 上调用 onSaveInstanceState(Bundle) A 的用户界面的状态将保持不变。

@Takhion

实例状态 Bundle 必须通过 IPC,整个 Android 应用程序状态的硬限制为 1MB

那不是我的问题。
我只需要知道什么时候坚持/恢复,坚持自己在磁盘上对我来说很好。

传播onSaveInstanceState / onRestoreInstanceState是简单的部分:Flutter 包含在一个视图中,并且可以正确访问这些回调。 我想要做的是创建一个设计方案和一个原型,以实际自动序列化某个“存储库”中包含的对象,以便使用它变得轻松(而不是必须手动完成所有操作)。

@Takhion你是我翅膀下的风。 迫不及待想看到提案!

好的,所以我正在为此建立一个 PR 并且我遇到了一个障碍: [ View.onSaveInstanceState ] (或 [ Activity.onSaveInstanceState ],就此而言)需要同步提供已保存的实例状态,并且从我的经验(和这个 [评论])看起来主机/平台消息只能是 async 。 我怀疑这是因为他们使用 Android 的消息队列。

@sethladd @Hixie @eseidelGoogle @jason-simmons 请告诉我有一种简单的方法可以从 Android 到 Flutter 进行单个同步/阻塞调用,该调用不涉及临时 C/C++ 代码,因为 [ dart:jni已退休]?

抄送@mravn-google

平台消息的异步特性来自于 Dart UI 在与用于 Android 平台回调的线程不同的线程上执行的事实。 但是您总是可以通过使用锁存器将异步 API 调用转换为同步调用:

final MethodChannel channel = new MethodChannel(messenger, someName);
final CountDownLatch latch = new CountDownLatch(1);
channel.invokeMethod("someMethod", someArguments, new MethodChannel.Result() {
  <strong i="6">@Override</strong>
  public void success(Object result) {
    // handle successful invocation
    latch.countDown();
  }
  <strong i="7">@Override</strong>
  public void error(String errorCode, String errorMessage, Object errorDetails) {
    // handle failed invocation
    latch.countDown();
  }
  <strong i="8">@Override</strong>
  public void notImplemented() {
    // handle invocation of unimplemented method
    latch.countDown();
  }
});
try {
  latch.await();
} catch (InterruptedException e) {
  // handle interruption
}

如果您需要将值从// handle xxx位置传送到latch.await()返回后运行的代码,您可以在闩锁旁边声明一个final AtomicReference<SomeType>变量(或类似变量)。

谢谢@mravn-google!

@mravn-google 这正是我尝试的第一种方法,但 Android 上的主线程永远挂在await()调用上:(

@Takhion是的,当然。

您始终可以通过使用闩锁将异步 API 调用转换为同步 API 调用

...除非将异步响应传递给您已经在的线程:-/

那么,我们似乎需要为onSaveInstanceState进行某种同步握手。 我们是否知道其他类似的回调需要涉及 Dart 端计算的同步响应? 这将需要通用的同步平台消息交换。 如果没有,我们可以专门为onSaveInstanceState做一些事情。

@mravn-google 不幸的是,Android 是这些讨厌的“同步机会窗口”的_full_,在从方法调用返回之前,一切都需要发生! 在我的脑海中:许多Activity生命周期事件、 Broadcast ReceiverSyncAdapter中的方法等等。老实说,我很惊讶它以前并不是一个更大的问题!

在我看来,如果我们希望 Flutter 真正通过“原生”API 实现功能对等,那么同步消息通信是必须的。

@mit-mit 我的意思是 SystemChannels.lifecycle 和任何使用它的东西,是的。

@sethladd我想是的。

@mravn-google 谢谢! 我已经关闭了 #7560 以支持这个,只是为了消除重复和集中对话。

对于这种平台到 Dart 的通信,我倾向于为MethodChannel添加一个同步对应项。

@mravn-google 进展如何? 我能提供帮助吗?

@Takhion谢谢! 应该很快就有了。 我正在做一个同步方法频道,我希望在一两天内有一个 PR。 一旦它在那里,我会要求你的评论。 我想尽快将新频道连接到onSaveInstanceState以测试在该场景中一切正常。 如果您已经有一个可以贡献的示例应用程序,那就太棒了。

@mravn-google 太棒了! 我没有应用程序,因为我是直接修改框架,但我当然可以提供示例。 顺便说一句,我直接使用BinaryMessages因为在 Android 端不需要编解码器(它只是传递字节),可以吗?

你们真棒。

@Takhion如果您不需要编解码器,直接使用BinaryMessages就可以了。 我会添加一个

void setSynchronousMessageHandler(String channel, ByteData handler(ByteData message))

到那个班级。

@Takhion打击那个。 我们不能只是同步调用 Dart VM 来调用handler 。 所以BinaryMessages将保持不变。 相反,从 Dart 代码的角度来看,一切都将像往常一样异步。 将对BinaryMessenger及其在平台端的实现进行更改。 我来补充

ByteBuffer sendSynchronous(String channel, ByteBuffer message);

支持FlutterView的平台视图代码将通过发送异步平台消息然后等待闩锁来实现。 包含在该平台消息中的响应对象不会向平台线程(现在被阻止)发布回复处理闭包,而是存储回复并释放闩锁。

@mravn-google 对我有用,因为它不会阻止我们从 Dart 端发回数据,即使它是异步调用:+1:
我想如果在 Dart 异步响应中使用Future.value(...)不会有任何优化?

我看到你在这里主要谈论 Android 平台。 但是,实际上,当应用程序因某种原因被杀死时,iOS 也有一些类似的机制来保存/恢复 UI 状态。 例如,当用户切换到不同的应用程序时,当前应用程序被挂起,甚至可能因为内存限制而被杀死。 您可以在官方文档中找到有关此过程的更多信息。

iOS 开发人员希望这个保存/恢复过程几乎是自动的。 我想,如果您的目标是制作真正的跨平台框架,您绝对应该考虑到这一点。

你会认为打算将 Android 定位为一个平台的东西至少会尝试遵守 Activity 合同的基础......

@Zhuinden让我们不要抱怨仍处于测试阶段的东西? :)

其他同步api的有onLowMemoryonTrimMemoryonConfigurationChanged不知道IOS是否也有类似的api或者机制

我们可能不想在 Flutter 上重现有缺陷的 android 保存和恢复机制:由于事务大小限制,您无法将整个状态保存在包中。

在 android 上,您最终拥有多个状态保存/恢复逻辑:

  • 开发者的savedInstanceState,但是你不能在里面存储大对象,比如元素列表
  • 数据库,您可以在其中存储大对象
  • 大多数小部件还管理自己的状态
  • 活动/片段堆栈

开发人员很容易实现混乱和错误的逻辑。 根据我自己的经验,很少能看到正确的状态保存和恢复。

在 Flutter 中,开发者的状态是唯一的事实来源。
开发人员应在此状态发生变化时将其持久化到磁盘中,而不必通过有限的本机平台机制。

当然,您将无法轻松保存滚动位置,但谁在乎呢。
大多数情况下,良好的恢复用户体验意味着用户应该看到与之前相同的屏幕。 现在你已经可以用 Flutter 做到这一点了。

底点:Flutter 方面还有改进的空间,以使状态保存/恢复更少样板(对于 Navigator 之类的东西)。 但我认为它不应该为平台保存/恢复状态提供挂钩。

当然,您将无法轻松保存滚动位置,但谁在乎呢。

每个人? 如果每次我不小心触发方向更改时应用程序都重置其列表,我会非常沮丧。 :p

@Saketme FWIW,Flutter 不会在方向改变时重置滚动位置。

在 Android Go 设备上使用带有一点多任务处理的 Flutter 应用程序应该很有趣! 每次 1GB 或更少的 RAM 承受压力时,进度都会丢失

@Saketme :正如@mravn-google 所说,Flutter 不受像 android 活动这样的配置更改的约束。
唯一需要的状态管理是当应用程序在后台被操作系统杀死时。

@LouisCAD当然状态保存和恢复很重要。
Flutter 开发人员已经可以自己实现大部分逻辑,并且 Flutter 可能应该提供减少样板的方法。 然而,Android 提供的机制存在缺陷,我们可以利用 Flutter 设计更好的东西,而不是侵入 Activity onSaveInstanceState / onRestoreInstanceState

这个问题的现状如何?

考虑聊天应用程序的情况,我在TextField输入长消息。
突然我接到一个电话,一段时间后,操作系统由于内存不足而终止了聊天应用程序。
打完电话后,我回到聊天应用程序。 Flutter 会自动保留并重新创建我输入的消息吗? 或者应用程序必须保留并手动重新创建它?

除了滚动位置和路线,还有哪些“状态”通常不被开发者管理?

@nsreenath任何聊天应用程序都应该每隔几个字符和进入后台时自行保存消息草稿。 您可以键入一条长消息,如果设备因某种原因关机,您不希望在这方面失去进展。 Android 实例状态用于轻量用户输入和进入屏幕的进度

我实际上睡在这个上面,我开始意识到 Flutter 开发人员实际上很容易检测应用程序是否从进程死亡中恢复或者是全新的实例。

很简单,只要他们检查savedInstanceState == null (假设他们在onSaveInstanceState(bundle)中至少放了 1 个项目)。 如果静态布尔标志isFirstInit == falsesavedInstanceState != null ,那么它是在进程死亡之后。

然后他们可以为复杂状态创建任何类型的序列化/反序列化回调,当他们检测到savedInstanceState == null时可以自动清除这些回调。 这样他们只需要持久化一些随机布尔值,但可以管理他们自己的持久化系统。

很简单,只要他们检查savedInstanceState == null(假设他们在onSaveInstanceState(bundle)中至少放了1个项目)。

iOS 上没有savedInstanceStateonSaveInstanceState 。 但是该应用程序仍然可以被操作系统杀死。

考虑一个典型的工作流程:

  • 用户转到应用程序内的特定屏幕
  • 然后用户切换到另一个应用程序
  • 突然聪明的操作系统决定杀死以前的应用程序以保留内存
  • 用户返回到上一个应用程序,但她看到了主屏幕。 不是她决定切换应用程序时打开的屏幕

那么,目前在 Flutter 中保存屏幕历史记录的推荐方法是什么?

PS当然,如果您的应用程序只有一个屏幕,这并不重要:smirk:

@Zhuinden 无论如何,我没有看到很多

a) 聊天示例:
始终保存和恢复以前未发送的文本字段中写入的内容。

b) 长格式示例:

  1. 始终保存内容
  2. 当用户返回表单屏幕时,通知用户有一个已保存但未提交的草稿版本,询问他是否要恢复它。 如果用户手动退出表单(可能询问用户是否要保存草稿)或者应用程序是否在后台被终止,这没有任何区别。

在某些情况下,“全新屏幕”与“进程死亡后的新屏幕”的特定知识很有用,但是当您将应用程序的内容设计为始终由数据库支持时,这种情况可能并不常见。

@storix您需要手动保留当前路由并在应用程序再次启动时恢复它。 一个常见用例的样板太多了,我同意。 不过还是可以的。
另外:许多 Android 开发人员会惊讶于 iOS 应用程序/开发人员很少会为状态恢复而烦恼。 我认识的大多数开发人员甚至都不知道这是一个问题。 猜猜这是只在高端设备上工作的奢侈。

@Zhuinden来自https://github.com/flutter/flutter/issues/6827#issuecomment -335160555的生命周期回调适用于我的聊天应用程序。 每次应用程序切换到后台时,我都会保存一些状态。 另见https://docs.flutter.io/flutter/dart-ui/AppLifecycleState-class.html

我想敦促 Flutter 团队不要将 Android/iOS 生命周期方法公开为持久/恢复 UI 状态的规范方式。 即使在本机应用程序中,它们也很难正确理解和编码,并且 UI 和应用程序状态之间的界限通常很模糊,为每个应用程序提供了不同的机制。 将所有这些不同的 API 发布到 Flutter 中将是一种耻辱(特别是如果将来针对其他平台)。

Flutter 的无状态小部件已经_已经_将 UI 状态的责任从小部件迁移到了应用程序中,因此 UI 状态的持久化/恢复问题越来越多地被持久化/恢复 _app_ 状态所覆盖。 我认为这是一件好事,而不是像在 iOS 和 Android 中那样以不同的方式对待 UI 和 App 状态。

我注意到一些 Flutter 小部件,如 ScrollView 和 TextField,提供了封装小部件动态状态的“控制器”类。 在某些方面,它们充当“纪念品”对象——小部件状态的快照——可以在稍后提供回小部件以将它们的状态恢复到某些先前的配置。 听起来很熟悉。 如果这些“纪念品”易于持久化/恢复,那么应用程序可以将它们作为管理自己状态的一部分负责。 这将结束应用程序/UI 状态的二分法,可以选择加入,并且无需 Flutter 框架提供 UI 状态保存钩子(少即是多!)

换句话说,让它成为开发人员的责任,但尽量让它变得简单。 如果开发人员认为半完成的表单对于 Flutter 能够在调用中持续存在足够重要,那么人们可能会争辩说,它可能足够重要以被视为应用程序状态。 我意识到保持对象的状态会引入版本控制问题,但我认为这是一个更易于管理的问题。

这件事有没有什么进展? 我认为确实需要区分“首次运行”和“恢复”进程状态。 当用户导航回已终止的进程时,您还能如何编程以显示正确的屏幕?

@lukaspili你写道:“唯一需要的状态管理是当应用程序在后台被操作系统杀死时。”

好的,那么路由器堆栈呢,也保存到光盘? 我也不t find proper solution to determine that application was restored. Every time when app goes to background I need to save router stack, app data, state to disc? What if I don使用 Redux 吗?

我认为作为第一步,我们应该给插件保存和恢复状态的可能性。 这是我的建议:#22328

是否有关于此问题的更新?

我面临同样的问题。 我有一个解决方法可以通过 Firebase 实时数据库在此处保存文本输入。 但是该方法非常粗糙,并且通常缺乏可扩展性选项,因为每个用户都将被重定向到同一个数据库。 因此,可以访问 Gmail 帐户的其他所有人都可以使用我的数据。 我曾尝试使用 Redis,但虽然我可以在同一个数据库中为其他用户创建单独的列表,但它始终是通过 POST 方法完成的。 这意味着每次存储新数据时都必须刷​​新视图。 这意味着没有实时视觉效果。 确实是个大故障。

这里有一个更大的问题。 两个月前,我与 @matthew-carroll 进行了一些来回交流,我非常有信心这正在进行中。

我已经开始制作一个处理 this 的库。 我实际上不会推荐使用它,但它是仅 Dart 实现的一种想法。 #6225 让事情变得有点棘手。

我注意到,据报道是用 Flutter 制作的 Google Ads 应用程序在活动重新启动后确实保留了导航状态。 了解它是如何实现的,这将是一个很好的参考,即使它是一种解决方法。

@aletorrado是否 Google Ads 应用程序会保留导航状态,以防用户从 Android/IOS“任务管理器”中丢弃该应用程序,然后再次启动它?

@gitspeak不,它仅在活动被操作系统自动

@aletorrado所以你这可能是唯一的方法......

我实际上尝试进入 Google Ads 应用程序,但您需要有一个活动帐户才能进入第一个屏幕以外的任何屏幕:DI 无法测试它是否真的有效

确实如此。 保证。 它还保留其他状态,如滚动位置。

澄清一些混淆:

  • 是的,Google Ads 应用是用 Flutter 编写的。
  • 作为早期采用者之一,他们不得不为后台执行做一些不寻常的事情。 它们在应用程序启动时(不是活动)创建和维护它们的主要隔离。 然后,他们在创建时将这个隔离传递给 Flutter 视图。
  • 您所观察到的“状态保持”是上述方法的副作用。 当您在 Google Ads 中终止 Activity 时,Flutter 的“UI”不会消亡。 它保存在与应用程序上下文相关的内存中。 当活动重新启动时,它会加载相同的隔离和中提琴,您将恢复您的 UI。

我们不推荐这种方法来解决这个问题,因为它根本不能解决问题。 如果应用程序进程终止,整个状态仍然丢失。 您可以在 Google Ads 中尝试此操作,方法是从“设置”>“应用”中转到并停止该过程。 用户界面将丢失。

可能有一种方法可以在本地保存隔离状态,但需要更多调查,因为我们不确定它的可行性/安全性。

目前,上述解决方案是最好的解决方案:连接本地方法并有选择地保存应用程序的状态,以便您以后可以恢复它。 在 iOS 和 Android 上都支持框架的自动解决方案还很遥远。

您可以在 Google Ads 中尝试此操作,方法是从“设置”>“应用”中转到并停止该过程。 用户界面将丢失。

不,那完全没问题。 强制停止应用程序应该会丢失状态。

不应该被低内存条件终止。

无论哪种方式,如果他们将应用程序中的状态作为内存中的单例保持而不将任何内容持久化到磁盘/捆绑中,那么它将丢失。

很遗憾,我无法在没有帐户的情况下测试应用程序,因为我不会创建 Google Ads 帐户只是为了修补应用程序本身。 :thinking_face:

连接到本地方法并有选择地保存应用程序的状态,以便您以后可以恢复它。

实际上,最好的解决方案是将活动状态持久化到磁盘,并在 Android 本机端检测进程死亡; 如果您检测到进程死亡,请从磁盘重新加载,否则将其清除并从头开始。

我对 Flutter 的导航器了解得不够多,无法告诉您是否可以使用某种setHistorysetBackstack方法重建导航历史记录,就像使用 backstack 或 Conductor 或类似的方法一样。

活动/片段使用 ActivityRecord/FragmentRecord 在内部处理这些内容,因此除此之外不会提及它们。

感谢@mehmetf 的所有这些见解。 这是可悲听到保存的支持/恢复状态是远离正在实施。

也许 flutter_redux 可以帮助序列化一些应用程序状态。

好吧,如果您将 Redux 存储的 blob 持久化到磁盘上。 请注意“进程死亡后无限加载对话框”问题。 :thinking_face:

澄清一些混淆:

其实我比较迷茫..

作为早期采用者之一,他们不得不为后台执行做一些不寻常的事情。 他们在应用程序启动时(不是活动)创建和维护他们的主要隔离。 然后,他们在创建时将这个隔离传递给 Flutter 视图。

这与手头的问题有什么关系? Dart 隔离是否在单独的进程中运行?

您所观察到的“状态保持”是上述方法的副作用。 当您在 Google Ads 中终止 Activity 时,Flutter 的“UI”不会消亡。 它保存在与应用程序上下文相关的内存中。 当活动重新启动时,它会加载相同的隔离和中提琴,您将恢复您的 UI。

我的理解是@aletorrado在进程终止后观察到“状态保持”,可能是当应用程序移到后台时(例如按下主页按钮)。

仅供参考:当一个“活动在运行时死亡”时,它总是跟着终止进程。

我们不推荐这种方法来解决这个问题,因为它根本不能解决问题。 如果应用程序进程终止,整个状态仍然丢失。 您可以在 Google Ads 中尝试此操作,方法是从“设置”>“应用”中转到并停止该过程。 用户界面将丢失。

正如@Zhuinden提到的,这不是一个有效的“状态保持”场景,因此从“任务管理器”中丢弃应用程序

可能有一种方法可以在本地保存隔离状态,但需要更多调查,因为我们不确定它的可行性/安全性。

这不是解决问题的方法。 对于 Android,您应该挂钩 onSaveInstanceState 机制,因为它明确地为那些在进程终止后需要“状态保持”的场景保存 Activity 状态。 我不熟悉 IOS 应用程序生命周期。

目前,上述解决方案是最好的解决方案:连接本地方法并有选择地保存应用程序的状态,以便您以后可以恢复它。 在 iOS 和 Android 上都支持框架的自动解决方案还很遥远。

在运行时决定终止进程的情况下,没有描述解决方案,而是承认适当的 Android 设施来保留状态。

在运行时决定终止进程的情况下,没有描述解决方案,而是承认适当的 Android 设施来保留状态。

我有点描述了我们可以做什么,但我对 Flutter 了解得不够多,无法告诉你它是否支持它所必需的(在任何给定时间设置任意导航历史的能力😄)

@Zhuinden onSaveInstanceState 涵盖的“无数”边缘案例与活动/控件有关......相信我......但欢迎您作为教育练习进行调查。

... 重申我的立场:Android 任务/活动、服务、应用程序生命周期(包括 onSaveInstanceState)都是Android 编程契约的一部分,“顺便说一下”,它们首先是在 Java 中实现的,但关键是它们是对应用程序进程施加系统级约束的方法,并且由于 Flutter 代码存在于 Android 应用程序进程中,因此 Flutter 绕过这些约束是不明智的。

亲爱的安卓开发者,

感谢您热情地为您的用户提供最佳用户体验,不仅在配置更改时保存状态,而且在进程死亡期间保存状态。 您可能已经阅读了上面的 200 条评论,说 Flutter 在保存应用程序状态方面很挣扎。 我想告诉你为什么以及如何解决它

为什么我不能使用onSavedInstanceState

onSavedInstanceState要求开发者同步保存应用状态。 你必须立即返回。 但是默认情况下与 Flutter 的通信是异步的( Future )。 因此,当 Android 要求时,您无法从 Flutter 应用程序请求保存的状态。
_但是如果您事先创建了保存的状态,您可以返回它,不是吗?_

问题不在于onSavedInstanceState不可访问,问题在于时机。 当 Android 要求保存状态时,您不必保存数据。

恢复方案A:自己保存的实例状态文件

您可以收听onSavedInstanceState并将此事件通知您的 flutter 应用程序。 然后保存您的应用程序状态并将其写入file 。 当您的应用从进程死亡中恢复时(您可以通过 Android 端的savedInstanceState检测到这一点)从文件系统加载文件,解析它并恢复您的状态。 (提示:使用平台渠道,它们比您想象的要容易)。

您甚至可以在用户杀死应用程序时恢复状态,或者如果保存的状态太旧,则关闭它!

恢复方案B:持续保存状态

我们可以运行一个计时器并不断地将平台端的应用程序状态保存在内存中。 假设每 3 秒一次。 当 Android 然后调用onSavedInstanceState我们可以返回之前保存的状态。 (是的,我们可能会丢失过去 3 秒内发生的更改)

恢复方案C:在关键点保存状态

与解决方案 B 一样,保存的状态将保存在平台端的内存中。 但是不是每隔几秒钟保存一次状态,而是在用户执行某些操作后手动请求保存状态。 (打开一个抽屉,在TextField输入文本,您可以命名)。


所有 3 种解决方案都是可组合的,使用最适合您的应用程序的解决方案。

但是如何保存我的应用程序状态?

与 Android ViewWidget没有onSaveInstanceState回调。 Widget 树不会自动保存。 这是一件好事! UI 和状态最终分离,并且在恢复 View 状态或实例化 Activity 和 Fragment 时没有反射魔法。

要保存状态,您必须遍历应用程序中的每个屏幕并保存恢复它们所需的所有信息。 我喜欢在 json 中执行此操作,但您可能会选择 protobuf 以获得更好的性能(在优化之前进行测量!)。

看看我们心爱的样品计数器应用程序,这可能非常简单:

// complete app state of sample application
{
  "counter": 24,
}

如果您有多个屏幕或对话框,您可能会得到以下应用状态 json:

{
    "currentRoute": "/todo-detail/241",
    "routes": {
        "todo-detail/241": {
            "edited": "true",
            "title": "Buy bananas",
            "picture": "http://....",
            "show_confirm_delete_dialog": "false"
        },
        "todo-list": {
            "current_scroll_position": "365",
            "filter": "Filter.TODAY"
        }
    }
}

实际实现在很大程度上取决于您的应用程序架构。

  • 如果您使用的是 Redux,则可以使用中间件来动态保存全局应用程序状态。
  • 如果您使用scoped_model可能足以保存模型。 重新创建它们与重新创建 Redux 状态有着根本的不同。

我能给你的最好的建议:

  • 严格分离您的 UI 和模型。
  • 查看每个屏幕,问问自己真正需要什么来恢复状态。 大多数时候它真的不多。
  • 与你的 iOS colleagues交谈,他们会告诉你他们多年来没有通过encodeRestorableState实现状态恢复。 Android 1M+iOS 974

帕斯卡

打开“不要保持活动”设置。

不,这不能正确测试进程死亡。 除非您明确启用该设置,否则这永远不会发生。 😞

如果您有多个屏幕或对话框,您可能会得到以下应用状态 json:

{
  "currentRoute": "/todo-detail/241",
  "routes": {
      "todo-detail/241": {
          "edited": "true",
          "title": "Buy bananas",
          "picture": "http://....",
          "show_confirm_delete_dialog": "false"
      },
      "todo-list": {
          "current_scroll_position": "365",
          "filter": "Filter.TODAY"
      }
  }
 }

哦,天哪,我们正在达到“导航状态是应用程序状态的一部分,作为有限状态机的状态”使其工作,但我喜欢它的外观😄

哦,男孩,我们正在达到“导航状态是应用程序状态的一部分,作为有限状态机的状态”以使其工作

😂不要让我开始。

应用程序状态的其余部分应保存在数据库/服务器上。 在 onSaveInstanceState 中唯一值得保存的瞬态状态是导航堆栈和一些用户输入。 这也是大多数人在这个线程中所要求的。

应用程序状态的其余部分应保存在数据库/服务器上。 在 onSaveInstanceState 中唯一值得保存的瞬态状态是导航堆栈和一些用户输入。 这也是大多数人在这个线程中所要求的。

这就是关键! (以及“Android 服务”支持)

@gitspeak ,我们与此问题无关的最后一次交流。 我会隐藏两个。 如果你想继续讨论,请使用我的github句柄@gmail.com PM我。 这里显然存在一个超出本问题范围的误解。

作为从事 Flutter 和 Android 之间接口的开发人员之一,我要感谢@passsy对保存状态所涉及的问题提供了很好的概述。

我还想补充一点,我有兴趣了解该线程中的开发人员正在处理的任何特定用例。 保存状态是一个可能跨越许多不同目标的概念。 一个应用程序调用状态,另一个应用程序没有。 一个应用程序绝对必须保证已保存的状态,而另一个应用程序只是更喜欢保存一些状态但没有它就不会中断。 有些应用完全使用 Flutter 构建,其他应用必须在传统的 Android/iOS UI 和 Flutter 之间同步信息。

如果你们中的任何人想描述您的应用程序在保存状态方面需要支持的特定用例,请随时通过[email protected]给我发送电子邮件 - 我正在积极组装用例。

@passsy请把我在下面的评论视为提供建设性反馈的真诚尝试

恢复方案A:自己保存的实例状态文件
您可以收听 onSavedInstanceState 并将此事件通知您的 flutter 应用程序。 然后保存您的应用程序状态并将其写入文件。 当您的应用从进程死亡中恢复时(您可以通过 Android 端的 savedInstanceState 检测到这一点)从文件系统加载文件,解析它并恢复您的状态。 (提示:使用平台渠道,它们比您想象的要容易)。
您甚至可以在用户杀死应用程序时恢复状态,或者如果保存的状态太旧,则关闭它!

几个问题:

1) 这种方法存在固有的竞争条件。 “状态持久化”线程可能比调用生命周期事件的 Android 应用主线程存活时间更长,在这种情况下,进程可能会在写入文件时终止。 您可以尝试通过在生命周期方法中阻塞来自“状态保存”线程的完成信号来协调两个线程,但是,除了引入额外的同步复杂性之外,还不能确保正确性,因为足够长的延迟会引入 ANR。 (https://developer.android.com/topic/performance/vitals/anr)
您可能会争辩说,“状态保存”到文件是如此之快以至于这不太可能发生,但请考虑在其他进程正在运行时线程调度和存储延迟的实际延迟,例如后台下载、播放存储更新、媒体播放器等......我是我不想争论这个,但我的个人经验(由其他人分享)是,显然 Android 设备不能免受感染 Windows PC 的相同“流感”的影响——它们也会随着时间的推移而变慢。 我最近刚刚擦干净了我的 Nexus 7,不仅通过 Wifi 下载和安装所有应用程序需要花费 HOURS 小时,而且设备响应能力现在是一个完整的笑话,表明我怀疑使用它甚至查找食谱时是否有耐心。

2)你不能对“太老了”有一个可靠的定义。 如果我从任务管理器中丢弃该应用程序,我希望所有状态立即消失,而如果我切换到另一个应用程序,我希望只要手机开机,状态就会无限期地保留。 重点是,Android 没有为您提供区分这两种状态的方法。

3) 并非所有 UI 控件都通过 API 公开其状态,该 API 允许用户为特定状态配置控件。 这样的 API 无疑会代表控件开发人员进行额外的开发工作。 在 android 上,行为良好的 UI 控件有望通过在内部处理 onSave/Restore InstanceState 通知来从开发人员那里抽象出“状态保留”问题,从而控制保留状态的状态。 如果我没记错的话,AutoComplete 控件会保留查询词,但不会保留结果列表。

恢复方案B:持续保存状态
我们可以运行一个计时器并不断地将平台端的应用程序状态保存在内存中。 假设每 3 秒一次。 当 Android 然后调用 onSavedInstanceState 时,我们可以返回之前保存的状态。 (是的,我们可能会丢失过去 3 秒内发生的更改)

非常不同意,除了围绕“解决方案 A”存在相同问题之外,它还增加了打开数据复制蠕虫罐头的新风险。

恢复方案C:在关键点保存状态
与解决方案 B 一样,保存的状态将保存在平台端的内存中。 但是不是每隔几秒钟保存一次状态,而是在用户执行某些操作后手动请求保存状态。 (打开一个抽屉,在 TextField 中输入文本,您可以命名)。

围绕“解决方案 A”有同样的问题。

Widget 树不会自动保存。 这是一件好事! UI 和状态最终分离,并且在恢复 View 状态或实例化 Activity 和 Fragment 时没有反射魔法。

不同意。 在状态可能会被抛出的情况下, Android 自动保留小部件树非常有用! 你到底为什么要把这个责任全部转移到应用程序开发者身上?? 为什么这与 UI 状态分离形成对比? 实际上,这种自动保存/恢复小部件树的方式似乎是 UI/状态分离的完美示例。

在使用适当的锁时,“解决方案 A”对我来说似乎完全合理,如果工作量合理,则不应触发 ANR。 此外,通过采用平台的通知机制,它可以避免一直进行不必要的序列化工作,而这对于每次击键都可能代价高昂。

关于自动化这种行为的可能性,我认为在 Dart 运行时没有反射机制,并且没有结构化的方式在 Flutter 的内存中存储状态,这无论如何都是不可能的。 目前,我能想到的最简单的方法是选择将状态保存在单个动态对象中(就像 React 上的this.propsthis.state )。

@aletorrado

在使用适当的锁时,“解决方案 A”对我来说似乎完全合理,如果工作量合理,则不应触发 ANR。

我同意。 ...... Android 模型更加简单和健壮,因为它不需要开发人员处理锁,它不会将瞬态状态持久化到磁盘,并且它仅在那些与您相关的情况下保留状态(颤动)不能不挂钩到 Android 生命周期事件和 onSaveInstance 工具 - 首先,它有什么用?

此外,通过采用平台的通知机制,它可以避免一直进行不必要的序列化工作,而这对于每次击键都可能代价高昂。

不需要保留每个击键,而是保留由选定状态组成的快照

数据应该作为 ByteArray 存储在 Bundle 中以避免
触发由主线程上的 I/O 引起的任何 ANR 或延迟。

2018 年 12 月 19 日,星期三,下午 6:08,Alejandro Torrado通知@ github.com
写道:

在使用适当的方法时,“解决方案 A”对我来说似乎完全合理
如果工作量合理,则不应触发 ANR。
此外,通过采用平台的通知机制,它避免了
一直在进行不必要的序列化工作,这可能代价高昂
每一次击键。

关于自动化这种行为的可能性,我似乎很清楚
在 Dart 运行时没有反射机制,并且没有
在 Flutter 中将状态存储在内存中的结构化方式,这是不可能的
以任何方式。 目前,我能想到的最简单的方法
这将是选择将状态保留在单个动态对象中(就像
React 上的 this.props 和 this.state)。


你收到这个是因为你被提到了。
直接回复本邮件,在GitHub上查看
https://github.com/flutter/flutter/issues/6827#issuecomment-448671264
或静音线程
https://github.com/notifications/unsubscribe-auth/AGpvBUQk17YOsjLMHTWcdJHL9rR71u6lks5u6nKGgaJpZM4KwSuX
.

真正的技巧是建立你所说的这个 ByteArray :slightly_smiling_face:

我宁愿说真正的诡计是能够恢复这个
ByteArray 正确。

2018 年 12 月 19 日,星期三,下午 6:41,Gabor Varadi [email protected]写道:

真正的技巧是建立你所说的这个 ByteArray 🙂


你收到这个是因为你被提到了。
直接回复本邮件,在GitHub上查看
https://github.com/flutter/flutter/issues/6827#issuecomment-448682229
或静音线程
https://github.com/notifications/unsubscribe-auth/AGpvBep8AW2xkKECt6uyTjqRGwri9Pmoks5u6npKgaJpZM4KwSuX
.

不。一旦你有了字节数组,就很容易了。 只需在加载时显示加载指示器(假设您已经告诉自己需要加载它的频道)。

真正的问题是 Flutter 集成,我称之为恢复部分。
导航应该自动保存,用户输入也是如此,就像在
原生安卓应用。

由于这个问题,Flutter 应用程序无法在 Android 中正确集成,因为
当存在 RAM 压力并且用户切换时,它们的行为不正确
应用程序在这些条件下回来之前。

2018 年 12 月 20 日星期四,凌晨 2:42 Gabor Varadi [email protected]写道:

不。一旦你有了字节数组,就很容易了。 只显示加载
加载时的指示器(假设您已经告诉自己
您需要加载它的频道)。


你收到这个是因为你被提到了。
直接回复本邮件,在GitHub上查看
https://github.com/flutter/flutter/issues/6827#issuecomment-448827537
或静音线程
https://github.com/notifications/unsubscribe-auth/AGpvBR4rmSxelodAjZvcQ6kOwPd7yxKPks5u6ushgaJpZM4KwSuX
.

我们中的一些人在这里对此进行了更多讨论: https :

  • 更正 (https://github.com/flutter/flutter/issues/6827#issuecomment-447488177):Google Ads 已在“Don't Keep Activities”开启的情况下进行了测试。 在这种情况下,应用程序进程不会终止,因此我在 (https://github.com/flutter/flutter/issues/6827#issuecomment-447955562) 上的解释成立。 正如@gitspeak@Zhuinden所指出的,这不是测试我们所防范的场景的好方法,因为它在实践中并不适用。

  • 每当应用程序在后台运行时,都会在 UI 线程上为层次结构中已启用保存的每个视图调用View.onSaveInstanceState 。 因此,从开发人员的角度来看,一个好的解决方案是FlutterView来实现这一点。 为了防止 UI 线程(您不能/不应该阻止)的竞争条件,此方法不应进入 Flutter 应用程序,因为没有同步方法可以做到这一点。

  • 无论 onSaveInstanceState 保存什么都不能大于 1MB,因此保存整个隔离状态是

  • 当视图稍后恢复时,FlutterView 会创建一个隔离并解压缩之前保存的数据部分(如果有)。

  • 当应用程序处于后台时,状态应该会在 Flutter 端停止更改。 总会有一些极端情况,因此提出 Flutter 应用程序的最佳实践列表会很好地配合这个解决方案(考虑到生命周期内存压力)。

  • 我想,对于印度等新兴市场,Flutter 预计将在低端手机上运行,​​这将是一个更为紧迫的问题。 也有传言称Android Go拥有更加激进的内存管理策略。

  • 当 _application_ 启动(不是活动)时,他们创建和维护他们的主要隔离。 然后,他们在创建时将这个隔离传递给 Flutter 视图。

你能提供一个例子这个代码吗?

他们在应用程序启动时(不是活动)创建和维护他们的主要隔离。 然后,他们在创建时将这个隔离传递给 Flutter 视图。

你能提供一个例子这个代码吗?

我无法提供复制/粘贴类型的示例,但这是正在发生的事情:

应用

  • 在您的Application.onCreate ,调用FlutterMain初始化函数。 ( startInitializationensureInitializationComplete )。 您可以在 flutter/engine repo 中找到对这些的引用。
  • 创建一个新的 FlutterNativeView 对象,传入 Application 实例作为上下文。
  • 在本机视图中运行您的包(查找runFromBundle示例)。
  • 在这个应用程序中实现一个接口,让你返回这个原生视图(类似于FlutterNativeViewProvider )。

活动

  • 扩展 FlutterFragmentActivity 并覆盖这两个方法:
  <strong i="26">@Override</strong>
  public FlutterNativeView createFlutterNativeView() {
    FlutterNativeViewProvider provider = (FlutterNativeViewProvider) getApplication();
    return provider.getFlutterNativeView();
  }

  <strong i="27">@Override</strong>
  public boolean retainFlutterNativeView() {
    return true;
  }

沿着这些路线的东西应该让你前进。 请注意,您的 Flutter 应用程序将在 Activity(以及窗口)可用之前开始运行。 因此,您不应该在主函数中调用runApp() ,直到您收到来自 Activity 的准备就绪信号。 您可以通过 PlatformChannels 和一个在 createFlutterNativeView() 中发出信号的地方来做到这一点。

免责声明

  1. 正如我上面提到的,我们并不完全容忍这种模式,因为它并没有真正解决这个特定的问题,而且非常混乱。

  2. 请注意,其中许多 API 都将发生变化。 由于 add2app 用例仍在不断发展,Android 和 iOS 嵌入 API 还不是很稳定。 我们将像往常一样在 flutter-dev@ 上宣布重大更改。

@passsy@gitspeak

恢复方案A:自己保存的实例状态文件

为了确保您有一个有效的状态文件,您可以写入 .tmp 文件,然后在写入完成后重命名。 如果您还保留以前的状态文件,则始终以有效状态结束。 此外,当进程在保存状态时被终止时。

@jsroest

你总是会得到有效的状态。

但不一定是正确的状态,这就是重点。

也不需要将状态持久化到非易失性存储器以 ..

@gitspeak

你总是会得到有效的状态。

但不一定是正确的状态,这就是重点。

也不需要将状态持久化到非易失性存储器以 ..

在许多情况下,拥有一致的状态比拥有“大多数最新值”更重要。 与事务一起使用的关系数据库就是一个例子。 在我看来,这也是存储状态的好地方。

坚持使用非易失性存储器有一些优点; 该进程也会因“电话掉线”、电池更换、硬件故障、操作系统更新、一般重启等而终止,因此当保存到非易失性存储器时,状态确实会持续存在。

@jsroest您的论点是有效的,但恕我直言不适用于此特定问题,因为此处提到的“状态”是瞬态 UI 状态(例如滚动位置、活动选择、临时文本输入等)。 请记住,只有在用户没有明确丢弃“屏幕”但用户输入仍然可能丢失的情况下,才需要恢复此类数据,例如当用户切换到另一个应用程序时。 没有用户期望在断电时恢复瞬态,就像没有用户期望浏览器将最后显示的网页滚动到系统断电之前的确切偏移量一样。 重点是,被认为是“瞬态”的数据的性质很重要,这里的讨论(特别是 Android onSaveInsatnceState 设施)是关于处理这种类型的数据。

@gitspeak
我为企业编写程序。 例如,订单拣货员在仓库中使用的程序,或零售业务中的其他示例,其中店主扫描商品以从其供应商处订购。 我的客户希望应用程序从他们离开的地方开始,无论发生什么。 这包括瞬态,还包括导航堆栈。 你能解释一下为什么这不适合这里讨论的 OnSaveInstanceState 工具吗? 也许消费者市场的用户期望不同,但恕我直言,可以使用相同的逻辑来保存和恢复状态。

@jsroest

我的客户希望应用程序从他们离开的地方开始,无论发生什么。

抱歉,我不知道如何为此类客户构建应用程序,也许其他人可以帮助...

@jsroest这是一个很好的问题,但不在此问题的范围内。 Flutter 受到平台本身可以做什么的限制,正如@gitspeak 所提到的,Android 的onSaveInstanceState是关于保存瞬态 UI 状态,其中数据生命周期与您想要的不同。 本地存储或远程存储(如果您希望状态在卸载和设备之间继续存在)会给您您想要的东西,但它有警告,这又超出了本问题的范围。

进一步推荐阅读:

https://developer.android.com/topic/libraries/architecture/saving-states [注意例如本文如何将本地存储与 onSaveInstanceState 分开]
https://developer.android.com/guide/topics/data/data-storage
https://firebase.google.com/docs/firestore/

@jsroest如果您的客户希望应用程序返回到原来的位置,即使它被强制停止、任务清除或操作系统重新启动,那么这完全是自定义的,而不是人们在这里谈论的内容。

onSaveInstanceState 正在保存给定任务的任务状态,但它在任务清除或强制停止或操作系统重新启动时被丢弃。

@mehmetf我认为这个问题确实已经达到了一个冗长的水平,在这个水平上理解它可能非常具有挑战性。 在@LouisCAD 的祝福下,我建议您关闭此问题并打开一个新的问题,该问题以您最近的总结(可能在某种程度上适合强调问题陈述和范围)以及对先前讨论的反向引用开始。

我同意,但我会把它留给@matthew-carroll; 他很可能会拥有这个问题。

感谢您的反馈。

我不想推动事情,但我认为我没有足够清楚地描述我在寻找什么。 我不符合此线程上使用的所有术语。 我看到我希望在 Flutter 中看到的内容与@LouisCAD@passsy所描述的内容之间有很多相似之处。

我的程序通常由以下部分组成:

  • 当用户完成工作时,用于从稳定状态推送数据的后端。
  • 用于稳定状态的本地 sqlite 数据库
  • 在内存数据结构中,称为“变量”的瞬态状态可以序列化到非易失性内存中,以便它可以在过程中存活。

“变量”数据结构只有具有可序列化属性的简单类。 这个“变量”数据结构的一个重要部分是屏幕堆栈。 (简单列举)。 顶部的屏幕是用户与之交互的最后一个屏幕。 下面的那个是用户按“返回”时导航到的那个。

我可以想象在Android平台上可以使用OnSaveInstanceState来触发这个数据结构的序列化。 必须在其他平台(iOS、未来:win、mac、web)上找到类似的东西,但正如@passy 所暗示的那样,其他关键点也可能触发这一点,这可能就足够了。

当程序启动时,它会检查序列化数据结构是否可用,以及程序的当前版本是否与序列化它的版本相同。 这可以通过比较版本号来完成。 我们不想加载不兼容的数据结构。

如果这一切加起来,那么数据结构将被反序列化并在内存中可用。 该程序加载位于堆栈顶部的屏幕。 屏幕本身从数据结构加载其瞬态。 现在我们有一个程序可以在进程死亡中幸存下来。 (或者我错过了什么?这绝对适用于我过去在 windows mobile、Xamarin 表单和 asp.net 上的项目。我认为它也应该适用于 Flutter)。

示例应用程序,其中 SXXX 代表带有数字的屏幕。 该数字仅用于提醒程序员哪些屏幕或多或少属于一起。

S100 Main menu
  S200 Order pick
    S210 Select order
      S220 Confirm pick location
        S230 Pick nr of pieces
          S240 Report shortage
        S250 Scan dropoff location
    S300 Cycle count
      S210 XXXXXX
        S220 XXXXXX
      S230 XXXXXX
    S300 Reprint labels
      S310 XXXXXX

样本变量数据结构

Class variables {
    Class AmbientVariables {
        ….
    }

    Class ScreenStack{
        List<string> ScreenStack;
    }

    Class Orderpick {
        int selected_order;
        string comfirmed_location;
        string nr_picked;
        ….
    }

    Class CycleCount {
        ….
    }

    Class ReprintLabels {
        ….
    }
}

除了屏幕堆栈又名导航堆栈的重新创建之外,我想在颤振中看到的所有内容可能已经存在。 我不知道如何在内存中重新创建这个对象树。 我也不确定是否应该重新创建它。 我还可以制作自己的导航堆栈,并将其作为单页应用程序在 flutter 中实现。 和我在以前的项目中所做的一样。 但是我会从 MaterialApp 和 Scaffold 类中丢失很多内置的好东西,其中后退箭头/按钮是最明显的。

我意识到这不会自动保存所有瞬态。 例如,选定的文本、列表的位置、特定动画的位置。 但是作为程序员,您可以决定每个屏幕需要保存什么。 尽管这正是@LouisCAD试图阻止的,因为需要所有样板代码。

我应该创建一个新问题还是适合这个线程?

再次感谢您的反馈,我真的很感激。

@jsroest感谢您的详细解释。 请使用 flutter 标签将其发布到 StackOverflow 上。 您实际上是在寻求有关如何构建和构建路线以及如何将上次访问的路线保存在持久存储中的建议。

您可能认为解决这个特定问题也可以解决您的问题,但事实并非如此。 如果你不明白为什么,你可以在我的 github handle @ gmail.com 上 PM 我。

这个问题有什么进展吗?

@vanlooverenkoen理论上,如果您将导航器的路由列表和密钥层次结构保存到一个文件(以及有状态小部件的状态),并在 Android 本机端将其清除savedInstanceState == null ,那么您可能会成功持久性和可恢复性,只是没那么容易。

@Zhuinden仅保存路由和密钥实际上要复杂得多。 屏幕之间的通信要么使用回调完成,要么通过等待结果来完成。 您还必须以某种方式恢复这些状态。 否则你最终会得到一些看起来正确但不正确的东西。 您可以像使用原生 Android(带有 Intent)一样,通过使用数据包的通信形式来解决恢复回调等问题,但这会降低 Flutter 的许多易用性。 只要这个问题没有真正的解决方案,Flutter 就不能用于很多应用程序,主要是那些用于收集大量复杂的基于表单的数据的应用程序。 我想知道为什么 Flutter 团队没有公开解决这个问题,因为他们肯定意识到了这一点。 也许它会揭示 Flutter 架构中的一个严重缺陷,并表明除了华丽的演示来打动管理层之外,该系统对于现实生活应用程序来说并不是一个好的选择。

@jsroest @mehmetf @claudiofelber ,这个 StackOverflow 问题的答案是否解决了这些问题? https://stackoverflow.com/questions/54015775/what-is-the-best-way-to-pass-data-or-arguments-between-flutter-app-screens/54015932

我从一开始就一直在关注这个,但由于我失去了上下文,不得不重新阅读一堆消息。

如果 Flutter 团队认为应该有一个新的、新鲜的问题,更少的洪水和更清晰的计划,我对此没有问题,因此这个问题被关闭了。

我创建了native_state 插件以利用 Android 和 iOS 机制在进程

Navigator已经可以通过initialRoute属性恢复应用程序路由,事实证明,_if_ 你遵循一些特定的规则,我已经记录并添加了示例,你可能总是想使用该插件与恢复路线相结合(通过使用提供的SavedStateRouteObserver或其他一些机制)。

Tao 刚刚向我指出,这是我们去年秋天调查中要求最高的第四个问题。 我认为这里表达了多种用户需求(在 100 多条评论中!),我们需要在某个时候坐下​​来,将它们整理成我们可以采取行动的单独项目。

@eseidelGoogle我不同意您的评估。 恕我直言,没有“表达的用户愿望混合”,而是缺乏 Android 平台支持,这会导致同一核心问题导致不同的编程挑战。

Android 应用程序有两个中心主题:

1) 进程状态机(又名生命周期事件)。
2)用户界面工具包。

Flutter 有 (2) 但没有 (1)。 它需要同时解决这两个问题。

3年...

快速更新:我目前正在积极探索此问题的解决方案。

@goderbauer只是为了确保清楚这不仅仅是 Android 问题,iOS 具有相同的机制,尽管对 iOS 应用程序的影响可能较小。 我创建了一个插件,允许通过 os 机制保存状态,这也可能是值得一看的: https :

@hvisser你如何建议保存导航状态? Flutter 的 Navigator 没有getRoutes方法。

@hvisser是的,我正在为两个平台研究这个(它应该足够灵活以插入其他平台)。 我删除了 platform-android 标签以明确说明这不是 Android 特定问题。

@hvisser你如何建议保存导航状态? Flutter 的 Navigator 没有getRoutes方法。

@Zhuinden导航器已经支持它(尽管我认为它不是众所周知的)。 我有一个如何在本机状态项目中设置它的示例,但是 TL;DR

  • 您必须使用命名路由
  • 路由应该是分层的,例如/screen1/screen2/screen1/
  • 当您设置initialRoutes属性并且您的路线以这种方式组织时,路线将被推送到导航堆栈
  • 我_认为_您还需要GlobalKey来表示navigatorKey上的MaterialApp但这可能特定于我的情况。

因此,如果您将initialRoutes/screen1/screen2并且您的路线设置正确,那么恢复导航也将起作用。

当您打开和关闭本机 youtube 和 flutter 应用程序的飞行模式时,带有 2 个 SIM 卡的旧设备上的另一个案例将重新启动 #49057
视频附在我的问题中

尝试禁用颤振调试模式。 如果启用调试模式应用程序总是重新启动。

debugShowCheckedModeBanner: 假,

自 2018 年(6 月/7 月)以来,我一直在颤振中工作,并且在 2-3 个月后才知道这是一个问题。 我不知道这变成了一个大问题。 我正在制作一个音乐播放器应用程序,如果我按下后退按钮,当音乐在后台继续播放时,flutter 会破坏所有状态。 为了解决这个问题,我制作了这个system_shortcuts库。
pubspec.yaml中将插件添加到您的项目 _[ 对于初学者:) ]_

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  system_shortcuts:

我一直在使用一种对我来说很好

用法 -

无论您使用什么小部件和按下后退按钮,您都知道应用程序将关闭(即用户离开应用程序)在状态类中添加WidgetsBindingObserver Mixin,如下所示

class _MusicPlayerState extends State<MusicPlayer>
    with WidgetsBindingObserver{
...
}

这样做之后覆盖 didPopRoute 方法

<strong i="21">@override</strong>
  Future<bool> didPopRoute() async {
    await SystemShortcuts.home();
  return true;
  }

_让我们了解这是如何工作的_:
应用程序正在运行>>用户按下后退按钮>>主页按钮被按下而后退按钮没有>> 应用程序进入后台并且状态没有被破坏

工作样本 -

_编辑_
_此应用程序是使用提供程序状态管理编写的,但在关闭应用程序并从最近重新打开后,没有用于管理任何状态的代码。 它只是我们在这里添加的代码。_

ezgif com-video-to-gif

ezgif com-video-to-gif (1)

问题 -

如果您是从另一个应用程序访问您的应用程序,则无论何时您从主窗口小部件返回,您都将始终转到主屏幕,而不是从您来时的上一个应用程序。
例子 :-
用户在 WhatsApp(或任何其他应用程序)中>>他们从您的应用程序收到通知>>他们从通知中打开您的应用程序>>现在他们按下后退按钮>>他们将返回手机的主屏幕而不是WhatsApp(这应该是默认场景)
_到目前为止,这个问题还没有给我带来任何大问题,我认为所有普通用户和开发人员都会接受它,因为在极少数情况下,您从另一个应用程序访问您的应用程序。 我并没有忽视它可能会安静地发生几次,但它对我有用,它只是一种解决方法,直到它从颤振核心团队获得任何实际帮助为止。_

事实上,这个问题几乎已经存在 3 年半了,但仍然没有什么比这更令人沮丧的。

显然,从目前设定的里程碑来看,这将在 2020 年 5 月之前完成……赶上 Google I/O?

上面的 hvisser 已经解决了这个问题,现在人们只需要编写使其工作的代码

我正在为此提供解决方案方面取得进展。 有关应用程序如何在完成后恢复实例状态的早期预览,请查看此 PR: https :

不幸的是,这需要更多的时间才能完成。

有关如何在 Flutter 中进行状态恢复的详细设计文档,请访问: http :

上面设计中概述的具有通用状态恢复框架的 PR 已发布: https :

通用状态恢复框架(https://github.com/flutter/flutter/pull/60375)已经提交。 我现在正在努力将它集成到我们的小部件中。 将从可滚动、文本字段和导航器开始。

您可以通过将要恢复的数据存储在sqlite数据库中来自己实现。 这是在编写服务器时使用的一种技术。

即使手机电池没电或出现内核恐慌或其他任何情况,这也有利于恢复应用程序。 我建议围绕您的 SQLite 调用制作一个 OO 包装器。 Dart 没有像 SQLite.Net 这样的东西,不幸的是它会自动为你做。

编辑:这也是我们在移动开发早期所做的事情,因为内存非常有限,而 sqlite 是在内存和磁盘之间交换事物的好方法。

我正在关闭这个问题,因为通用状态恢复框架(和 Android 嵌入)已经登陆。 剩余的工作在单独的问题中进行跟踪:

做得好!!!!! 谢谢飘飘!!!

此页面是否有帮助?
0 / 5 - 0 等级