在 onPause、onStop 和 onDestroy 方法中调用超类方法的正确顺序是什么?为什么?

2022-08-31 10:39:53

我刚刚浏览了Android开发人员网站,刷新了活动生命周期,在每个代码示例中,在超类方法旁边都有一个注释,上面写着“始终首先调用超类方法”。

虽然这在创建半周期中是有意义的:onCreate,onStart和onResume,但我对销毁半周期的正确程序是什么有点困惑:onPause,onStop,onDestroy。

首先销毁实例特定资源,然后再销毁实例特定资源可能依赖的超类资源是有意义的,而不是相反。但这些评论表明情况并非如此。我错过了什么?

编辑:由于人们似乎对问题的意图感到困惑,我想知道的是以下哪项是正确的?为什么?

1.谷歌建议

    @Override
    protected void onStop() {
      super.onStop();  // Always call the superclass method first

      //my implementation here
    }

2.另一种方式

    @Override
    protected void onStop() {
       //my implementation here

       super.onStop();  
    }

答案 1

首先销毁实例特定资源,然后再销毁实例特定资源可能依赖的超类资源是有意义的,而不是相反。但这些评论表明情况并非如此。我错过了什么?

在我看来:没有一件事。

Mark(又名CommonsWare on SO)的这个答案阐明了这个问题:链接 - 对超类方法的调用应该是第一个语句吗?但是,你可以看到他的答案留下了以下评论:

但是为什么官方文档在onPause()中说:“总是先调用超类方法”?

从头再来。好吧,让我们从另一个角度来看这个问题。我们知道Java语言规范没有指定必须放置调用的顺序(或者是否必须放置调用)。super.overridenMethod()

在类活动的情况下,调用是必需的并强制执行的super.overridenMethod()

if (!mCalled) {
    throw new SuperNotCalledException(
        "Activity " + mComponent.toShortString() +
            " did not call through to super.onStop()");
}

mCalled在 中设置为 true。Activity.onStop()

现在,唯一需要辩论的细节是排序。

I also know that both work

确定。查看 Activity.onPause() 的方法主体:

protected void onPause() {
    if (DEBUG_LIFECYCLE) Slog.v(TAG, "onPause " + this);

    // This is to invoke 
    // Application.ActivityLifecyleCallbacks.onActivityPaused(Activity)
    getApplication().dispatchActivityPaused(this);

    // The flag to enforce calling of this method
    mCalled = true;
}

无论你用哪种方式把电话夹在中间,你都会没事的。Activity.onStop() 有一个类似的方法主体。但看看Active.onDestroy():super.onPause()

protected void onDestroy() {
    if (DEBUG_LIFECYCLE) Slog.v(TAG, "onDestroy " + this);
    mCalled = true;

    // dismiss any dialogs we are managing.
    if (mManagedDialogs != null) {
        final int numDialogs = mManagedDialogs.size();
        for (int i = 0; i < numDialogs; i++) {
            final ManagedDialog md = mManagedDialogs.valueAt(i);
            if (md.mDialog.isShowing()) {
                md.mDialog.dismiss();
            }
        }
        mManagedDialogs = null;
    }

    // close any cursors we are managing.
    synchronized (mManagedCursors) {
        int numCursors = mManagedCursors.size();
        for (int i = 0; i < numCursors; i++) {
            ManagedCursor c = mManagedCursors.get(i);
            if (c != null) {
                c.mCursor.close();
            }
        }
        mManagedCursors.clear();
    }

    // Close any open search dialog
    if (mSearchManager != null) {
        mSearchManager.stopSearch();
    }

    getApplication().dispatchActivityDestroyed(this);
}

在这里,排序可能很重要,具体取决于您的活动的设置方式,以及调用是否会干扰后面的代码。super.onDestroy()

作为最后一句话,该声明似乎没有太多证据支持它。更糟糕的是(对于该语句)是以下代码取自:Always call the superclass method firstandroid.app.ListActivity

public class ListActivity extends Activity {

    ....

    @Override
    protected void onDestroy() {
        mHandler.removeCallbacks(mRequestFocus);
        super.onDestroy();
    }
    ....    
}

