在前面的文章里我们知道了 Activity 是一个应用组件,用户可与其提供的屏幕进行交互,以执行拨打电话、拍摄照片、发送电子邮件或查看地图等操作。 每个 Activity 都会获得一个用于绘制其用户界面的窗口,窗口通常会充满屏幕,但也可小于屏幕并浮动在其他窗口之上。
我们也知道一个应用通常由多个彼此松散联系的 Activity 组成,一般会指定应用中的某个 Activity 为“主”Activity,即首次启动应用时呈现给用户的那个 Activity。 而且每个 Activity 均可启动另一个 Activity,以便执行不同的操作。 每次新 Activity 启动时,前一 Activity 便会停止,但系统会在堆栈(“返回栈”)中保留该 Activity。 当新 Activity 启动时,系统会将其推送到返回栈上,并取得用户焦点。 返回栈遵循基本的“后进先出”堆栈机制,因此,当用户完成当前 Activity 并按“返回”按钮时,系统会从堆栈中将其弹出(并销毁),然后恢复前一 Activity。
而对于生命周期我们知道了当一个 Activity 因某个新 Activity 启动而停止时,系统会通过该 Activity 的生命周期回调方法通知其这一状态变化。Activity 因状态变化而收到的回调方法可能有若干种,每一种回调都会为您提供执行与该状态变化相应的特定操作的机会。 例如,停止时,我们的 Activity 应释放任何大型对象,例如网络或数据库连接。 当 Activity 恢复时,我们可以重新获取所需资源,并恢复执行中断的操作。 这些状态转变都是 Activity 生命周期的一部分。
我们本篇会阐述有关如何创建和使用 Activity 的基础知识(包括 Activity的启动和销毁、暂停与恢复、停止与重启、重新创建以及一些其他问题),这样我们就可以正确管理各种 Activity 状态之间的转变了。
一、 Activity的启动和销毁
编写过Java程序的小伙伴都知道,我们每个Java程序都有一个主入口,比如说Main()。但是Android编程不同于使用 Main() 方法启动应用的其他编程范例,Android 系统会通过调用对应于其生命周期中特定阶段的特定回调方法在 Activity 实例中启动代码。 有一系列可启动Activity的回调方法,以及一系列可分解Activity的回调方法。
1、生命周期回调
在Activity的生命周期中,系统会按类似于阶梯金字塔的顺序调用一组核心的生命周期方法。也就是说,Activity生命周期的每个阶段就是金字塔上的一阶,当系统创建新Activity实例时,每个回调方法会将Activity状态向顶端移动一阶,而金字塔的顶端就是Activity在前台运行并且用户可以与其交互的时间点。
当用户开始离开Activity时,系统会调用其他方法在金字塔中将Activity状态下移,从而销毁Activity。在有些情况下,Activity将只在金字塔中部分下移并等待(比如,当用户切换到其他应用时),Activity可从该点开始移回顶端(如果用户返回到该Activity),并在用户停止的位置继续。
如上图所示,对于用于将Activity朝顶端的“继续”状态移动一阶的每个回调,都有一种将Activity下移一阶的回调方法与之对应,也就是说Activity还可以从“暂停”和“停止”状态回到刚才的状态。
根据Activity的复杂程度,您可能不需要实现所有生命周期方法。但是了解每个方法并实现确保我们的应用按照用户期望的方式运行的方法是非常重要的。正确实现我们的Activity生命周期方法可确保我们的应用按照以下几种方式良好运行。
良好运行的几种方式:
- 如果用户在使用我们的应用时接听来电或切换到另一个应用,它不会崩溃。
- 在用户未主动使用它时不会消耗宝贵的系统资源。
- 如果用户离开我们的应用并稍后返回,不会丢失用户的进度。
- 当屏幕在横向和纵向之间旋转时,不会崩溃或丢失用户的进度。
正如我们将要在以下课程中要学习的,Activity会在上图所示不同状态之间过渡,但是这些状态中只有三种可以是静态。也就是说,Activity只能在三种状态之一下存活很长时间。
三种存活时间长的状态:
- Resumed:在这种状态下,Activity处于前台,且用户可以与其交互,有时也称为“运行”状态。
- Paused:在这种状态下,Activity被在前台中处于半透明状态或者未覆盖整个屏幕的另一个Activity—部分阻挡,暂停的Activity不会接收用户输入并且无法执行任何代码。
- Stopped:在这种状态下,Activity被完全隐藏并且对用户不可见;它被视为处于后台。停止时,Activity实例及其诸如成员变量等所有状态信息将保留,但它无法执行任何代码。
其它状态 (Created与Started)都是短暂的瞬态,系统会通过调用下一个生命周期回调方法从这些状态快速移到下一个状态。 也就是说,在系统调用 onCreate() 之后,它会快速调用 onStart(),紧接着快速调用 onResume()。
生命周期回调部分到此为止,现在我们将开始学习特定生命周期行为的一些知识。
2、指定程序首次启动的Activity
当用户在主界面点击程序图标时,系统会调用App中被声明为"launcher" (或者 "main") Activity中的 onCreate() 方法,这个Activity就是被用来当作程序的主要进入点的Activity。我们可以在 AndroidManifest.xml 中定义作为主Activity的Activity。
这个main activity必须在manifest使用包括 MAIN action 与 LAUNCHER category 的 <intent-filte 标签来声明,例如:
<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>
注意:当你使用Android SDK工具来创建Android工程时,工程中就包含了一个默认的声明有这个filter的activity类。
当然,这也就意味着如果程序中没有声明了 MAIN action 或者 LAUNCHER category的activity,那么在设备的主界面列表里面就不会呈现App图标。
3、创建一个Activity实例
大多数App包括多个 Activity,使用户可以执行不同的动作。不论这个Activity是当用户点击应用图标创建的 Main Activtiy 还是为了响应用户行为而创建的其他 Activity,系统都会调用新 Activity 实例中的 onCreate() 方法。所以我们必须实现onCreate()方法来执行程序启动所需要的基本逻辑,例如可以在 onCreate() 方法中定义UI以及实例化类成员变量。
我们用下面的onCreate()方法演示为了建立一个 Activity 所需要的一些基础操作,如声明UI元素、定义成员变量、配置UI等。当然** onCreate 里面要尽量少做事情,避免程序启动太久都看不到界面**:
TextView mTextView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
mTextView = (TextView) findViewById(R.id.text_message);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
ActionBar actionBar = getActionBar();
actionBar.setHomeButtonEnabled(false);
}
}
我们的 Activity 不会在 Created 或者 Started 状态停留。从技术上来说,Activity 在 onStart() 被调用后开始被用户可见,但是 onResume() 会迅速被执行使得 Activity 停留在 Resumed 状态,直到一些因素发生变化才会改变这个状态,例如接收到一个来电,用户切换到另外一个Activity,或者是设备屏幕关闭等。
上图显示了 onCreate()、onStart() 和 onResume() 是如何执行的。当这三个顺序执行的回调函数完成后,Activity会到达 Resumed 状态。当然,如果你足够细心的话,你会发现 onCreate() 方法包含了一个参数叫做 savedInstanceState,这部分的内容将会在后面的文章中涉及到。
4、销毁Activity
Activity 的第一个生命周期回调函数是 onCreate(),它的最后一个回调是 onDestroy(),当收到需要将该 Activity 彻底移除的信号时,系统会调用这个方法。
大多数 App 并不需要实现这个方法,因为局部类的 references 会随着 Activity 的销毁而销毁,并且我们的 Activity 应该在 onPause() 与 onStop() 中执行清除 Activity 资源的操作。然而,如果 Activity 含有在 onCreate 调用时创建的后台线程、打开的数据库、请求的网络连接等,则应该在 onDestroy() 时进行资源清理,以此避免内存泄漏。
@Override
public void onDestroy() {
super.onDestroy();
android.os.Debug.stopMethodTracing();
}
注意:除非程序在 onCreate() 方法里面就调用了 finish() 方法,系统通常是在执行了onPause() 与 onStop() 之后再调用 onDestroy() 。在某些情况下,例如我们的 Activity 只是做了一个临时的逻辑跳转的功能,它只是用来决定跳转到哪一个 Activity,这样的话就需要在 onCreate 里面调用 finish 方法,这样系统会直接调用 onDestory,跳过生命周期中的其它方法。
二、Activity的暂停与恢复
在正常使用App时,前端的Activity有时会被其他可见的组件阻塞,从而导致当前的Activity进入Pause状态。例如,当打开一个半透明的Activity时(比如对话框),之前的Activity会被暂停。 只要之前的Activity仍然被部分可见,这个Activity就会一直处于Paused状态。
然而,一旦之前的Activity被完全阻塞并不可见时,则其会进入Stop状态(这是下一个话题了)。Activity一旦进入Paused状态,系统就会调用Activity中的onPause()方法,该方法中可以停止不应该在暂停过程中执行的操作,如暂停视频播放,或者保存那些有可能需要长期保存的信息。如果用户从暂停状态回到当前Activity,系统应该恢复那些数据并执行onResume()方法。
注意: 当我们的Activity收到调用onPause()的信号时,那意味着Activity将被暂停一段时间,并且用户很可能会在将来回到我们的Activity。然而,这也是用户要离开我们的Activtiy的第一个信号。
当另一个半透明的Activity阻塞当前Activity时,系统会调用onPause()方法并且当前这个Activity会停留在 Paused 状态(1)。如果用户在这个Activity还是在Paused 状态时回到这个Activity,系统则会调用它的onResume() (2)。
1、暂停Activity
当系统调用Activity中的onPause()时,从技术上来讲,这意味着Activity仍然处于部分可见的状态,但更多时候意味着用户正在离开这个Activity,并马上会进入Stopped 状态,通常应该在onPause() 回调方法里面做以下事情:
onPause() 回调方法里做的事:
- 停止动画或者是其他正在运行的操作,那些都会导致CPU的浪费。
- 提交在用户离开时期待保存的内容(例如邮件草稿)。
- 释放系统资源,例如 Broadcast Receivers、Sensors (比如GPS) 或者其他任何会影响到电量的资源。
例如, 如果程序使用Camera时,onPause()会是一个比较好的地方去做那些释放资源的操作:
@Override
public void onPause() {
super.onPause();
if (mCamera != null) {
mCamera.release()
mCamera = null;
}
}
通常来讲,我们不应该使用onPause()来保存用户改变的数据 (例如填入表格中的个人信息) 到永久存储(文件或者数据库)上,而仅仅当确认用户期待那些改变能够被自动保存的时候(例如正在撰写邮件草稿),才把那些数据存到永久存储 。但是,我们应该避免在onPause()时执行CPU高负荷的工作,例如写数据到数据库,因为它会导致切换到下一个Activity变得缓慢,所以应该把那些高负荷的工作放到onStop()去做。
注意:当Activity处于暂停状态时,Activity的实例是驻留在内存中的,并且在Activity恢复的时候会被重新调用,所以我们不需要在恢复到Resumed状态的一系列回调方法中重新初始化组件。
2、恢复Activity
当用户从Paused状态恢复Activity时,系统会调用onResume()方法。需要我们注意的是,系统每次调用这个方法时,Activity都是处于前台的,包括第一次创建的时候。所以,应该在onResume()中初始化那些在onPause方法里面释放掉的组件,并执行那些Activity每次进入Resumed 状态都需要的初始化动作 (例如开始动画与初始化那些只有在获取用户焦点时才需要的组件)。
下面的onResume()的例子是与上面的onPause()例子相对应的:
@Override
public void onResume() {
super.onResume();
if (mCamera == null) {
initializeCamera();
}
}
三、Activity的停止与重启
恰当的停止与重启我们的Activity是很重要的,在Activity生命周期中,他们能确保用户感知到程序的存在并不会丢失他们的进度。在下面一些关键的场景中会涉及到停止与重启:
停止与重启的场景:
- 用户打开最近使用App的菜单并从我们的App切换到另外一个App,这个时候我们的App是被停止的。如果用户通过手机主界面的启动程序图标或者最近使用程序的窗口回到我们的App,那么我们的Activity会重启。
- 用户在我们的App里面执行启动一个新Activity的操作,当前Activity会在第二个Activity被创建后Stop。如果用户点击Back按钮,第一个Activtiy会被重启。
- 用户在使用我们的app时接收到一个来电通话.
Activity 类提供了 onStop() 与 onRestart() 方法来允许在 Activity 停止与重启时进行调用。不同于暂停状态的部分阻塞UI,停止状态是UI不再可见并且用户的焦点转移到另一个Activity中。
注意:因为系统在Activity停止时会在内存中保存Activity的实例,所以有时不需要实现onStop()、onRestart()甚至是onStart()方法,因为大多数的Activity相对比较简单,Activity会自己停止与重启,我们只需要使用onPause()来停止正在运行的动作并断开系统资源链接。
上图显示:当用户离开我们的Activity时,系统会调用onStop()来停止Activity (1),这个时候如果用户返回,系统会调用onRestart()(2),之后会迅速调用 onStart()(3) 与 onResume()(4)。也就是说:无论什么原因导致Activity停止,系统总是会在onStop()之前调用onPause()方法。
2、停止Activity
当Activity调用onStop()方法时,Activity变得不再可见,并且应该释放那些不再需要的所有资源。一旦Activity停止了,系统会在需要内存空间时摧毁它的实例(和栈结构有关,通常back操作会导致前一个Activity被销毁)。极端情况下,系统会直接杀死我们的App进程,并不执行Activity的onDestroy()回调方法, 因此我们需要使用onStop()来释放资源,从而避免内存泄漏。尽管onPause()方法是在onStop()之前调用,我们应该使用onStop()来执行那些CPU高负荷的关闭操作,例如往数据库写信息。
下面是一个在onStop()的方法里面保存笔记草稿到持久化内存的示例:
@Override
protected void onStop() {
super.onStop();
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);
}
Activity已经停止后,Activity 对象会保存在内存中,并在Activity Resume时被重新调用。我们不需要在恢复到Resumed状态前重新初始化那些被保存在内存中的组件。系统同样保存了每一个在布局中的视图的当前状态,如果用户在EditText组件中输入了text,它会被保存,因此不需要保存与恢复它。
注意:即使系统会在Activity stop时停止这个Activity,它仍然会保存 View 对象的状态(比如 EditText 中的文字到一个 Bundle 中,并且在用户返回这个Activity时恢复它们(后面的文章会介绍在Activity销毁与重新建立时如何使用 Bundle 来保存其他数据的状态。
3、启动与重启Activity
当Activity从Stopped状态回到前台时,它会调用onRestart(),系统再调用onStart()方法,onStart()方法会在每次Activity可见时都会被调用。onRestart()方法则是只在Activity从stopped状态恢复时才会被调用,因此我们可以使用它来执行一些特殊的恢复工作,请注意之前是被Stopped而不是Destrory。
使用onRestart()来恢复Activity状态是不太常见的,因此对于这个方法如何使用没有任何的指导。然而,因为onStop()方法应该做清除所有Activity资源的操作,我们需要在重启Activtiy时重新实例化那些被清除的资源,同样,我们也需要在Activity第一次创建时实例化那些资源。介于上面的原因,应该使用onStart()作为onStop()所对应方法。因为系统会在创建Activity与从停止状态重启Activity时都会调用onStart()。也就是说,我们在onStop里面做了哪些清除的操作,就该在onStart里面重新把那些清除掉的资源重新创建出来。
因为用户很可能在回到这个Activity之前已经过了很长一段时间,所以onStart()方法是一个比较好的地方来验证某些必须的系统特性是否可用:
@Override
protected void onStart() {
super.onStart();
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
boolean gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
if (!gpsEnabled) {
}
}
@Override
protected void onRestart() {
super.onRestart();
}
当系统Destory我们的Activity,它会为Activity调用onDestroy()方法。因为我们会在onStop方法里面做释放资源的操作,那么onDestory方法则是我们最后去清除那些可能导致内存泄漏的地方,因此需要确保那些线程都被Destroyed并且所有的操作都被停止。
四、Activity的重新创建
有几个场景Activity是由于正常的程序行为而被Destory的。例如当用户点击返回按钮或者是Activity通过调用finish()来发出停止信号。系统也有可能会在Activity处于Stop状态且长时间不被使用,或者是在前台Activity需要更多系统资源的时关闭后台进程,以图获取更多的内存。当Activity是因为用户点击Back按钮或者是Activity通过调用Finish()结束自己时,系统就丢失了对Activity实例的引用,因为这一行为意味着不再需要这个Activity了。
然而,如果因为系统资源紧张而导致Activity的Destory, 系统会在用户回到这个Activity时有这个Activity存在过的记录,系统会使用那些保存的记录数据(描述了当Activity被Destory时的状态)来重新创建一个新的Activity实例。那些被系统用来恢复之前状态而保存的数据被叫做 "instance state" ,它是一些存放在Bundle对象中的键值对。
注意:我们的Activity会在每次旋转屏幕时被Destroyed与Recreated,当屏幕改变方向时,系统会Destory与Recreate前台的Activity,因为屏幕配置被改变,你的Activity可能需要加载另一些替代的资源。
默认情况下,系统使用 Bundle 实例来保存每一个View(视图)对象中的信息(例如输入EditText 中的文本内容)。因此,如果Activity被Destroyed与Recreated,则layout的状态信息会自动恢复到之前的状态。然而,Activity也许存在更多你想要恢复的状态信息,例如记录用户Progress的成员变量。
注意:为了使Android系统能够恢复Activity中的View的状态,每个View都必须有一个唯一ID,由android:id定义。
为了可以保存额外更多的数据到saved instance state,在Activity的生命周期里面存在一个额外的回调函数,我们必须重写这个函数。该回调函数并没有在前面的图片示例中显示,这个方法是 onSaveInstanceState() ,当用户离开Activity时,系统会调用它。当系统调用这个函数时,系统会在Activity被异常Destory时传递 Bundle 对象,这样我们就可以增加额外的信息到Bundle中并保存到系统中。若系统在Activity被Destory之后想重新创建这个Activity实例,之前的Bundle对象会被传递到你我们Activity的 onRestoreInstanceState() 方法与 onCreate() 方法中。
注意: 当系统开始停止Activity时,只有在Activity实例需要重新创建的情况下才会调用到onSaveInstanceState()(1) ,在这个方法里面可以指定额外的状态数据到Bunde中。如果这个Activity被Destroyed然后这个实例又需要被重新创建时,系统会传递在 (1) 中的状态数据到 onCreate() (2) 与 onRestoreInstanceState()(3)。
通常来说,跳转到其他的activity或者是点击Home都会导致当前的Activity执行onSaveInstanceState,因为这种情况下的Activity都是有可能会被Destory并且是需要保存状态以便后续恢复使用的,而从跳转的Activity点击Back回到前一个Activity,那么跳转前的Activity是执行退栈的操作,所以这种情况下是不会执行onSaveInstanceState的,因为这个Activity不可能存在需要重建的操作。
1、保存Activity状态
当我们的Activity开始Stop,系统会调用 onSaveInstanceState() ,Activity可以用键值对的集合来保存状态信息。这个方法会默认保存Activity视图的状态信息,如在 EditText 组件中的文本或 RecyclerView 的滑动位置。
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);
super.onSaveInstanceState(savedInstanceState);
}
注意:我们必须要调用 onSaveInstanceState() 方法的父类实现,这样默认的父类实现才能保存视图状态的信息。
2、恢复Activity状态
当Activity从Destory中重建,我们可以从系统传递的Activity的Bundle中恢复保存的状态。 onCreate() 与 onRestoreInstanceState() 回调方法都接收到了同样的Bundle,里面包含了同样的实例状态信息。
由于 onCreate() 方法会在第一次创建新的Activity实例与重新创建之前被Destory的实例时都被调用,我们必须在尝试读取 Bundle 对象前检测它是否为null。如果它为null,系统则是创建一个新的Activity实例,而不是恢复之前被Destory的Activity。
下面是一个示例:演示在onCreate方法里面恢复一些数据:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
} else {
}
...
}
我们也可以选择实现 onRestoreInstanceState() ,而不是在onCreate方法里面恢复数据。 onRestoreInstanceState()方法会在 onStart() 方法之后执行,系统仅仅会在存在需要恢复的状态信息时才会调用 onRestoreInstanceState() ,因此不需要检查 Bundle 是否为null。
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
}
注意:与上面保存一样,总是需要调用onRestoreInstanceState()方法的父类实现,这样默认的父类实现才能保存视图状态的信息。
点此进入:GitHub开源项目“爱阅”。
感谢优秀的你跋山涉水看到了这里,欢迎关注下让我们永远在一起!