你不知道的 Activity 生命周期

图片

一、前言

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

二、常见的生命周期函数

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

图片

图 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 所示:

图片

图 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 所示:

图片

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

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

图片

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

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

图片

图 3-4 切换回 FirstChildActivity 生命周期流程图

这个时候 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]https://developer.android.com/guide/components/activities/activity-lifecycle

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值