第4章 Activity的生命周期
当我们在屏幕上发生一些事件,比如点击某个按钮用来切换Activity或来电情况等,都会触发Activity的生命周期事件,为什么我们要了解 Activity的生命周期呢?因为了解了它的生命周期我们才能根据不同的情况来处理我们的应用,让我们的应用程序更好用,更人性化。例如,如果你建立一 个流媒体视频播放器,当用户切换到另一个应用程序,你可能会暂停视频和终止网络连接。当用户返回时,你可以重新连接到网络,并允许用户恢复从同一地点的录 像。这些行为都可以直接在Activity的生命周期方法中处理它们。可能有的读者以前在网上看过关于生命周期的解释。这里我会透彻的,详细的讲述它的原 理。让你的“角色等级”在上一个档次。下面是内容预览:
1.启动一个Activity
学习了解Activity生命周期的基本知识,用户创建和执行Activity是怎样调用生命周期的方法的
2.暂停和恢复一个Activity
了解Activity暂停(paused)和恢复(resumed)时发生了什么,在这些状态改变期间我们应该做什么
3.停止和重新启动Activity
当activity完成它的工作后发生了什么
4.重新创建Activity
当你的Activity被destroyed(摧毁)后发生了什么,并且学习如何能重新创建这个Activity
4.1 启动一个Activity
不同于其他程序范例用main()方法启动,Android系统启动Activity通过在不同的生命周期阶段调用指定的回调方法来完成初始化。
4.1.1了解生命周期的回调函数
在一个Activity的生命周期中,系统会根据一定顺序来调用一套核心的生命周期方法,就像阶梯金字塔一样,也就是说,Activity生命周期 的每一个阶段都是一个阶梯,由于系统创建了一个新的Activity实例,每一个回调方法会让Activity移向金字塔的顶端,当到达顶端时,用户就可 以与之交互了。当用户离开这个Activity时,会向下调用部分生命周期的方法。接下来我们看下图4-1:
图4-1 像金字塔一样的图,一些回调方法调用后会发生什么事,可以从图中看到
根据Activity的复杂性,你可能不需要实现所有的生命周期方法,然后你必须清楚了解每一个方法会在什么样情况下被调用,这样你才能正确处理一些用户的行为。你需要考虑以下情况来灵活实现一些生命周期的方法:
- 如果用户接到一个电话,或者切换到另一外程序,当继续使用你的应用程序时,它不会崩溃。
- 当用户没有积极的使用它时,不消耗宝贵的系统资源
- 如果用户离开你的应用程序并在稍后返回,不要丢失用户的进度
- 当屏幕横向或纵向旋转时,不会崩溃或丢失用户进度
仔细观察,我们会发现图4-1中有三个状态时持续存在一段时间的
1.Resumed(恢复)
在这种状态下,Activity在前台并且可以与用户交互。(有时也可称为running状态)
2.Paused(暂停)
在这种状态下,Activity被另一个Activity部分遮挡。暂停状态下的Activity不接受用户输入,并不能执行任何代码。
3.Stopped(停止)
在这种状态下,Activity完全隐藏对用户不可见,它被放入后台,虽然它停止了,但所有成员变量都被保留,这种状态下不可见,也不能执行代码。
其他状态(创建和启动)是短暂的,也就是说当系统调用onCreate(),onStart(),onRestart()时就瞬间的行为。这就是Activity的生命周期,现在让我们学习一些具体的生命周期行为。
4.1.2 指定应用程序启动的Activity
当用户从home屏幕选中你的App图标,系统会为你已经声明的“launcher(启动)”Activity调用onCreate()方法,这个 Activity为你应用程序的主入口点。你可以在AndroidManifest.xml声明需要启动的Activity,如代码清单4-1所示:
<activity android:name=".MainActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
代码清单4-1
需要说明的是使用Eclipse创建Android项目时,代码清单4-1的内容已经被自动创建好了。虽然它帮我们做好了,但我们需要了解这段代码 的意思。只要删除action.MAIN或category.LAUNCHER其中一个,那么你的应用程序图标将不会显示在home屏幕上,就是说这个程 序虽然安装了,但home屏幕上看不到。
4.1.3 创建一个新的实例
大部分App包含几个不同的Activities用来让用户执行不同的操作。当系统创建Activity实例的时候,会调用onCreate()方 法。Activity的整个生命只有一次。例如,你在实现的onCreate()方法中应该定义用户界面,和一些变量实例。以下代码清单4-2是使用 onCreate()的例子。
TextView mTextView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 为这个Activity设置用户界面布局 // layout 文件定义在res/layout/main_activity.xml setContentView(R.layout.main_activity); // 初始化成员TextView mTextView = (TextView) findViewById(R.id.text_message); //确保我们执行的机器上使用的系统为3.0或更高,因为只有这样才能使用ActionBar APIs if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { // 执行一些与ActionBar相关的操作 ActionBar actionBar = getActionBar(); actionBar.setHomeButtonEnabled(false); } }
代码清单4-2
一旦执行完毕的onCreate()系紧接着调用onStart()和onResume()方法。您的Activity从来没有停留在创建或启动状 态,从技术上讲Android在onStart()方法调用后就可见了,就由于这种片刻的行为,所以大部分时间都是停留在onResume()状态下的。 注意onCreate()中有一个savedInstanceState参数,这个当重新创建Activity时可以用到。
4.1.4 摧毁Activity
当前我们的Activity起始于onCreate()必然有一个对应的结束方法,那就是onDestroy()。当你的Activity实例需要 删除时,调用此方法来让虚拟机回收。可能我们并不经常实现这个方法。在调用onDestroy()之前系统会调用onPause()和onStop()。 顺序就和图4-1的金字塔一样,从上至下。
4.2 暂停和恢复一个Activity
在使用某个App的过程中,前台Activity有时会被其他可视化组件阻挡,导致Activity处于暂停状态。其实当你弹出一个对话框时,底部的Activity不会处于暂停状态。 当系统调用onPause()方法时,Activity进入暂停状态,例如在播放视频或音乐时,当由于一个来电而进入暂停状态的时候,你应该在 onPause()中使用代码暂停正好播放的视频或音乐。当系统调用onResume()方法后,Activity又回到了resume状态。
4.2.1 暂停Activity
当系统对你的Activity调用onPause()方法时,专业点来说你的Act仍然部分是可见的,但大多数迹象表明用户正在离开Act,很快进入停止状态。以下情况我们需要重写onPause()方法:
1.停止动画或其他消耗CPU之类的动作
2.提交未保存的更改,但只有用户希望这么做时才需要保存,例如正在编写电子邮件
3.释放系统资源,例如广播,传感器(例如GPS),或任意影响电池寿命的资源
例如, 如果我们正在使用摄像头,在暂停方法中应该释放它。如代码清单4-3所示:
@Override public void onPause() { super.onPause(); // 总是优先调用父类的方法 // 释放摄像头因为当暂停时,我们不需要它并且其他Act可能需要使用它 if (mCamera != null) { mCamera.release() mCamera = null; } }
代码清单4-3
我们应该在onPause()方法中执行相对简单的操作,以便迅速过渡到下一个目的地,例如写入数据库这种密集型CPU的操作,我们可以放入onStop()中处理。
4.2.2 恢复Activity
系统系统onResume()方法后当用户从暂停状态想回到resume状态。要知道系统每次调用这个方法,你的Act又会回到前台。某些资源需要你重新启动它。下面代码清单4-4就是与4-3对应的处理方式,这里相机需要初始化
@Override public void onResume() { super.onResume(); if (mCamera == null) { initializeCamera(); //自己编写的本地方法来处理相机的初始化 } }
代码清单4-4
4.3 停止和重新启动一个Activity
假设你有一个正在运行的程序,当用户点击home键切换到主屏幕时,此时你的Act会依次执行onPause()->onStop()。如果用户又 冲home屏幕点击你应用程序的图标的话,这时会重新启动Act来返回到我们的应用程序。还有一种情况,例如你的一个程序有两个Act,假设第一个为A, 第二个为B,当A跳转到B时,A会执行onStop()方法,然后进入B执行一些onCreate()等之类的方法,但当你在B中点击back键返回时, 这时A会执行reStart()方法来重新启动。与onPause()不同之处是onStop()会让你的Act失去焦点并且UI不再可见,下面我们看一 下图4-2:
图4-2 从stop状态到resume状态需要经过2,3,4步骤,而反之总是从pause->stop。
4.3.1 停止Activity
当系统调用onStop()方法时,act不再可见并且几乎释放掉所有用户不在使用的资源。一旦你的act停止了,如果需要恢复系统资源的话,系统 可能摧毁这个实例。极端的情况下,系统可以直接kill掉你的APP进程并且没有调用onDestroy()方法,所以onStop()方法很重要,你应 该在onStop()中释放资源,不然可能有内存泄露的风险。虽然onPause()方法在onStop()之前调用,但我们应该使用的onStop() 来执行更高,CPU密集型的关闭操作,如信息写入数据库。下面代码清单4-5就是一个例子:
@Override protected void onStop() { super.onStop(); // 保存当前内容的草稿,因为处于stop状态,我们要确保不会丢失当前内容的进度 ContentValues values = new ContentValues(); values.put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText()); values.put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle()); getContentResolver().update(mUri, values, null, null); }
代码清单4-5
如果用户在编辑一个EditText文本,该内容被自动保留,我们不需要保存和恢复。因为即使在onStop()状态 下,act实例依旧存在,则正在编写的EditText文本也在内存中。但是如果你按back键,离开程序(调用onDestroy),那么系统不会保存 你的View
的状态(例如在EditText中的文本)。
4.3.2 开始/重新开始Activity
当你的Act从停止状态回到前台,首先需要调用onRestart()方法,然后调用onStart()。只有在stop状态到resume状态才会调用onRestart(),它的特殊性可以让你在此方法中处理一些特别的操作。下面我们看下代码清单4-6:
@Override protected void onStart() { super.onStart(); // activity要么是重新开始要么第一次开始,所以我们需要确保GPS被打开 LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); boolean gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); if (!gpsEnabled) { //如果GPS没打开,你可以创建一个对话框,请求用户打开GPS。 } } @Override protected void onRestart() { super.onRestart(); // 这里从stop状态转入resume状态时,才会被调用 }
代码清单4-6
4.4 重新创建一个Activity
当你的act由于用户按下back键或者代码中调用finish()这种正常行为而destroy的话,act实例就一去不复返了。然而由于系统的 限制而destroy的话,虽然act的实例消失,但系统会记住这个实例。当用户导航回到上次的act,系统会使用Bundle来恢复上一次act的状 态。注意当你每次旋转屏幕时,act会被destroy然后重新创建,你可以需要重新加载一些资源,或者在AndroidManifest.xml中设置 configChange让act旋转的时候不重新创建。
默认的系统使用Bundle来保存布局中的各个View对象的信息。所以你的act实例被销毁并重新创建后,会自动恢复到以前的布局状态。然而我们 的act可能有更多的状态信息需要恢复,例如像恢复某些成员变量等。为了增加额外需要保存的数据。我们还有一个额外的回调方法 onSaveInstanceState(),当用户离开你的act系统会调用此方法,你可以通过其中的Bundle来保存需要的数据,当系统需要重新创 建实例的时候会使用onRestoreInstanceState()方法或者直接在onCreate()中使用参数Bundle来恢复。记住只有当你的 act由于系统原因被destroy时使用它们才是有用的,直白来说就是由于系统原因使act实例不存在时才需要考虑使用它们。下面我们看一下图4-3:
图4-3 保存和恢复act实例状态的示意图
4.4.1 保存你的Activity状态
随着你的act启动到停止,系统会调用onSaveInstanceState()方法,因此你的act能使用键值对的形式保存状态信息。默认来说 它只保存view的状态信息,例如EditText中的文本或者ListView滚动的位置。你的act为了保存额外的状态信息,你必须实现 onSaveInstanceState()方法,使用Bundle参数添加键值对。例如代码清单4-7:
static final String STATE_SCORE = "playerScore"; static final String STATE_LEVEL = "playerLevel"; ... @Override public void onSaveInstanceState(Bundle savedInstanceState) { // 保存用户当前的游戏状态 savedInstanceState.putInt(STATE_SCORE, mCurrentScore); savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel); // 调用父类的方法用来保存view的状态 super.onSaveInstanceState(savedInstanceState); }
代码清单4-7
4.4.2 恢复你的Activity状态
当你的act在先前被摧毁又被重新创建后,你能从Bundle中通过系统自动恢复到摧毁之前的状态。在 onCreate()和onRestoreInstanceState()两个回调方法中的Bundle是同一实例的状态信息。因为不管系统是创建新的实 例还是重新创建先前的实例都需要调用onCreate()方法,在你企图读取Bundle之前必须检查Bundle是否为null。如果为null系统会 创建一个新的act实例,否则会恢复先前摧毁的那个act实例:下面是在onCreate()中恢复实例数据的代码:如代码清单4-8所示:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //检查是否是重新创建的先前的实例 if (savedInstanceState != null) { //从保存的状态中恢复成员变量的值 mCurrentScore = savedInstanceState.getInt(STATE_SCORE); mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL); } else { // 对于一个新的实例可能需要初始化默认值而不是恢复 } ... }
代码清单4-8
你可以选择实现onRestoreInstanceState()来代替onCreate()恢复状态,此方法在onStart()之后调用。系统 只有在有保存状态的时候才会调用onRestoreInstanceState()来恢复状态,所以我们不需要检查Bundle是否为null,如代码清 单4-9所示:
public void onRestoreInstanceState(Bundle savedInstanceState) { // 调用父类的方法来恢复View的状态 super.onRestoreInstanceState(savedInstanceState); // 恢复保存实例中的成员变量 mCurrentScore = savedInstanceState.getInt(STATE_SCORE); mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL); }
代码清单4-9