而且,从Android sdk中包含的LunarLander示例应用程序中:

public class LunarLander extends Activity {

    ....

    @Override
    protected void onPause() {
        mLunarView.getThread().pause(); // pause game when Activity pauses
        super.onPause();
    }
    ....
}

总结和值得一提:

用户 Philip Sheard :提供一个场景,在使用 开始活动的情况下,必须延迟对 的调用。使用 after 设置结果将不起作用。他后来在回答的评论中澄清了这一点。super.onPause()startActivityForResult(Intent)setResult(...)super.onPause()

用户Sherif elKhatib :解释为什么让超类首先初始化其资源,最后销毁其资源遵循逻辑:

让我们考虑一个您下载的库,它具有一个LocationActivity,其中包含提供位置的getLocation()函数。最有可能的是,此活动需要在 onCreate() 中初始化其内容,这将强制您首先调用 super.onCreate。你已经这样做了,因为你觉得这是有道理的。现在,在 onDestroy 中,您决定要将“位置”保存在“共享首选项”中的某个位置。如果先调用 super.onDestroy,则 getLocation 在一定程度上可能会在此调用后返回空值,因为 LocationActivity 的实现会使 onDestroy 中的位置值无效。这个想法是,如果发生这种情况,你不会责怪它。因此,在完成自己的 onDestroy 之后,您会在最后调用 super.onDestroy。

他接着指出:如果子类与父类适当地隔离(就资源依赖性而言),则调用不需要遵循任何顺序规范。super.X()

在此页面上查看他的答案,以通读调用放置确实影响程序逻辑的场景。super.onDestroy()

从马克的回答中

您重写的组件创建方法(onCreate()、onStart()、onResume() 等),您应该链接到超类作为第一个语句,以确保 Android 在您尝试执行依赖于已完成的工作之前有机会完成其工作。

你重写的那些是组件破坏(onPause()、onStop()、onDestroy()等)一部分的方法,你应该先做你的工作,最后一件事就是链接到超类。这样,如果Android清理了您的工作所依赖的内容,您将首先完成工作。

返回 void 以外的内容的方法(onCreateOptionsMenu() 等),有时你会链接到 return 语句中的超类,假设你没有专门执行需要强制特定返回值执行的操作。

其他一切 - 例如onActivityResult() - 总体上取决于你。我倾向于将链接到超类作为第一件事,但除非你遇到问题,否则以后的链接应该没问题。

来自此线程Bob Kerns

这是一个很好的模式[(Mark上面建议的模式)],但我发现了一些例外。例如,我想应用于我的OptiveActivity的主题不会生效,除非我把它放在超类的onCreate()之前。

用户Steve Benett也对此表示关注:

我只知道一种情况,其中超级呼叫的时机是必要的。如果要在 onCreate 中更改主题或显示的标准行为等,则必须在调用 super 以查看效果之前执行此操作。否则,AFAIK没有区别,您何时调用它。

用户 Sunil Mishra 确认,在调用 Activity class 的方法时,顺序(最有可能)不起作用。他还声称,首先调用超类方法被认为是一种最佳实践。但是,我无法证实这一点。

用户LOG_TAG:解释为什么对超类构造函数的调用需要先于其他一切。在我看来,这种解释并没有增加所提出的问题。

尾注:信任,要验证。此页面上的大多数答案都遵循这种方法,以查看该语句是否具有逻辑支持。事实证明,事实并非如此。至少,在类活动的情况下不是。通常,应该通读超类的源代码,以确定是否需要对 super 的方法进行排序调用。Always call the superclass method first


答案 2

因为(你说)首先调用super onCreate是有意义的:想想看。

当我想创建时,我的超级创建其资源>我创建我的资源。

相反:(有点像堆栈)

當我想毀滅時,我摧毀我的資源,>我的超級摧毀他的資源。


从这个意义上说,它适用于任何几个函数(onCreate/onDestroy,onResume/onPause,onStart/onStop)。当然,onCreate 将创建资源,onDestroy 将释放这些资源。顺便说一句,同样的证据也适用于其他夫妇。

