如何完成已销毁的Activity

13
据我了解,一个活动被“销毁”并不等同于一个活动被“完成”。
- “完成” - 活动从后退栈中移除。 - 它可以由程序触发(例如通过调用finish()),或者用户按下返回键(这会隐式调用finish())。 - 结束一个活动将销毁它。
- “销毁” - Android操作系统可能会销毁一个不可见的活动以回收内存。当用户再次导航到该活动时,该活动将被重新创建。 - 当用户旋转屏幕时,活动将被销毁并重新创建。 - 参考:重新创建活动

那么我应该如何结束一个已销毁的活动呢?finish()方法需要一个Activity对象,但如果活动已被销毁,我就没有Activity对象了 - 我不应该持有对已销毁活动的引用,对吗?


案例研究:

我有一个活动a,它启动b,然后启动c(使用Activity.startActivity()),现在返回堆栈如下:

a → b → c

在 c 中,用户填写表格并点击提交按钮。使用 AsyncTask 向远程服务器发出网络请求。任务完成后,我会显示一个toast并通过调用c.finish()结束活动。非常好。 现在考虑这种情况: 在异步任务正在进行时,用户切换到另一个应用程序。然后,由于内存限制,Android操作系统决定销毁所有3个活动( a , b , c )。稍后,异步任务完成了。那么我该如何完成 c ? 我尝试过的:
  • Call c.finish():
    • Can't, because c is destroyed.
  • Call b.finishActivity():
    • Can't, because b is destroyed.
  • Use Context.startActivity() with FLAG_ACTIVITY_CLEAR_TOP so as to raise b to the top, thus finishing c:

    // appContext is an application context, not an activity context (which I don't have)
    Intent intent = new Intent(appContext, B.class);    // B is b's class.
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    appContext.startActivity(intent);
    
    • Failed, appContext.startActivity() throws an exception:
android.util.AndroidRuntimeException: 在Activity上下文之外调用startActivity()需要使用FLAG_ACTIVITY_NEW_TASK标志。这真的是你想要的吗?
编辑:澄清:我需要等待异步任务完成并根据服务器的响应决定是否完成c。

尝试一下: Intent intent = new Intent(appContext, B.class); //B是b的类。 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); finsh(); appContext.startActivity(intent); - Sree
@Sreekanthss *what*.finish()?我没有活动。 - Pang
7个回答

3
android.util.AndroidRuntimeException: 调用startActivity()方法时,需要在Activity上下文之外添加FLAG_ACTIVITY_NEW_TASK标志。这真的是你想要的吗?
这个异常通常发生在你从后台线程或服务中启动一个活动时。每当需要"launcher"类型的行为时,您需要传递FLAG_ACTIVITY_NEW_TASK标志。
只需添加mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);即可避免此异常。
  • 不建议您尝试终止活动,让安卓自己处理。结束已经销毁的活动没有任何意义。

现在,你能做什么?

  • 如果您在应用程序不在前台时遇到了无法完成活动的问题,您可以实现一个安全检查,仅当应用程序在前台时才完成活动以转到后退堆栈活动,否则跳过该步骤。

  • 我觉得您正在尝试在应用程序处于后台时杀死活动。这似乎有点困难,但是您可以利用onUserLeaveHint来决定何时将应用程序置于后台以完成活动,或者您可以通过在onStop()中添加finish();来完成活动。只要确保异步任务的onPost()不会再次完成它,以避免异常。

  • 请查看android:clearTaskOnLaunch属性并将其设置为true。

    Google Doc对此属性的说明如下:

    例如,某人从主屏幕启动活动P,然后从那里进入活动Q。用户接下来按Home键,然后返回到活动P。通常情况下,用户将看到活动Q,因为这是他们在P的任务中最后所做的事情。但是,如果P将此标志设置为"true",则在用户按Home键并任务转到后台时,所有在其上方的活动(在本例中为Q)都将被删除。因此,当用户返回到任务时,只会看到P。

    我认为这正是您想要的情况。

希望这能为您实现所需的任务提供一些提示。

  1. 异常发生在异步任务的onPostExecute()方法中,该方法在主/UI线程上执行,而不是在后台线程上执行。
  2. 我不想杀死一个应用程序,我只想结束其中一个活动。
  3. 我想结束已销毁的活动c,这样当用户切换回我的应用程序时,c就不存在了,用户会看到b
- Pang
android:clearTaskOnLaunch 很有趣。让我看看是否可以以编程方式更改此标志。 - Pang

1
你可以在c的onPostExecute方法中广播你的操作,并在a和b中注册一个广播receiver来接收该操作。然后在该接收器的onRevice方法中完成操作。
在AsyncTask中,
 void onPostExecute(Long result) {
         ----
         Intent intent1 = new Intent("you custom action");
    context.sendBroadcast(intent1);
     }

In a and b

registerReceiver(new BroadcastReceiver() {

            @Override
            public void onReceive(Context context, Intent intent) {
                finish();

            }
        },new IntentFilter("you custom action"));

那么我需要在abonDestroy()中调用unregisterReceiver(),否则系统将会一直保留对广播接收器的引用,从而使其永远保持对该活动的引用,是吗? - Pang
你需要做的就是,我忘了提到。 - Labeeb Panampullan
好的,对于我的情况,abc 都已经被销毁了,现在怎么办? - Pang
1
当您导航回应用程序时,当前活动将被重新创建。如果您非常担心重新创建活动,则可以在SharedPreferences中设置一些标志,并在onCreate中检查该值,然后从那里调用finish。 - Labeeb Panampullan
еңЁonCreate()дёӯи°ғз”Ёfinish()и§ЈеҶідәҶжҲ‘зҡ„й—®йўҳгҖӮж„ҹи°ўжҸҗзӨәгҖӮ - Pang

