【问题标题】:How to perform background operations in Android that update the UI properly using Fragments?如何在 Android 中执行后台操作以使用 Fragments 正确更新 UI?
【发布时间】:2014-08-01 19:18:07
【问题描述】:

我找不到任何满足我所有要求的解决方案。

在我的应用程序中,我使用AsyncTasks 执行一些操作,例如将数据保存到内存或从数据库中读取数据。我在onPreExecute 中创建一个进度对话框,在onProgressUpdate 中更新一个进度值并在onPostExecute 中关闭该对话框。

最近我切换到 Fragment API(我使用支持库来定位旧版本的 Android),这意味着我的活动子类 FragmentActivity 和对话框子类 DialogFragment

切换到 Fragment API 导致了一个众所周知的问题 - 有时我会遇到以下异常:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

例如,当用户启动后台操作(出现进度对话框),使用主页按钮最小化应用程序并在活动处于后台时操作完成时,就会发生这种情况。然后应用程序尝试关闭对话框,但由于保存了活动的状态,因此失败了。

我理解这个问题。可以通过确保推迟对 UI 的更改直到活动恢复,如本文所述:How to handle Handler messages when activity/fragment is paused

但是,这种解决方案会导致另一个问题。如果活动在后台时操作完成,然后活动被 Android 杀死怎么办?当用户导航回应用程序时,它会恢复其保存在onSaveInstanceState 中的状态。因此,进度对话框仍然可见,其进度值与将活动置于后台时相同。应该关闭它的消息从未被处理过,并且在 Activity 被终止时丢失了。

正确处理所有描述的问题的正确解决方案是什么?如何在活动处于后台时允许更改 UI,或者至少允许推迟 UI 更改并确保在活动被 Android 杀死的情况下它们不会丢失?该解决方案必须允许跟踪后台任务的进度。

【问题讨论】:

  • 如果DialogFragmentFragment重新实例化后恢复,那么只需检查操作是否仍在运行,如果没有,则关闭DialogFragment。如果你没有持久化你的任务,那么在进程重启后它无论如何都不会运行。

标签: java android android-asynctask android-fragmentactivity


【解决方案1】:

避免您所描述的情况的唯一可靠方法是将结果保存到某种存储中。您可以启动一个服务来执行长时间运行的任务,并将结果写入数据库,并在完成时发送本地广播。

如果活动恰好在前台,它将接收广播并更新其状态以知道进程已完成,并可以删除有关任务的信息 来自您使用的任何持久存储机制。

如果活动在后台,服务将完成,发送广播,并持久化结果。下次活动进入前台时,它应该检查这个持久存储并查看是否有未解决的任务状态,并更新其状态以匹配。