让我们考虑一个您下载的库,它具有一个LocationActivity,其中包含提供位置的getLocation()函数。最有可能的是,此活动需要在 onCreate() 中初始化其内容,这将强制您首先调用 super.onCreate。你已经这样做了,因为你觉得这是有道理的。现在,在 onDestroy 中,您决定要将“位置”保存在“共享首选项”中的某个位置。如果先调用 super.onDestroy,则 getLocation 在一定程度上可能会在此调用后返回空值,因为 LocationActivity 的实现会使 onDestroy 中的位置值无效。这个想法是,如果发生这种情况,你不会责怪它。因此,在完成自己的 onDestroy 之后,您会在最后调用 super.onDestroy。我希望这有点道理。

如果上述内容有意义,请考虑在任何时候我们都有遵守上述概念的活动。如果我想扩展这个活动,我可能会有同样的感觉,并遵循相同的顺序,因为相同的参数。

通过归纳,任何活动都应该做同样的事情。对于强制遵循这些规则的活动,下面是一个很好的抽象类:

package mobi.sherif.base;

import android.app.Activity;
import android.os.Bundle;

public abstract class BaseActivity extends Activity {
    protected abstract void doCreate(Bundle savedInstanceState);
    protected abstract void doDestroy();
    protected abstract void doResume();
    protected abstract void doPause();
    protected abstract void doStart();
    protected abstract void doStop();
    protected abstract void doSaveInstanceState(Bundle outState);
    @Override
    protected final void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        doCreate(savedInstanceState);
    }
    @Override
    protected final void onDestroy() {
        doDestroy();
        super.onDestroy();
    }
    @Override
    protected final void onResume() {
        super.onResume();
        doResume();
    }
    @Override
    protected final void onPause() {
        doPause();
        super.onPause();
    }
    @Override
    protected final void onStop() {
        doStop();
        super.onStop();
    }
    @Override
    protected final void onStart() {
        super.onStart();
        doStart();
    }
    @Override
    protected final void onSaveInstanceState(Bundle outState) {
        doSaveInstanceState(outState);
        super.onSaveInstanceState(outState);
    }
}

最后,如果你的活动扩展了BaseActivity,后来,我想创建扩展你的活动,该怎么办?我应该以什么顺序调用这些函数?这最终是一回事。AnudeepBullaActivitySherifElKhatibActivitysuper.do


至于你的问题:

我认为谷歌的意图是告诉我们:无论在哪里,请打电话给超级。当然,作为一般做法,请在开始时调用它。谷歌当然拥有最聪明的工程师和开发人员,所以他们可能做得很好,隔离了他们的超级通话,不干扰孩子的通话。

我尝试了一下,可能并不容易(因为它是谷歌,我们试图证明是错误的)创建一个活动,这个活动会因为什么时候超级被调用而崩溃。

为什么?

在这些函数中执行的任何内容实际上对 Activity 类都是私有的,并且永远不会与子类产生任何冲突。例如(在驱逐舰上)

protected void onDestroy() {
    if (DEBUG_LIFECYCLE) Slog.v(TAG, "onDestroy " + this);
    mCalled = true;

    // dismiss any dialogs we are managing.
    if (mManagedDialogs != null) {
        final int numDialogs = mManagedDialogs.size();
        for (int i = 0; i < numDialogs; i++) {
            final ManagedDialog md = mManagedDialogs.valueAt(i);
            if (md.mDialog.isShowing()) {
                md.mDialog.dismiss();
            }
        }
        mManagedDialogs = null;
    }

    // close any cursors we are managing.
    synchronized (mManagedCursors) {
        int numCursors = mManagedCursors.size();
        for (int i = 0; i < numCursors; i++) {
            ManagedCursor c = mManagedCursors.get(i);
            if (c != null) {
                c.mCursor.close();
            }
        }
        mManagedCursors.clear();
    }

    // Close any open search dialog
    if (mSearchManager != null) {
        mSearchManager.stopSearch();
    }

    getApplication().dispatchActivityDestroyed(this);
}

