你不知道的 Activity 生命周期

1,109 阅读8分钟

一、前言

对于 Android 开发者而言,Activity 生命周期执行顺序并不陌生。但总是如此吗?它的生命周期会不会在某些场景下发生改变呢?下面我们一起来看看,这些熟悉的东西为何不一样,我们又有什么应对策略。

二、常见的生命周期函数

Google 官网提供的 Activity 生命周期流程如图 2-1 所示:

image.png

图 2-1 Activity 生命周期流程图[1] 首先我们来看看常见的生命周期函数,有 onCreate()、onStart()、onResume()、onPause()、onStop()、onDestory() 等:

  1. onCreate() 是在系统首次创建 Activity 时触发,该回调在 Activity 的整个生命周期中只应该发生一次。onCreate() 方法执行完成后,Activity 进入 “已开始” 状态,系统会触发 onStart() 回调;

  2. onStart() 表示 “已开始” 状态。一旦该方法被调用,代表 Activity 对用户可见,应用会为 Activity 进入前台并且与用户互动做准备。onStart() 会非常快速的完成,一旦结束此回调,Activity 便会进入 “已恢复” 状态,系统将调用 onResume() 方法;

  3. onResume() 是应用与用户互动的状态,我们称为 “已恢复” 状态。应用会一直保持这种状态,直到某些事情发生,让焦点远离应用。此类事件包括:接到来电、用户导航到另一个 Activity、设备屏幕关闭等。当发生中断事件时,Activity 进入 “已暂停” 状态,系统调用 onPause() 回调;

  4. onPause() 视为用户将要离开 Activity 的第一个标志(尽管这并不总是意味着 Activity 会被销毁),onPause() 方法的完成并不意味着 Activity 离开 “已暂停” 状态。相反,Activity 会保持此状态,直到其恢复或变成对用户完全不可见。如果 Activity 恢复,系统将再次调用 onResume() 回调。如果 Activity 从 “已暂停” 状态返回 “已恢复” 状态,系统会让 Activity 实例继续驻留在内存中,并调用该实例的 onResume() 方法。如果 Activity 变为完全不可见,系统会调用 onStop() 方法;

  5. 如果 Activity 不再对用户可见,说明进入了 “已停止” 状态,因此系统将触发 onStop() 回调。例如:当新启动的 Activity 覆盖整个屏幕时,可能会发生这种情况。在 onStop() 方法中,应用应释放或调整对用户不可见时的无用资源,同时还应在 onStop() 中执行 CPU 相对密集的关闭操作。进入“已停止” 状态后,Activity 要么返回与用户互动,要么结束运行并消失。如果 Activity 返回,系统将调用 onRestart()。如果 Activity 结束运行,系统将调用 onDestroy();

  6. 在销毁 Activity 之前,系统会先调用 onDestroy()。如果 Activity 即将结束,onDestroy() 是 Activity 收到的最后一个生命周期回调。如果由于配置变更而调用 onDestroy(),系统会立即新建 Activity 实例,然后在新配置中为新实例调用 onCreate()。

上面介绍了常见情况下 Activity 的生命周期,但当我们使用 TabActivity 时却不一样,下面我们一起来看看。

三、TabActivity

3.1 如何使用

TabActivity 作用主要是使每个 Tab 能对应一个 Activity(当然现在一般是使用 Fragment),效果如图 3-1 所示:

image.png 图 3-1 使用 TabActivity 效果图 我们现在来看如何使用 TabActivity。

首先,定义一个页面 ParentActivity,让它继承 TabActivity,然后获取 TabActivity 中的 TabHost,示例代码如下:


public class ParentActivity extends TabActivity {
    private TabHost mTabHost;
    private final static String TAG = "SA.ParentActivity";
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d(TAG, "onCreate");
        //获取 TabActivity 中的 TabHost
        mTabHost = getTabHost();
        //这里用于添加具体的 Tabs,并用 Tab 切换到对应的 Activity
        addFirstTab();
        addSecondTab();
        addThirdTab();
        addFourthTab();
    }
 
    public void addFirstTab(){
        Intent intent = new Intent();
        intent.setClass(ParentActivity.this, FirstChildActivity.class);
        TabHost.TabSpec spec = mTabHost.newTabSpec("One");
        spec.setIndicator("one", null);
        spec.setContent(intent);
        mTabHost.addTab(spec);
    }
 
    public void addSecondTab(){
        Intent intent = new Intent();
        intent.setClass(ParentActivity.this, SecondChildActivity.class);
        TabHost.TabSpec spec = mTabHost.newTabSpec("Two");
        spec.setIndicator("two", null);
        spec.setContent(intent);
        mTabHost.addTab(spec);
    }
    public void addThirdTab(){
        Intent intent = new Intent();
        intent.setClass(ParentActivity.this, ThirdChildActivity.class);
        TabHost.TabSpec spec = mTabHost.newTabSpec("Three");
        spec.setIndicator("three", null);
        spec.setContent(intent);
        mTabHost.addTab(spec);
    }
    public void addFourthTab(){
        Intent intent = new Intent();
        intent.setClass(ParentActivity.this, FourthChildActivity.class);
        TabHost.TabSpec spec = mTabHost.newTabSpec("Four");
        spec.setIndicator("four", null);
        spec.setContent(intent);
        mTabHost.addTab(spec);
    }
 
}

