前言
对于 Android 开发者而言,Activity 生命周期执行顺序并不陌生。但总是如此吗?它的生命周期会不会在某些场景下发生改变呢?下面我们一起来看看,这些熟悉的东西为何不一样,我们又有什么应对策略。
常见的生命周期函数
Google 官网提供的 Activity 生命周期流程如图 2-1 所示:
图 2-1 Activity 生命周期流程图(https://developer.android.com/guide/components/activities/activity-lifecycle)
首先我们来看看常见的生命周期函数,有 onCreate()、onStart()、onResume()、onPause()、onStop()、onDestory() 等:
- onCreate() 是在系统首次创建 Activity 时触发,该回调在 Activity 的整个生命周期中只应该发生一次。onCreate() 方法执行完成后,Activity 进入 “已开始” 状态,系统会触发 onStart() 回调;
- onStart() 表示 “已开始” 状态。一旦该方法被调用,代表 Activity 对用户可见,应用会为 Activity 进入前台并且与用户互动做准备。onStart() 会非常快速的完成,一旦结束此回调,Activity 便会进入 “已恢复” 状态,系统将调用 onResume() 方法;
- onResume() 是应用与用户互动的状态,我们称为 “已恢复” 状态。应用会一直保持这种状态,直到某些事情发生,让焦点远离应用。此类事件包括:接到来电、用户导航到另一个 Activity、设备屏幕关闭等。当发生中断事件时,Activity 进入 “已暂停” 状态,系统调用 onPause() 回调;
- onPause() 视为用户将要离开 Activity 的第一个标志(尽管这并不总是意味着 Activity 会被销毁),onPause() 方法的完成并不意味着 Activity 离开 “已暂停” 状态。相反,Activity 会保持此状态,直到其恢复或变成对用户完全不可见。如果 Activity 恢复,系统将再次调用 onResume() 回调。如果 Activity 从 “已暂停” 状态返回 “已恢复” 状态,系统会让 Activity 实例继续驻留在内存中,并调用该实例的 onResume() 方法。如果 Activity 变为完全不可见,系统会调用 onStop() 方法;
- 如果 Activity 不再对用户可见,说明进入了 “已停止” 状态,因此系统将触发 onStop() 回调。例如:当新启动的 Activity 覆盖整个屏幕时,可能会发生这种情况。在 onStop() 方法中,应用应释放或调整对用户不可见时的无用资源,同时还应在 onStop() 中执行 CPU 相对密集的关闭操作。进入“已停止” 状态后,Activity 要么返回与用户互动,要么结束运行并消失。如果 Activity 返回,系统将调用 onRestart()。如果 Activity 结束运行,系统将调用 onDestroy();
- 在销毁 Activity 之前,系统会先调用 onDestroy()。如果 Activity 即将结束,onDestroy() 是 Activity 收到的最后一个生命周期回调。如果由于配置变更而调用 onDestroy(),系统会立即新建 Activity 实例,然后在新配置中为新实例调用 onCreate()。
上面介绍了常见情况下 Activity 的生命周期,但当我们使用 TabActivity 时却不一样,下面我们一起来看看。
TabActivity
如何使用
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 的生命周期不一样,下面我们进一步分析原因。
生命周期详情
我们来看看在不同情况下,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 也不会执行。明显与正常情况下的生命周期不一致,接下来我们一起分析原因。
源码分析
为什么使用 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 中,有三个关键点需要注意:
- 在 onContentChanged() 方法中初始化了 TabHost;
- 通过 getLocalActivityManager() 方法获取父类 ActivityGroup 中初始化的 LocalActivityManager,并且在 TabActivity 中通过 TabHost#setup() 方法绑定了 TabHost 和 LocalActivityManager;
- 在 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 还是非首次启动的情况:
- 在首次启动时,调用 ActivityThread#startActivityNow() 方法启动 Activity;
- 当非首次进入 ChildActivity 时,还是假设从 FirstChildActivity 切换到了 SecondChildActivity。FirstChildActivity 会调用 performPause 方法并触发 onPause() 生命周期,而 SecondChildActivity 会调用 performResumeActivity 方法,只会触发 onResume() 生命周期。
看到这里,就解释了为什么利用 TabActivity 的生命周期会如此不一样,原来都是 ActivityGroup 中 LocalActivityManager 的 “错”。
小结
我们来总结一下,为什么在 Activity 的不同阶段,会触发 Activity 的生命周期函数。通过我们上面的分析,也可以窥知一二,原因都是系统调用了触发生命周期的相关方法。如果在某些情况下,系统没有调用这些方法,那么生命周期函数就 “失灵” 了,这也是使用 TabActivity 导致生命周期不一致的根本原因。
TabActivity 如此特别,我们有方法判断它吗?答案是有的。
我们可以回顾一下 LocalActivityManager 启动 Activity 的过程:调用 ActivityThread#startActivityNow() 方法时,传入了 ParentActivity 参数。而我们平常利用 ContextWrapper#startActivity() 的方式启动 Activity,此时的 Activity 是没有 ParentActivity 的。因此,可以利用 Activity#getParent() 方法判断当前 Activity 有无 Parent,从而判断出此种特殊情况。
总结
本文介绍了 Activity 的生命周期,然后以 TabActivity 为例介绍了一种不一样的生命周期。最后,也欢迎大家一起讨论、学习。所谓君子之学,未尝离行以为知也,必矣。