因此,一个示例可能是(让我们使用“上传照片”作为任务):

  1. 用户单击按钮开始上传图片

  2. Activity 启动服务以将图像上传到远程服务

  3. Activity 为您定义的某些广播事件注册LocalBroadcastManager(例如com.mypackage.ACTION_PHOTO_UPlOADED

  4. 服务保留有关任务的一些信息(例如 SharedPreferences.putBoolean("TASK_PHOTO_UPLOADED", false)

  5. 在服务处理上传任务时,Activity 会显示某种加载 UI

  6. 用户按下主页并将应用程序发送到停止状态

  7. 活动取消注册本地广播管理器

  8. 服务完成图片上传

  9. 服务发送一个未使用的本地广播 (com.mypackage.ACTION_PHOTO_UPlOADED),因为 Activity 不再监听

  10. 服务更新持久化信息 (SharedPreferences.putBoolean("TASK_PHOTO_UPLOADED", true))

  11. Android 终止 Activity,保存其状态

  12. 用户稍后返回活动

  13. 活动在onResume() 中使用服务检查是否有未处理的任务结果 (if (SharedPreferences.has("TASK_PHOTO_UPLOADED"))),然后检查它是否应该更新状态。

  14. 如果照片上传键存在且为真,Activity 会移除对话框片段并从 SharedPreferences 中清除 TASK_PHOTO_UPLOADED 键。

    如果照片上传键存在且为假,则 Activity 注册等待事件完成的本地广播管理器

【讨论】:

  • 虽然您用ServiceLocalBroadcastManager 来描述它,而不是AsyncTask,但这里可以使用相同的想法。但是,使用SharedPreferences 来保留下次创建/恢复活动时是否应关闭对话框的信息似乎是在使用大炮杀死苍蝇。我想知道有没有更简单、更优雅的解决方案。
  • SharedPreferences(如 Service 和 LocalBroadcastManager)只是建议的实现。您将需要某种 类型的持久存储——SharedPreferences 是最容易展示的,尽管您可以使用数据库(现在那是 大炮飞行)。 Service 可以替换为 AsyncTask(尽管我强烈建议使用 Service,因为您不必担心使用 Activity 生命周期来管理它)并且 LocalBroadcastManager 可以替换为 Event Bus 或类似模式。跨度>
  • +1 Service 是您真正想要完成操作的方法,即使在由于进程被终止而终止的情况下也是如此。否则,您最好在应用程序被杀死后恢复时关闭ProgressDialog,因为在任何情况下操作都不会运行,@maral。或者,您可能有兴趣使用一些第三方库来处理您的任务的持久性,例如 TapePriority Job Queue
  • 请注意,这里的重要部分是持久化 task(这将通过使用 Service 来完成),而不是状态或结果,否则问题不适用。 @maral,如果您可以提供一种查询操作状态的方法,那么这足以确定DialogFragment 是否需要在Fragment 重新实例化时被解除。如果您使用我提到的任务队列库,它们应该固有地提供此功能。
【解决方案2】:
  • 你的问题很长,没有任何代码示例,我试着想出我最好的答案,我已经为你做了一个项目,关于你如何 应该在android中使用onSaveinstance状态,这将解决所有 可能的问题

  • 我在下面写了一篇解释性文章,将消除你所有的疑惑


在android Life-Cycle中处理Fragments

  • Activitycontainer 对应于 fragments

  • oncreateActivity 挂载第一个片段

  • 当我们使用片段添加替换等时,我们需要使用android fragment引用而不是classreference

  • 每次我们将片段挂载到容器时,都将片段保存在backstack

  • 在项目中我们可以观察到我们正在收集onpause()上的所有小部件状态并存储在一个局部变量中并使用这个局部变量,然后将此局部变量传递给onSaveInstance()事件访问这些标签在onActivityCreatd() 中设置为视图对象。我们使用这个过程是因为局部变量保存在类中,但视图对象在onSaveInstance 中是null。在Activity-FragmentOne-FragmentTwo-FragmentOne-OrientationChange-OrientationChange 中观察到这一特殊观察非常重要

  • Android static objects 能够保持 onOrientation change 的状态,但 dynamic Objects 必须重置 onOrientation 使用上面解释的局部变量更改值

  • 如果我们使用动态片段,我们需要先在OnActivityCreated() 事件中创建动态对象,然后再使用saveInstanceState 恢复动态对象

  • 片段在onPause事件上被添加到backstack

  • 如果屏幕导航是Activity-FragmentOne-FragmentTwo-FragmentOne-FragmentTwo,则按下后退按钮导航将作为FragmentTwo-FragmentOne-@987654356 @-FragmentOne-Activity,因此我们可以清楚地观察到我们的路径正在被android backstack跟踪

  • 如果路径是Activity-FragmentOne并且第一次改变方向,那么触发的事件如下 MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume

  • 如果路径是Activity-FragmentOne-orientationchange并且第一次改变方向,那么触发的事件如下

MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume- FragmentOne-onPause- FragmentOne-onSaveInstanceState- FragmentOne-onStop- FragmentOne-onDestroy- FragmentOne-onDetach- MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume

  • 如果路径是Activity-FragmentOne-orientationchange-orientationchange并第一次改变方向则触发的事件如下

MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume- FragmentOne-onPause- FragmentOne-onSaveInstanceState- FragmentOne-onStop- FragmentOne-onDestroy- FragmentOne-onDetach- MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume- FragmentOne-onPause- FragmentOne-onSaveInstanceState- FragmentOne-onStop- FragmentOne-onDestroy- FragmentOne-onDetach- MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume

  • 如果路径是Activity-FragmentOne-orientationchange-orientationchange-FragmentTwo第一次改变方向则触发的事件如下

MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume- FragmentOne-onPause- FragmentOne-onSaveInstanceState- FragmentOne-onStop- FragmentOne-onDestroy- FragmentOne-onDetach- MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume- FragmentOne-onPause- FragmentOne-onSaveInstanceState- FragmentOne-onStop- FragmentOne-onDestroy- FragmentOne-onDetach- MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume- FragmentOne-onStop- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentTwo-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume

  • 如果路径是Activity-FragmentOne-orientationchange-orientationchange-FragmentTwo-orientationchange第一次改变方向则触发的事件如下

MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume- FragmentOne-onPause- FragmentOne-onSaveInstanceState- FragmentOne-onStop- FragmentOne-onDestroy- FragmentOne-onDetach- MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume- FragmentOne-onPause- FragmentOne-onSaveInstanceState- FragmentOne-onStop- FragmentOne-onDestroy- FragmentOne-onDetach- MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentOne-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume- FragmentOne-onStop- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentTwo-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume- FragmentOne-onPause- FragmentOne-onSaveInstanceState- FragmentOne-onSaveInstanceState- FragmentOne-onStop- FragmentOne-onDestroy- FragmentOne-onDetach- FragmentOne-onDestroy- FragmentOne-onDetach- MainActivity-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onAttach- FragmentOne-onCreate- FragmentOne-onCreateView- FragmentTwo-onActivityCreated- FragmentOne-onStart- FragmentOne-onResume


总结::

  • 现在请记住onPause 事件总是在关闭片段之前触发,存储您要保留的值onOrientation,当phonecall 出现时,任何其他情况

  • 接下来会执行onSaveInstanceState,所以使用那些局部变量来设置这里来设置bundle中的数据

  • 接下来活动将被推送到backstack点击主页按钮不会丢失您的数据

  • 所以当onActivityCreatedbundle' and store it back to yourviews获取数据时`

  • 1234563 987654539@,它会自动发生

如果您需要更多信息,请告诉我

【讨论】:

  • 这个答案没有解决我的问题。使用onSaveInstanceState() 或活动生命周期,我没有改变方向的问题。看最后一段:我问如果AsyncTask 在后台活动结束时,如何确保FragmentActivity 的状态(UI)被更新(或将在用户下次导航到它时更新)。
  • @maral .... 好的.... ! ......Scenario-1: ...导航到要放置在onPostExecute中的下一个活动,以便在AsyncTask事情完成之前不会发生导航......Scenario-2: ...如果您正在处理使用数据库,您可以将其添加到Transaction..... 在其中一个答案kcoppock 中还描述了一种以正确方式处理这种情况的方法。 ! .....希望对您有所帮助
猜你喜欢
  • 2023-01-11
  • 2011-07-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-27
  • 2015-06-13
  • 2020-08-28
  • 1970-01-01
相关资源
最近更新 更多