mManagedCursors 和 mManagedDialogs 以及 mSearchManager 都是私有字段。任何公共/受保护的 api 都不会受到此处所做操作的影响。

但是,在 API 14 中,添加了 dispatchActivityDestroyed 以调度 onActivityDestroyed 到注册到应用程序的 ActivityLifecycleCallbacks。因此,任何依赖于 ActivityLifecycleCallbacks 中某些逻辑的代码都会根据您调用 super 的时间产生不同的结果。例如:

创建一个计算当前正在运行的活动数的应用程序类:

package mobi.shush;

import android.app.Activity;
import android.app.Application;
import android.app.Application.ActivityLifecycleCallbacks;
import android.os.Bundle;

public class SherifApplication extends Application implements ActivityLifecycleCallbacks {
    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(this);
    }
    public int getCount() {
        return count;
    }
    int count = 0;
    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        count++;
    }
    @Override
    public void onActivityDestroyed(Activity activity) {
        count--;
    }
    @Override
    public void onActivityPaused(Activity activity) {}
    @Override
    public void onActivityResumed(Activity activity) {}
    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState)           {}
    @Override
    public void onActivityStarted(Activity activity) {}
    @Override
    public void onActivityStopped(Activity activity) {}
}

以下可能没有意义,或者不是一个好的做法,但它只是为了证明一个观点(人们可能会找到一个更真实的情况)。创建 MainActivity,该活动应该在完成时以及最后一个活动时转到 GoodBye 活动:

@Override
protected void onDestroy() {
    super.onDestroy();
    if(((SherifApplication) getApplication()).getCount() == 0) {
        //i want to go to a certain activity when there are no other activities
        startActivity(new Intent(this, GoodBye.class));
    }
}

如果您在 onDestroy 开始时调用 super.onDestroy,则将启动 GoodBye 活动。如果您在 onDestroy 结束时调用 super.onDestroy,则不会启动 GoodBye 活动。

当然,同样,这不是最佳示例。然而,这表明谷歌在这里搞砸了一点。任何其他变量都不会影响应用的行为。然而,将这些调度添加到 onDestroy 会导致 super 以某种方式干扰你的子类。

我说他们也因为不同的原因搞砸了。他们(在api 14之前)不仅在超级调用中触及最终和/或私有,而且还调用了不同的内部函数(私有),然后真正调度onPause...功能。

例如,function 是调用的函数,它反过来调用 onStop 函数:performStop

final void performStop() {
    if (mLoadersStarted) {
        mLoadersStarted = false;
        if (mLoaderManager != null) {
            if (!mChangingConfigurations) {
                mLoaderManager.doStop();
            } else {
                mLoaderManager.doRetain();
            }
        }
    }

    if (!mStopped) {
        if (mWindow != null) {
            mWindow.closeAllPanels();
        }

        if (mToken != null && mParent == null) {
            WindowManagerGlobal.getInstance().setStoppedState(mToken, true);
        }

        mFragments.dispatchStop();

        mCalled = false;
        mInstrumentation.callActivityOnStop(this);
        if (!mCalled) {
            throw new SuperNotCalledException(
                    "Activity " + mComponent.toShortString() +
                    " did not call through to super.onStop()");
        }

        synchronized (mManagedCursors) {
            final int N = mManagedCursors.size();
            for (int i=0; i<N; i++) {
                ManagedCursor mc = mManagedCursors.get(i);
                if (!mc.mReleased) {
                    mc.mCursor.deactivate();
                    mc.mReleased = true;
                }
            }
        }

        mStopped = true;
    }
    mResumed = false;
}

请注意,他们在此函数中的某个位置调用活动的 onStop。因此,他们可能已经将所有代码(包含在super.onStop中)放在调用onStop之前或之后,然后使用空的onStop超函数通知子类有关onStop的信息,甚至不添加SuperNotCalledException或检查这个调用。

为此,如果他们在performdestroy中将此调度调用到ActiveLifeCycle,而不是在super.onDestroy的末尾调用它,那么无论我们何时调用super,我们的活动的行为都是相同的。

无论如何,这是他们做的第一件事(有点错误),而且只是在API 14中。


推荐