然后,创建 FirstChildActivity 等页面,这里正常创建即可,不再赘述。

现在进入正题,我们在 ParentActivity 及每个 ChildActivity(FirstChildActivity、SecondChildActivity 等每个子页面的统称)中的生命周期函数打印日志。

当我们切换 ChildActivity 时,会看到生命周期与正常情况下打开 Activity 的生命周期不一样,下面我们进一步分析原因。

3.2生命周期详情

我们来看看在不同情况下,ParentActivity 和每个 ChildActivity 的生命周期。在冷启动时,默认选中 FirstChildActivity,生命周期流程如图 3-2 所示:

image.png 图 3-2 冷启动生命周期流程图

可以看到,冷启动时两个 Activity 的生命周期交替执行。我们再切换到 SecondChildActivity,生命周期流程如图 3-3 所示:

image.png 图 3-3 首次切换到 SecondActivity 生命周期流程图

此时 FirstChildActivity 的 onStop 生命周期就不会执行了,最后我们再从 SecondChildActivity 切回到 FirstChildActivity,如图 3-4 所示:

image.png

这个时候 FirstChildActivity 的 onStart 生命周期不会执行,SecondChildActivity 的 onStop 也不会执行。明显与正常情况下的生命周期不一致,接下来我们一起分析原因。

3.3源码分析

为什么使用 TabActivity 时,会有不一样的 Activity 生命周期呢?下面我们一起通过源码分析下原因。首先来看看 TabActivity 的源码。


public class TabActivity extends ActivityGroup {
    private TabHost mTabHost;
 
    // ...
    @Override
    public void onContentChanged() {
        super.onContentChanged();
        // 1.初始化 TabHost
        mTabHost = findViewById(com.android.internal.R.id.tabhost);
 
        if (mTabHost == null) {
            throw new RuntimeException(
                    "Your content must have a TabHost whose id attribute is " +
                    "'android.R.id.tabhost'");
        }
        // 2.TabHost 与 LocalActivityManager 绑定
        mTabHost.setup(getLocalActivityManager());
    }
 
    private void ensureTabHost() {
        if (mTabHost == null) {
            this.setContentView(com.android.internal.R.layout.tab_content);
        }
    }
 
    @Override
    protected void onPostCreate(Bundle icicle) {       
        super.onPostCreate(icicle);
 
        ensureTabHost();
 
        if (mTabHost.getCurrentTab() == -1) {
            // 3.默认选中第一个 Tab
            mTabHost.setCurrentTab(0);
        }
    }
 
    public TabHost getTabHost() {
        ensureTabHost();
        return mTabHost;
    }
}

在 TabActivity 中,有三个关键点需要注意:

  1. 在 onContentChanged() 方法中初始化了 TabHost;

  2. 通过 getLocalActivityManager() 方法获取父类 ActivityGroup 中初始化的 LocalActivityManager,并且在 TabActivity 中通过 TabHost#setup() 方法绑定了 TabHost 和 LocalActivityManager;

  3. 在 onPostCreate() 方法中,通过 mTabHost.setCurrentTab(0) 默认选中了第一个 Tab。

这里我们仍然没有看到 Activity 生命周期不一样的原因,其父类 ActivityGroup 也只是初始化了 LocalActivityManager 对象,接下来重点看看 mTabHost.setCurrentTab(0) 这一步做了什么。


public class TabHost extends FrameLayout implements ViewTreeObserver.OnTouchModeChangeListener {
 
    //...
    public void setCurrentTab(int index) {
        // ...
        // 关掉上一个页面
        if (mCurrentTab != -1) {
            mTabSpecs.get(mCurrentTab).mContentStrategy.tabClosed();
        }
 
        mCurrentTab = index;
        // 获取当前页面的 view
        mCurrentView = spec.mContentStrategy.getContentView();
 
        if (mCurrentView.getParent() == null) {
            mTabContent
                    .addView(
                            mCurrentView,
                            new ViewGroup.LayoutParams(
                                    ViewGroup.LayoutParams.MATCH_PARENT,
                                    ViewGroup.LayoutParams.MATCH_PARENT));
        }
 
        // ...
    }
}

这里利用 TabHost.IntentContentStrategy#getContentView() 获取了当前页面的 View,那么 getContentView() 中又做了什么呢,我们来看看它的源码。


