95

我刚刚浏览了Android Developer Site,刷新了Activity生命周期,在每个代码示例中,超类方法旁边都有一条注释,上面写着“始终首先调用超类方法”。

虽然这在创建半周期: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();  
    }
4

7 回答 7

111

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

在我看来:不是一件事。

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

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

回到原点。好吧,让我们从另一个角度来看这个。我们知道 Java 语言规范没有指定调用的顺序super.overridenMethod()(或者是否必须进行调用)。

在 Activity 类的情况下,super.overridenMethod()需要并强制执行调用:

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

mCalled中设置为真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;
}

无论您以哪种方式将呼叫夹在中间super.onPause(),您都会没事的。Activity.onStop() 有一个类似的方法体。但是看看 Activity.onDestroy():

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 first似乎没有太多证据支持它。更糟糕的是(对于声明)以下代码取自android.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:提供了一个场景,super.onPause()在 Activity 开始使用的情况下必须延迟调用startActivityForResult(Intent)setResult(...) 使用after 设置结果super.onPause()将不起作用。他后来在对他的回答的评论中澄清了这一点。

用户 Sherif elKhatib:解释为什么让超类首先初始化其资源并最后销毁其资源的逻辑如下:

让我们考虑您下载的一个库,它有一个 LocationActivity,其中包含一个提供位置的 getLocation() 函数。最有可能的是,此活动将需要在 onCreate() 中初始化其内容,这将迫使您首先调用 super.onCreate。你已经这样做了,因为你觉得这很有意义。现在,在您的 onDestroy 中,您决定要将 Location 保存在 SharedPreferences 中的某处。如果先调用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 在上面建议的模式)],但我发现了一些例外情况。例如, 我想应用到我的 PreferenceActivity 的主题不会生效,除非我把它放在超类的 onCreate() 之前。

用户 Steve Benett也注意到了这一点:

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

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

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

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

于 2013-09-18T14:09:48.577 回答
12

因为(你说)首先调用 super onCreate 是有意义的:考虑一下。

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

反过来:(某种堆栈)

当我想破坏时,我破坏了我的资源>我的超级破坏了他的资源。


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

让我们考虑您下载的一个库,它有一个 LocationActivity,其中包含一个提供位置的 getLocation() 函数。最有可能的是,这个活动需要在 onCreate() 中初始化它的东西,这将迫使你首先调用 super.onCreate。你已经这样做了,因为你觉得这很有意义。现在,在您的 onDestroy 中,您决定要将 Location 保存在 SharedPreferences 中的某处。如果先调用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);
    }
}

最后,如果您的活动被称为AnudeepBullaActivity扩展 BaseActivity 并且稍后,我想创建SherifElKhatibActivity扩展您的活动怎么办?我应该按什么顺序调用super.do函数?最终是一样的。


至于你的问题:

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

我尝试了一点,可能不容易(因为我们试图证明是谷歌是错误的)创建一个由于何时调用 super 而容易崩溃的活动。

为什么?

在这些函数中所做的任何事情都是 Activity 类真正私有的,并且永远不会与您的子类发生任何冲突。例如(onDestroy)

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 会导致超级以某种方式干扰您的子类。

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

例如,performStopfunction 是被调用的函数,它又调用 onStop 函数:

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;
}

请注意,他们在此函数的某处调用了 Activity 的 onStop。因此,他们也可以在调用 onStop 之前或之后放置所有代码(包含在 super.onStop 中),然后使用空的 onStop 超级函数通知子类关于 onStop,甚至无需添加 SuperNotCalledException 或检查此调用。

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

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

于 2013-09-18T11:54:41.300 回答
3

您说 Google 建议方法 1,但是著名的 Android 框架工程师 Dianne Hackborn 建议否则请参阅Google 论坛链接

在onPause、onStoponDestroy方法中销毁实例时最后调用超类以及使用onCreate、onResumeonStart方法创建实例时首先调用超类具有直观的意义。