1
个人而言,我会使用通知栏来告知用户他的查询状态。这样,我就避免了未完成活动的整个问题。只有当用户尚未点击提交按钮时,我才会保留未完成的活动。

我不想在用户点击提交按钮后立即结束 c。想象一下,c 是一个账户注册表单。如果账户注册失败(例如,账户名已被使用),用户可以编辑表单并快速重新提交;如果账户注册成功,则活动结束,用户可以立即开始使用应用程序。这种情况可能吗? - Pang
好的,只要不完成任何事情,如果活动被电话或其他事情打断,请让您的线程更新一个布尔标志以表示提交已被接受。然后,在您的活动的onResume()中验证布尔标志是否成功。 - Stephan Branczyk
我知道onResume()的措辞似乎令人困惑,但当您的活动重新启动时,它将被调用,即使它已被销毁。 onResume()甚至在活动第一次启动时也会被调用。 onResume()是关于UI线程恢复的(即使应用程序第一次被调用,它也会在第一次被调用)。 - Stephan Branczyk
现在,如果您的用户按下主页键或其他键,则用户将被强制重新定位您应用程序的启动器图标并自行重新启动它。一旦您的应用程序重新启动,其活动将仅检查布尔标志以查看是否已注册。 - Stephan Branczyk
+1 了解通知的重要性,对于一个进入后台的应用程序来说非常有用。 - Pang

0

无法直接完成已销毁的活动,因此建议在其onCreate()中使用finish()方法(由@Labeeb P建议)。以下是具体步骤:

  1. 如果在尝试结束活动时,该活动已被销毁,请将布尔标志保存在某个地方。

    if(activity != null)
    {
        // Activity 对象仍然有效,因此现在 finish()。
        activity.finish();
    }
    else
    {
        // 活动已被销毁,因此保存一个标志。
        is_activity_pending_finish = true;
    }
    
    • 如果需要即使应用程序被销毁也保留标志,请使用持久存储,例如 SharedPreferences(由@Labeeb P建议)。
  2. 在活动的 onCreate() 中,检查标志并调用 finish()

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
    
        if(is_activity_pending_finish)
        {
            is_activity_pending_finish = false; // 清除标志。
    
            // 应该已经完成此活动,因此现在完成它。
            finish();
            return;
        }
    
        ...
    }
    
    • 如果有多个相同的 Activity 类的实例,则可能需要更多的东西来标识要完成的特定活动实例。

onCreate()中调用finish()实际上是一种合法的操作,因为文档中提到:

...您可能会从onCreate()中调用finish()来销毁活动。 在这种情况下,系统立即调用onDestroy()而不调用任何其他生命周期方法。


其他考虑事项:

  • 在应用程序处于后台时结束一个活动可能不是一个好主意,特别是如果它是唯一的活动。确保您不会让用户感到困惑。
  • 为了更好的用户体验,如果您在应用程序处于后台时结束一个活动,您可能需要通知用户。考虑使用toasts(适用于短通知)或notifications(适用于用户可能已经忘记的长时间操作)(由@Stephan Branczyk@dilix建议)。

当然,一个活动被销毁并不意味着应用程序处于后台(可能有另一个前台活动)。尽管如此,上述解决方案(在onCreate()中调用finish())仍然有效。


0
当系统试图销毁您的Activity时,它会调用onSaveInstanceState。在这里,您可以调用finish()。就是这样。
警告:我从未尝试过这个方法,所以我不确定从onSaveInstanceState中调用finish()是否存在任何问题。如果您尝试了,请评论并让我知道它的效果如何。

我不想在系统试图销毁它时结束该活动。我想要等待异步任务完成,并根据服务器的响应来决定是否结束它。此时,该活动已经被销毁了,这就是为什么我找不到一种结束它的方法。 - Pang

0
关于android手册中的onDestroy()方法,在活动被销毁之前会被准确地调用,因此你可以在其中调用finish(甚至可以在完全销毁活动之前停止后台线程)。
我们可以假设如果活动被销毁,我们对后台线程也不感兴趣,例如,如果后台线程是用来下载图片等需要完成的任务,那么你必须使用服务而不是异步任务。

我不想杀死后台线程,因为用户需要知道操作是否成功。我只想在操作成功的情况下完成 c - Pang
我认为在这种情况下最好使用服务,并在收到成功交付通知时通知用户。 - dilix

0
抱歉十年后才回复这个问题。我理解此问题的前提错误,主要在于以下部分:“当异步任务正在进行时,用户切换到另一个应用程序。然后,Android操作系统由于内存限制决定销毁所有3个活动(a、b、c)。稍后,异步任务完成。现在我该如何结束c?”据我理解,如果操作系统由于内存限制而决定销毁所有三个活动,则它不会仅销毁它们,而是整个进程都被销毁,包括所讨论的AsyncTask。因此,异步任务也无法完成。
参考资料:https://medium.com/androiddevelopers/who-lives-and-who-dies-process-priorities-on-android-cb151f39044f,主要来自文章中的这一行:“请注意,虽然我们将谈论特定的组件(服务、活动),但Android只会杀死进程,而不是组件。”
在当今世界中,我猜一个WorkManager会被用于运行需要保证执行的工作。

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