public View getContentView() {
    if (mLocalActivityManager == null) {
        throw new IllegalStateException("Did you forget to call 'public void setup(LocalActivityManager activityGroup)'?");
    }
    // 利用 LocalActivityManager 启动了 Activity
    final Window w = mLocalActivityManager.startActivity(
            mTag, mIntent);
    final View wd = w != null ? w.getDecorView() : null;
    if (mLaunchedView != wd && mLaunchedView != null) {
        if (mLaunchedView.getParent() != null) {
            mTabContent.removeView(mLaunchedView);
        }
    }
    mLaunchedView = wd;
    // ...
    return mLaunchedView;
}

利用 LocalActivityManager 启动了 Activity,我们接下来看看 LocalActivityManager 的 startActivity() 方法是如何实现的。


public Window startActivity(String id, Intent intent) {
    // ...
    // 在 ActivityGroup 初始化 LocalActivityManager 传入变量 mSingleMode
    if (mSingleMode) {
        LocalActivityRecord old = mResumed;
  
        if (old != null && old != r && mCurState == RESUMED) {
            // 改变 FirstChildActivity 页面的状态
            moveToState(old, STARTED);
        }
    }
    // ...
    // 改变 SecondChildActivity 页面的状态
    moveToState(r, mCurState);
    if (mSingleMode) {
        mResumed = r;
    }
    return r.window;
}

上面展示了部分 startActivity 的源码。其中,mSingleMode 是在 ActivityGroup 初始化 LocalActivityManager 时传入的变量,且始终为 true。假设现在从 FirstChildActivity 切换到了 SecondChildActivity,那么首先利用 moveToState() 方法改变了 FirstChildActivity 的状态,紧接着修改了 SecondChildActivity 状态。

真相应该在 moveToState() 方法中了,我们来看看 moveToState() 的实现。


private void moveToState(LocalActivityRecord r, int desiredState) {
    //...
    // 如果是第一次启动 ChildActivity
    if (r.curState == INITIALIZING) {
        // 启动当前 ChildActivity
        r.activity = mActivityThread.startActivityNow(
                mParent, r.id, r.intent, r.activityInfo, r, r.instanceState, instance);
        if (r.activity == null) {
            return;
        }
          
        r.curState = STARTED;
        // ...
        return;
    }
    // 假设从 FirstChildActivity 切换到了 SecondChildActivity
    switch (r.curState) {
        //...
        //SecondChildActivity 的状态,由 STARTED 到 RESUMED
        case STARTED:
            if (desiredState == RESUMED) {
                // Need to resume it...
                if (localLOGV) Log.v(TAG, r.id + ": resuming");
                mActivityThread.performResumeActivity(r, true, "moveToState-STARTED");
                r.instanceState = null;
                r.curState = RESUMED;
            }
            // ...
            return;
         // FirstChildActivity 的状态,由 RESUMED 到 STARTED
        case RESUMED:
            if (desiredState == STARTED) {
                if (localLOGV) Log.v(TAG, r.id + ": pausing");
                performPause(r, mFinishing);
                r.curState = STARTED;
            }
            // ...
            return;
    }
}

在 moveToState() 方法中,分为首次启动当前 ChildActivity 还是非首次启动的情况:

  1. 在首次启动时,调用 ActivityThread#startActivityNow() 方法启动 Activity;

  2. 当非首次进入 ChildActivity 时,还是假设从 FirstChildActivity 切换到了 SecondChildActivity。FirstChildActivity 会调用 performPause 方法并触发 onPause() 生命周期,而 SecondChildActivity 会调用 performResumeActivity 方法,只会触发 onResume() 生命周期。

看到这里,就解释了为什么利用 TabActivity 的生命周期会如此不一样,原来都是 ActivityGroup 中 LocalActivityManager 的 “错”。

3.4 小结

我们来总结一下,为什么在 Activity 的不同阶段,会触发 Activity 的生命周期函数。通过我们上面的分析,也可以窥知一二,原因都是系统调用了触发生命周期的相关方法。如果在某些情况下,系统没有调用这些方法,那么生命周期函数就 “失灵” 了,这也是使用 TabActivity 导致生命周期不一致的根本原因。

TabActivity 如此特别,我们有方法判断它吗?答案是有的。

我们可以回顾一下 LocalActivityManager 启动 Activity 的过程:调用 ActivityThread#startActivityNow() 方法时,传入了 ParentActivity 参数。而我们平常利用 ContextWrapper#startActivity() 的方式启动 Activity,此时的 Activity 是没有 ParentActivity 的。因此,可以利用 Activity#getParent() 方法判断当前 Activity 有无 Parent,从而判断出此种特殊情况。

四、总结

本文介绍了 Activity 的生命周期,然后以 TabActivity 为例介绍了一种不一样的生命周期。最后,也欢迎大家一起讨论、学习。所谓君子之学,未尝离行以为知也,必矣。

参考文献:

[1]developer.android.com/guide/compo…