于 2013-09-19T11:26:49.837 回答
1

两者都是正确的国际海事组织

根据文档

派生类必须调用该方法的超类实现。如果他们不这样做,将引发异常。

Super当文档明确说明时,应始终调用方法。

但是,您可以选择何时调用 super 方法。

看着源头onPause

protected void onPause() {
    getApplication().dispatchActivityPaused(this);
    mCalled = true;
}

因此,无论在它被调用之前还是之后。你应该很好。

但为了获得最佳实践,您应该先调用它。

我推荐它主要作为一种保护机制:如果有异常,那么super实例方法将已经被调用。

同样将这些调用放在第一行将有助于您避免将来犯错误,例如删除方法中的代码和不小心删除对超类的调用。

于 2013-09-16T06:40:36.960 回答
1

从 java 的角度来看,这里有一些解决这种混淆的方法:

为什么 this() 和 super() 必须是构造函数中的第一条语句?

父类的构造函数需要在子类的构造函数之前调用。这将确保如果您在构造函数中调用父类的任何方法,则父类已经正确设置。

您正在尝试做的事情,将 args 传递给超级构造函数是完全合法的,您只需要在执行时内联构造这些 args,或者将它们传递给您的构造函数,然后将它们传递给超级:

public MySubClassB extends MyClass {
        public MySubClassB(Object[] myArray) {
                super(myArray);
        }
}

如果编译器没有强制执行此操作,您可以这样做:

public MySubClassB extends MyClass {
        public MySubClassB(Object[] myArray) {
                someMethodOnSuper(); //ERROR super not yet constructed
                super(myArray);
        }
}

它表明实际上,子字段必须在超类之前初始化!同时,Java 需求通过专门化超级构造函数参数来“保护”我们免于专门化类

在父类具有默认构造函数的情况下,编译器会自动为您插入对 super 的调用。由于 Java 中的每个类都继承自 Object,因此必须以某种方式调用对象构造函数,并且必须首先执行它。编译器自动插入 super() 允许这样做。强制 super 首先出现,强制构造函数主体以正确的顺序执行,即: Object -> Parent -> Child -> ChildOfChild -> SoOnSoForth

(1) 检查 super 是第一个语句不足以防止该问题。例如,您可以输入“super(someMethodInSuper());” 在你的构造函数中。这会尝试在构造超类之前访问超类中的方法,即使 super 是第一条语句。

(2) 编译器似乎实现了一个不同的检查,它本身就足以防止这个问题。消息是“在调用超类型构造函数之前无法引用 xxx”。因此,检查 super 是否是第一个语句是没有必要的

请通过这个http://valjok.blogspot.in/2012/09/super-constructor-must-be-first.html

于 2013-09-18T12:05:28.527 回答
1

要记住的最重要的事情是super.onPause()隐式调用setResult(Activity.RESULT_CANCELED). 但是setResult只能调用一次,并且忽略所有后续调用。因此,如果您想将任何类型的结果推送回父活动,则必须在调用之前调用setResult自己。据我所知,这是最大的问题。super.onPause()

于 2013-09-18T15:17:51.727 回答
0

需要回调的 super 才能在系统内部将 Activity 置于正确的状态。

假设您启动了 Activity 并且系统调用了 onCreate。现在您可以覆盖它,例如加载您的布局。但是为了系统流程,您必须调用 super,系统才能继续执行标准程序。这就是为什么如果你不调用它会抛出异常的原因。

这与您在 onCreate 中的实现无关。它只是系统的重要部分。如果没有 ANR,您可能会在任何回调中出现无限循环,并且 Activity 将被捕获。因此,系统知道回调何时终止,然后调用下一个。

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

但是让系统做它可以做的最好的事情是把 super 放在回调的第一行,然后是你的代码,如果你没有很好的理由打破它。

于 2013-09-16T10:06:26.207 回答